@domternal/core 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { Schema, Node as Node$1, DOMOutputSpec, NodeType, Mark as Mark$1, MarkType, Attrs, DOMParser, ResolvedPos, ContentMatch, NodeSpec, MarkSpec } from '@domternal/pm/model';
1
+ import { Schema, Node as Node$1, DOMOutputSpec, NodeType, Mark as Mark$1, MarkType, Attrs, ResolvedPos, DOMParser, ContentMatch, NodeSpec, MarkSpec } from '@domternal/pm/model';
2
2
  import { Transaction, EditorState, Plugin, PluginKey, Selection as Selection$1 } from '@domternal/pm/state';
3
3
  export { PluginKey } from '@domternal/pm/state';
4
4
  import { InputRule } from '@domternal/pm/inputrules';
5
- import { EditorView, NodeViewConstructor } from '@domternal/pm/view';
5
+ import { NodeViewConstructor, EditorView } from '@domternal/pm/view';
6
6
  import { Placement } from '@floating-ui/dom';
7
7
 
8
8
  /**
@@ -131,8 +131,7 @@ interface ErrorEventProps$1 {
131
131
  context: string;
132
132
  }
133
133
  /**
134
- * All editor events with their payload types
135
- * Used by EventEmitter for type-safe event handling
134
+ * All editor events with their payload types.
136
135
  */
137
136
  interface EditorEvents {
138
137
  /** Fired before editor is created - can modify options */
@@ -169,6 +168,10 @@ interface EditorEvents {
169
168
  linkEdit: {
170
169
  anchorElement?: HTMLElement;
171
170
  };
171
+ /** Fired when the Notion color picker should open, with the trigger as anchor. */
172
+ notionColorOpen: {
173
+ anchorElement?: HTMLElement | null;
174
+ };
172
175
  }
173
176
  /**
174
177
  * Event names as a type
@@ -212,11 +215,9 @@ type FocusPosition = boolean | 'start' | 'end' | 'all' | number | null;
212
215
  */
213
216
  interface EditorOptions {
214
217
  /**
215
- * ProseMirror Schema for the editor
216
- *
217
- * Step 1.3: Required (no extensions system yet)
218
- * Step 2+: Optional if extensions are provided (schema built from extensions)
218
+ * ProseMirror Schema for the editor.
219
219
  *
220
+ * Optional when `extensions` is provided (schema is built from them).
220
221
  * The schema must contain at least 'doc' and 'text' nodes.
221
222
  */
222
223
  schema?: Schema;
@@ -480,7 +481,7 @@ interface ToolbarButton {
480
481
  * How to check if this button is active.
481
482
  * - string: extension name passed to `editor.isActive(name)`
482
483
  * - object: `{ name, attributes }` passed to `editor.isActive(name, attributes)`
483
- * - array: OR-check active if ANY entry matches (useful for attributes on multiple node types)
484
+ * - array: OR-check - active if ANY entry matches (useful for attributes on multiple node types)
484
485
  * - undefined: button has no active state (e.g. undo/redo)
485
486
  */
486
487
  isActive?: string | {
@@ -602,1485 +603,1600 @@ interface ToolbarLayoutDropdown {
602
603
  /**
603
604
  * A single entry in a toolbar layout array.
604
605
  *
605
- * - `string` item name (e.g. `'bold'`) or separator (`'|'`)
606
- * - `ToolbarLayoutDropdown` custom dropdown grouping
606
+ * - `string` - item name (e.g. `'bold'`) or separator (`'|'`)
607
+ * - `ToolbarLayoutDropdown` - custom dropdown grouping
607
608
  */
608
609
  type ToolbarLayoutEntry = string | ToolbarLayoutDropdown;
609
610
 
610
611
  /**
611
- * Extension configuration types
612
+ * Callback type for event handlers
613
+ * - Events with payload: (data: T) => void
614
+ * - Events without payload (undefined): () => void
615
+ */
616
+ type EventCallback<T> = T extends undefined ? () => void : (data: T) => void;
617
+ /**
618
+ * Generic, type-safe event emitter
612
619
  *
613
- * These types define the configuration object passed to Extension.create(),
614
- * Node.create(), and Mark.create() factory methods.
620
+ * @example
621
+ * ```typescript
622
+ * interface MyEvents {
623
+ * update: { value: number };
624
+ * destroy: undefined;
625
+ * }
626
+ *
627
+ * const emitter = new EventEmitter<MyEvents>();
628
+ * emitter.on('update', ({ value }) => console.log(value));
629
+ * emitter.on('destroy', () => console.log('destroyed'));
630
+ * ```
615
631
  */
632
+ declare class EventEmitter<Events extends {
633
+ [K in keyof Events]: unknown;
634
+ } = Record<string, never>> {
635
+ protected callbacks: Map<keyof Events, Set<(data: unknown) => void>>;
636
+ /**
637
+ * Register an event listener
638
+ */
639
+ on<E extends keyof Events>(event: E, callback: EventCallback<Events[E]>): this;
640
+ /**
641
+ * Remove an event listener, or all listeners for an event if no callback specified
642
+ */
643
+ off<E extends keyof Events>(event: E, callback?: EventCallback<Events[E]>): this;
644
+ /**
645
+ * Emit an event to all registered listeners
646
+ * Uses .call(this) to preserve context for callbacks
647
+ */
648
+ emit<E extends keyof Events>(event: E, ...args: Events[E] extends undefined ? [] : [Events[E]]): this;
649
+ /**
650
+ * Register an event listener that fires only once
651
+ */
652
+ once<E extends keyof Events>(event: E, callback: EventCallback<Events[E]>): this;
653
+ /**
654
+ * Remove all listeners for a specific event, or all events if no event specified
655
+ */
656
+ removeAllListeners(event?: keyof Events): this;
657
+ /**
658
+ * Get the number of listeners for a specific event
659
+ */
660
+ listenerCount(event: keyof Events): number;
661
+ /**
662
+ * Get all event names that have listeners
663
+ */
664
+ eventNames(): (keyof Events)[];
665
+ }
616
666
 
617
667
  /**
618
- * Editor instance type (forward declaration)
668
+ * ExtensionManager - manages extensions and schema.
669
+ *
670
+ * Handles:
671
+ * - Extension lifecycle (flatten, resolve, bind)
672
+ * - Schema building from Node/Mark extensions
673
+ * - Plugin collection from all extensions
674
+ * - Extension storage management
675
+ * - Conflict detection (duplicate extension names)
619
676
  */
620
- interface ExtensionEditor {
621
- readonly state: EditorState;
622
- readonly view: EditorView;
623
- readonly schema: unknown;
624
- readonly commands: SingleCommands;
677
+
678
+ /**
679
+ * Error event props for safeCall
680
+ */
681
+ interface ErrorEventProps {
682
+ error: Error;
683
+ context: string;
625
684
  }
626
685
  /**
627
- * Any extension type (forward declaration)
686
+ * Editor interface for ExtensionManager
687
+ * Forward declaration to avoid circular dependency
628
688
  */
629
- interface AnyExtensionConfig {
630
- name: string;
631
- type?: 'extension' | 'node' | 'mark';
689
+ interface ExtensionManagerEditor {
690
+ readonly schema: Schema;
691
+ emit?(event: 'error', props: ErrorEventProps): void;
632
692
  }
633
693
  /**
634
- * Global attribute specification for injecting attributes into multiple node/mark types.
635
- * Used by extensions like TextAlign to add alignment to heading, paragraph, etc.
694
+ * Context attached to node view constructors for framework wrappers.
695
+ * Accessible via `(constructor as any).__domternalContext`.
636
696
  */
637
- interface GlobalAttributeSpec {
697
+ interface NodeViewContext {
698
+ editor: ExtensionManagerEditor;
699
+ extension: {
700
+ name: string;
701
+ options: Record<string, unknown>;
702
+ };
703
+ }
704
+ /**
705
+ * Options for ExtensionManager constructor
706
+ */
707
+ interface ExtensionManagerOptions {
638
708
  /**
639
- * Default value for the attribute.
709
+ * Extensions to process
710
+ * If provided, schema is built from extensions
640
711
  */
641
- default?: unknown;
712
+ extensions?: AnyExtension[] | undefined;
642
713
  /**
643
- * Parse attribute value from HTML element.
644
- * @param element - The DOM element to parse from
645
- * @returns The parsed attribute value
714
+ * Direct schema. If provided, extensions are ignored for schema building.
646
715
  */
647
- parseHTML?: (element: HTMLElement) => unknown;
716
+ schema?: Schema | undefined;
717
+ }
718
+ declare class ExtensionManager {
648
719
  /**
649
- * Render attribute value to HTML attributes.
650
- * @param attributes - The node/mark attributes
651
- * @returns Object of HTML attributes to set, or null/empty to skip
720
+ * Processed extensions (flattened, sorted by priority)
652
721
  */
653
- renderHTML?: (attributes: Record<string, unknown>) => Record<string, string> | null;
654
- }
655
- /**
656
- * Global attributes definition for injecting into node/mark types.
657
- */
658
- interface GlobalAttributes {
722
+ private readonly _extensions;
659
723
  /**
660
- * Node or mark type names to add these attributes to.
661
- * @example ['heading', 'paragraph']
724
+ * ProseMirror schema (built from extensions or passed directly)
662
725
  */
663
- types: string[];
726
+ private readonly _schema;
664
727
  /**
665
- * Attribute specifications to add.
666
- * @example { textAlign: { default: 'left', parseHTML: (el) => el.style.textAlign } }
728
+ * Reference to the editor instance
667
729
  */
668
- attributes: Record<string, GlobalAttributeSpec>;
669
- }
670
- /**
671
- * Context interface that describes what `this` will be in config methods.
672
- * This enables proper typing of `this.options`, `this.editor`, etc.
673
- * without creating circular dependencies.
674
- *
675
- * @typeParam Options - Extension options type
676
- * @typeParam Storage - Extension storage type
677
- */
678
- interface ExtensionContext<Options = unknown, Storage = unknown> {
679
- /** Extension type identifier */
680
- readonly type: 'extension' | 'node' | 'mark';
681
- /** Unique extension name */
682
- readonly name: string;
683
- /** Extension options (immutable after creation) */
684
- readonly options: Options;
685
- /** Extension storage (mutable state) */
686
- storage: Storage;
687
- /** Editor instance (null until bound by ExtensionManager) */
688
- editor: ExtensionEditor | null;
730
+ readonly editor: ExtensionManagerEditor;
689
731
  /**
690
- * Reference to the parent config method when using extend().
691
- * Available only inside overridden config methods.
692
- * Use `this.parent?.()` to call the parent's version of the current method.
732
+ * Extension storage (keyed by extension name)
693
733
  */
694
- parent?: ((...args: unknown[]) => unknown) | undefined;
695
- }
696
- /**
697
- * Base configuration properties for all extension types.
698
- * This interface contains just the properties without ThisType,
699
- * allowing Node and Mark configs to use their own context types.
700
- *
701
- * @typeParam Options - Extension options type
702
- * @typeParam Storage - Extension storage type
703
- */
704
- interface ExtensionConfigBase<Options = unknown, Storage = unknown> {
734
+ private readonly _storage;
705
735
  /**
706
- * Unique extension name
707
- * Used for identification, storage access, and error messages
736
+ * Whether the manager has been destroyed
708
737
  */
709
- name: string;
738
+ private isDestroyed;
710
739
  /**
711
- * Extension priority (higher = loaded first)
712
- *
713
- * Reserved ranges:
714
- * - 900-1000: Core nodes (Document, Text, Paragraph)
715
- * - 500-899: Standard extensions (Heading, Bold, etc.)
716
- * - 100-499: Default range for user extensions
717
- * - 0-99: Low priority extensions (run after everything)
718
- *
719
- * @default 100
740
+ * Cached plugins (built lazily)
720
741
  */
721
- priority?: number;
742
+ private _plugins;
722
743
  /**
723
- * Required extensions that must be present
724
- * ExtensionManager throws if any dependency is missing
725
- *
726
- * @example
727
- * dependencies: ['bulletList', 'orderedList']
744
+ * Cached commands (collected lazily)
728
745
  */
729
- dependencies?: string[];
746
+ private _commands;
730
747
  /**
731
- * Default options for this extension
732
- * Called during extension creation with `this` bound to the extension
748
+ * Cached toolbar items (collected lazily)
733
749
  */
734
- addOptions?: () => Options;
750
+ private _toolbarItems;
735
751
  /**
736
- * Initial storage state for this extension
737
- * Storage is mutable and accessible via editor.storage[extensionName]
752
+ * Cached floating-menu items (collected lazily)
738
753
  */
739
- addStorage?: () => Storage;
754
+ private _floatingMenuItems;
740
755
  /**
741
- * Commands this extension provides
742
- * Commands are accessible via editor.commands.commandName()
743
- *
744
- * @example
745
- * addCommands() {
746
- * return {
747
- * toggleBold: () => ({ commands }) => commands.toggleMark('bold'),
748
- * };
749
- * }
756
+ * Cached node views (collected lazily)
750
757
  */
751
- addCommands?: () => Record<string, (...args: never[]) => Command>;
758
+ private _nodeViews;
752
759
  /**
753
- * Keyboard shortcuts for this extension
754
- * Keys are shortcut strings (e.g., 'Mod-b'), values are handler functions
760
+ * Creates a new ExtensionManager
755
761
  *
756
- * @example
757
- * addKeyboardShortcuts() {
758
- * return {
759
- * 'Mod-b': () => this.editor.commands.toggleBold(),
760
- * };
761
- * }
762
+ * @param options - Extensions or direct schema
763
+ * @param editor - Editor instance
762
764
  */
763
- addKeyboardShortcuts?: () => Record<string, KeyboardShortcutCommand>;
765
+ constructor(options: ExtensionManagerOptions, editor: ExtensionManagerEditor);
764
766
  /**
765
- * Input rules (markdown-style shortcuts)
766
- * Triggered when user types matching patterns
767
- *
768
- * @example
769
- * addInputRules() {
770
- * return [
771
- * textblockTypeInputRule(/^## $/, this.type),
772
- * ];
773
- * }
767
+ * Gets the processed extensions array
774
768
  */
775
- addInputRules?: () => InputRule[];
769
+ get extensions(): readonly AnyExtension[];
776
770
  /**
777
- * ProseMirror plugins for this extension
771
+ * Gets the ProseMirror schema
778
772
  */
779
- addProseMirrorPlugins?: () => Plugin[];
773
+ get schema(): Schema;
780
774
  /**
781
- * Nested extensions (for extension bundles like StarterKit)
782
- * These extensions are flattened and processed like top-level extensions
775
+ * Gets extension storage (accessed via editor.storage)
783
776
  */
784
- addExtensions?: () => AnyExtensionConfig[];
777
+ get storage(): Record<string, unknown>;
785
778
  /**
786
- * Global attributes to add to multiple node/mark types.
787
- * Useful for extensions like TextAlign that need to add attributes
788
- * to several nodes (heading, paragraph, etc.) without modifying each.
789
- *
790
- * @example
791
- * addGlobalAttributes() {
792
- * return [{
793
- * types: ['heading', 'paragraph'],
794
- * attributes: {
795
- * textAlign: {
796
- * default: 'left',
797
- * parseHTML: (element) => element.style.textAlign || 'left',
798
- * renderHTML: (attrs) => attrs.textAlign !== 'left'
799
- * ? { style: `text-align: ${attrs.textAlign}` }
800
- * : null,
801
- * },
802
- * },
803
- * }];
804
- * }
805
- */
806
- addGlobalAttributes?: () => GlobalAttributes[];
807
- /**
808
- * Toolbar items this extension contributes.
809
- * Framework toolbar components read these to auto-generate buttons.
810
- *
811
- * @example
812
- * addToolbarItems() {
813
- * return [{
814
- * type: 'button',
815
- * name: 'bold',
816
- * command: 'toggleBold',
817
- * isActive: 'bold',
818
- * icon: 'textB',
819
- * label: 'Bold',
820
- * shortcut: 'Mod-b',
821
- * group: 'format',
822
- * }];
823
- * }
779
+ * Gets plugins from all extensions
780
+ * Cached after first call
824
781
  */
825
- addToolbarItems?: () => ToolbarItem[];
782
+ get plugins(): Plugin[];
826
783
  /**
827
- * Called before editor is created
828
- * Can be used to modify editor options
784
+ * Gets commands from all extensions
829
785
  */
830
- onBeforeCreate?: () => void;
786
+ get commands(): CommandMap;
831
787
  /**
832
- * Called when editor is fully initialized and ready
788
+ * Gets toolbar items from all extensions
789
+ * Cached after first call
833
790
  */
834
- onCreate?: () => void;
791
+ get toolbarItems(): ToolbarItem[];
835
792
  /**
836
- * Called when document content changes
793
+ * Gets floating-menu items from all extensions
794
+ * Cached after first call
837
795
  */
838
- onUpdate?: () => void;
796
+ get floatingMenuItems(): FloatingMenuItem[];
839
797
  /**
840
- * Called when selection changes (without content change)
798
+ * Gets node views from all Node extensions that define addNodeView
841
799
  */
842
- onSelectionUpdate?: () => void;
800
+ get nodeViews(): Record<string, NodeViewConstructor>;
843
801
  /**
844
- * Called on every transaction
845
- * Can be used to intercept or modify transactions
802
+ * Clears all caches (plugins, commands)
803
+ * Call when extensions change dynamically
846
804
  */
847
- onTransaction?: (props: {
848
- transaction: Transaction;
849
- }) => void;
805
+ clearAllCaches(): void;
850
806
  /**
851
- * Called when editor receives focus
807
+ * Recursively flattens extensions by expanding addExtensions()
808
+ * This allows extension bundles like StarterKit to work
852
809
  */
853
- onFocus?: (props: {
854
- event: FocusEvent;
855
- }) => void;
810
+ private flattenExtensions;
856
811
  /**
857
- * Called when editor loses focus
812
+ * Removes duplicate extensions by name, keeping the last occurrence.
813
+ * This allows parent extensions to auto-include children via addExtensions()
814
+ * while letting users override with explicitly configured versions.
858
815
  */
859
- onBlur?: (props: {
860
- event: FocusEvent;
861
- }) => void;
816
+ private deduplicateExtensions;
862
817
  /**
863
- * Called when editor is being destroyed
864
- * Use for cleanup (remove event listeners, etc.)
818
+ * Sorts extensions by priority (higher priority first)
819
+ * Default priority is 100
865
820
  */
866
- onDestroy?: () => void;
867
- }
868
- /**
869
- * Full configuration type for Extension.create()
870
- * Combines base properties with ThisType for proper `this` typing.
871
- *
872
- * @typeParam Options - Extension options type
873
- * @typeParam Storage - Extension storage type
874
- */
875
- type ExtensionConfig<Options = unknown, Storage = unknown> = ExtensionConfigBase<Options, Storage> & ThisType<ExtensionContext<Options, Storage>>;
876
-
877
- /**
878
- * Attribute specification for Node and Mark extensions
879
- *
880
- * Used by addAttributes() to define node/mark attributes
881
- * with parsing, rendering, and validation rules.
882
- *
883
- * @example
884
- * addAttributes() {
885
- * return {
886
- * level: {
887
- * default: 1,
888
- * parseHTML: (element) => parseInt(element.tagName.charAt(1), 10),
889
- * renderHTML: (attributes) => ({ 'data-level': attributes.level }),
890
- * },
891
- * };
892
- * }
893
- */
894
- /**
895
- * Specification for a single attribute
896
- */
897
- interface AttributeSpec {
821
+ private resolveExtensions;
898
822
  /**
899
- * Default value for the attribute
900
- * Used when the attribute is not explicitly set
823
+ * Detects duplicate extension names.
824
+ * @throws Error if duplicate names found
901
825
  */
902
- default?: unknown;
826
+ private detectConflicts;
903
827
  /**
904
- * Whether this attribute is rendered to the DOM
905
- * @default true
828
+ * Validates that all extension dependencies are present
829
+ * @throws Error if required dependency is missing
906
830
  */
907
- rendered?: boolean;
831
+ private checkDependencies;
908
832
  /**
909
- * Keep this attribute when splitting the node
910
- * For example, heading level should be kept when pressing Enter
911
- * @default true
833
+ * Sets editor reference on all extensions
912
834
  */
913
- keepOnSplit?: boolean;
835
+ private bindEditorToExtensions;
914
836
  /**
915
- * Validate attribute value (ProseMirror 1.22.0+).
916
- * When a string, a `|`-separated list of primitive types
917
- * (`"number"`, `"string"`, `"boolean"`, `"null"`, `"undefined"`).
918
- * When a function, it should throw if the value is invalid.
919
- *
920
- * @example
921
- * validate: 'number'
922
- * validate: 'string|null'
923
- * validate: (value) => { if (typeof value !== 'number') throw new Error('expected number'); }
837
+ * Collects global attributes from all extensions.
838
+ * Returns a map of type name -> attribute specs to merge.
924
839
  */
925
- validate?: string | ((value: unknown) => void);
840
+ private collectGlobalAttributes;
926
841
  /**
927
- * Parse attribute value from HTML element
928
- * Called during HTML parsing to extract attribute value
929
- *
930
- * @param element - The HTML element being parsed
931
- * @returns The attribute value
932
- *
933
- * @example
934
- * parseHTML: (element) => element.getAttribute('data-level')
842
+ * Applies global attributes to a node or mark spec.
843
+ * Merges extra attrs into spec.attrs, wraps parseDOM getAttrs to parse
844
+ * global attributes from DOM elements, and wraps toDOM to inject rendered
845
+ * global HTML attributes into the output.
935
846
  */
936
- parseHTML?: (element: HTMLElement) => unknown;
847
+ private applyGlobalAttributes;
937
848
  /**
938
- * Render attribute to HTML attributes object
939
- * Called during HTML serialization
940
- *
941
- * @param attributes - All attributes of the node/mark
942
- * @returns HTML attributes object or null to skip
943
- *
944
- * @example
945
- * renderHTML: (attributes) => ({ 'data-level': attributes.level })
849
+ * Builds ProseMirror Schema from Node and Mark extensions
946
850
  */
947
- renderHTML?: (attributes: Record<string, unknown>) => Record<string, string | number | boolean | null | undefined> | null;
948
- }
949
- /**
950
- * Collection of attribute specifications
951
- * Keyed by attribute name
952
- */
953
- type AttributeSpecs = Record<string, AttributeSpec>;
954
-
955
- /**
956
- * Node configuration types
957
- *
958
- * These types define the configuration object passed to Node.create()
959
- * for creating ProseMirror node extensions.
960
- */
961
-
962
- /**
963
- * Editor interface for Node context
964
- * Includes schema with nodes for NodeType getter
965
- */
966
- interface NodeEditorContext {
967
- readonly state: EditorState;
968
- readonly view: EditorView;
969
- readonly schema: {
970
- nodes: Record<string, NodeType>;
971
- };
972
- readonly commands: Record<string, (...args: unknown[]) => boolean>;
973
- }
974
- /**
975
- * Context interface for Node config methods.
976
- * Extends ExtensionContext with node-specific properties.
977
- * This enables proper typing of `this.options`, `this.nodeType`, etc.
978
- *
979
- * @typeParam Options - Node options type
980
- * @typeParam Storage - Node storage type
981
- */
982
- interface NodeContext<Options = unknown, Storage = unknown> extends Omit<ExtensionContext<Options, Storage>, 'editor' | 'type'> {
983
- /** Node type identifier */
984
- readonly type: 'node';
985
- /** Editor instance with schema access */
986
- editor: NodeEditorContext | null;
987
- /** ProseMirror NodeType (null until editor is initialized) */
988
- readonly nodeType: NodeType | null;
989
- }
990
- /**
991
- * Parse rule for converting HTML to ProseMirror node
992
- * Simplified version of ProseMirror's ParseRule
993
- */
994
- interface NodeParseRule {
851
+ private buildSchema;
995
852
  /**
996
- * CSS selector or tag name to match
997
- * @example 'p', 'h1', 'div.my-class'
853
+ * Initializes storage for all extensions
998
854
  */
999
- tag?: string;
855
+ private initializeStorage;
1000
856
  /**
1001
- * Match by CSS style
1002
- * @example 'font-weight'
857
+ * Builds all ProseMirror plugins from extensions
1003
858
  */
1004
- style?: string;
859
+ private buildPlugins;
1005
860
  /**
1006
- * Priority for this rule (higher = checked first)
1007
- * @default 50
861
+ * Collects keyboard shortcuts from all extensions
862
+ * Returns ProseMirror-compatible commands for keymap plugin
863
+ *
864
+ * Note: Extensions should return PM-compatible commands from addKeyboardShortcuts()
1008
865
  */
1009
- priority?: number;
866
+ private collectKeyboardShortcuts;
1010
867
  /**
1011
- * Whether to consume the matched element
1012
- * @default true
868
+ * Collects input rules from all extensions
1013
869
  */
1014
- consuming?: boolean;
870
+ private collectInputRules;
1015
871
  /**
1016
- * Context required for this rule to match
1017
- * ProseMirror content expression
872
+ * Collects commands from all extensions
873
+ *
874
+ * Note: Commands with the same name will be overwritten by later extensions
875
+ * (lower priority extensions override higher priority). This is intentional
876
+ * to allow customization of built-in commands.
1018
877
  */
1019
- context?: string;
878
+ private collectCommands;
1020
879
  /**
1021
- * Get attributes from the matched element
1022
- * Return null/undefined to skip this rule
880
+ * Collects toolbar items from all extensions
1023
881
  */
1024
- getAttrs?: ((node: HTMLElement) => Record<string, unknown> | null | undefined) | null;
882
+ private collectToolbarItems;
1025
883
  /**
1026
- * Get content from the matched element
1027
- * Return false to use default content parsing
884
+ * Collects floating-menu items from all extensions via `addFloatingMenuItems()`.
1028
885
  */
1029
- getContent?: (node: HTMLElement, schema: unknown) => unknown;
886
+ private collectFloatingMenuItems;
1030
887
  /**
1031
- * How to preserve whitespace
1032
- */
1033
- preserveWhitespace?: boolean | 'full';
1034
- }
1035
- /**
1036
- * Props passed to renderHTML function
1037
- */
1038
- interface NodeRenderHTMLProps {
1039
- /**
1040
- * The ProseMirror node being rendered
1041
- */
1042
- node: Node$1;
1043
- /**
1044
- * Merged HTML attributes from all sources
1045
- */
1046
- HTMLAttributes: Record<string, unknown>;
1047
- }
1048
- /**
1049
- * Node-specific configuration properties (schema-related)
1050
- */
1051
- interface NodeSchemaProperties {
1052
- /**
1053
- * Node group(s) this node belongs to
1054
- * Used in content expressions
888
+ * Collects node views from all Node extensions.
889
+ * Returns a map of node name to NodeViewConstructor for EditorView.
1055
890
  *
1056
- * @example 'block', 'inline', 'block list'
1057
- * Can be a function that returns the group string (useful for dynamic group based on options)
891
+ * Each constructor is annotated with `__domternalContext` containing
892
+ * the editor and extension metadata so framework wrappers (React, Vue)
893
+ * can access them without changing the ProseMirror calling convention.
1058
894
  */
1059
- group?: string | (() => string);
895
+ private collectNodeViews;
1060
896
  /**
1061
- * Content expression defining allowed children
1062
- * Uses ProseMirror content expression syntax
1063
- *
1064
- * @example 'inline*', 'block+', 'paragraph block*'
897
+ * Validates that the schema has required nodes
898
+ * @throws Error if schema is missing 'doc' or 'text' nodes
1065
899
  */
1066
- content?: string;
900
+ validateSchema(): void;
1067
901
  /**
1068
- * Whether this is an inline node
1069
- * Can be a function for dynamic inline based on options
1070
- * @default false
902
+ * Cleans up the extension manager
903
+ * Calls onDestroy on all extensions and clears all caches
1071
904
  */
1072
- inline?: boolean | (() => boolean);
905
+ destroy(): void;
1073
906
  /**
1074
- * Whether this node is an atom (no direct content editing)
1075
- * Cursor moves around atoms, not into them
907
+ * Safely executes a function, catching and reporting errors
908
+ * Prevents a single extension error from crashing the entire editor
1076
909
  *
1077
- * @example true for images, mentions, emoji
1078
- */
1079
- atom?: boolean;
1080
- /**
1081
- * Whether the node can be selected as a whole
1082
- * @default true for leaf nodes, false for others
1083
- */
1084
- selectable?: boolean;
1085
- /**
1086
- * Whether the node can be dragged
1087
- * @default false
1088
- */
1089
- draggable?: boolean;
1090
- /**
1091
- * Whether this node represents code
1092
- * Affects text input handling (disables smart quotes, etc.)
1093
- */
1094
- code?: boolean;
1095
- /**
1096
- * How whitespace is handled in this node
1097
- * - 'pre': preserve whitespace (like <pre>)
1098
- * - 'normal': collapse whitespace (default)
910
+ * Handles both synchronous errors and async promise rejections.
911
+ *
912
+ * @param fn - Function to execute
913
+ * @param context - Context for error reporting (e.g., 'Bold.onUpdate')
914
+ * @returns The function result, or undefined if an error occurred
1099
915
  */
1100
- whitespace?: 'pre' | 'normal';
916
+ safeCall<T>(fn: () => T, context: string): T | undefined;
1101
917
  /**
1102
- * Whether this node isolates marks at its boundaries
1103
- * Marks don't extend across isolating boundaries
918
+ * Calls onBeforeCreate on all extensions
1104
919
  */
1105
- isolating?: boolean;
920
+ callOnBeforeCreate(): void;
1106
921
  /**
1107
- * Whether a gap cursor is allowed inside this node.
1108
- * Set to false to prevent gapcursor from appearing inside this node.
1109
- * Set to true to force gapcursor even if the default heuristic disallows it.
1110
- * When undefined, uses ProseMirror's default heuristic.
922
+ * Calls onCreate on all extensions
1111
923
  */
1112
- allowGapCursor?: boolean;
924
+ callOnCreate(): void;
1113
925
  /**
1114
- * Table role for prosemirror-tables integration.
1115
- * Nodes with a tableRole are discovered by prosemirror-tables via spec.tableRole.
1116
- * One of: 'table', 'row', 'cell', 'header_cell'
926
+ * Calls onUpdate on all extensions
1117
927
  */
1118
- tableRole?: 'table' | 'row' | 'cell' | 'header_cell';
928
+ callOnUpdate(): void;
1119
929
  /**
1120
- * Whether this is a top-level node (document root)
1121
- * Only one node should have this set to true
930
+ * Calls onSelectionUpdate on all extensions
1122
931
  */
1123
- topNode?: boolean;
932
+ callOnSelectionUpdate(): void;
1124
933
  /**
1125
- * Whether this node defines its own scope
1126
- * Content and marks don't leak out of defining nodes
934
+ * Calls onTransaction on all extensions
935
+ * @param props - Transaction props
1127
936
  */
1128
- defining?: boolean;
937
+ callOnTransaction(props: {
938
+ transaction: Transaction;
939
+ }): void;
1129
940
  /**
1130
- * Which marks are allowed in this node
1131
- * Empty string means no marks allowed
1132
- *
1133
- * @example '', '_', 'bold italic'
941
+ * Calls onFocus on all extensions
942
+ * @param props - Focus event props
1134
943
  */
1135
- marks?: string;
944
+ callOnFocus(props: {
945
+ event: FocusEvent;
946
+ }): void;
1136
947
  /**
1137
- * Custom text for leaf nodes
1138
- * Used by getText() and textContent
1139
- *
1140
- * @example '\n' for hard break
948
+ * Calls onBlur on all extensions
949
+ * @param props - Blur event props
1141
950
  */
1142
- leafText?: string | ((node: Node$1) => string);
951
+ callOnBlur(props: {
952
+ event: FocusEvent;
953
+ }): void;
954
+ }
955
+
956
+ /**
957
+ * Inline Styles Utility
958
+ *
959
+ * Applies inline CSS styles to serialized HTML so it renders correctly
960
+ * when pasted outside the editor (email clients, CMS, Google Docs, etc.).
961
+ *
962
+ * Uses hardcoded light-theme defaults (same approach as Google Docs, Notion,
963
+ * TinyMCE). Optionally accepts overrides for custom styling.
964
+ *
965
+ * Only structural styles are inlined (borders, padding, margins, fonts).
966
+ * Colors are NOT inlined - explicit colors (TextColor, Highlight, cell bg)
967
+ * are already inline from renderHTML, and default text color is browser default.
968
+ */
969
+ interface InlineStyleOverrides {
970
+ blockquoteBorder?: string;
971
+ blockquoteColor?: string;
972
+ tableBorder?: string;
973
+ tableHeaderBg?: string;
974
+ codeBg?: string;
975
+ codeFont?: string;
976
+ codeBorder?: string;
977
+ codeBlockBg?: string;
978
+ codeBlockFont?: string;
979
+ hrBorder?: string;
980
+ linkColor?: string;
981
+ detailsBorder?: string;
982
+ detailsBg?: string;
1143
983
  /**
1144
- * Define node attributes
1145
- * Returns attribute specifications
1146
- *
1147
- * @example
1148
- * addAttributes() {
1149
- * return {
1150
- * level: { default: 1 },
1151
- * };
1152
- * }
984
+ * How to export table column widths from `data-colwidth` attributes.
985
+ * - `'percent'` (default): convert to percentage widths on first-row cells
986
+ * - `'pixel'`: convert to pixel widths on first-row cells, table gets fixed width
987
+ * - `'none'`: leave `data-colwidth` as-is, no width styles applied
1153
988
  */
1154
- addAttributes?: () => AttributeSpecs;
989
+ tableColumnWidths?: 'percent' | 'pixel' | 'none';
1155
990
  /**
1156
- * Parse rules for converting HTML to this node
1157
- * Each rule defines how to match and parse HTML elements
991
+ * Optional callback to syntax-highlight code blocks.
992
+ * Receives the raw text content and optional language, returns highlighted HTML
993
+ * with `<span class="hljs-*">` markup (or any spans with inline styles).
1158
994
  *
1159
995
  * @example
1160
- * parseHTML() {
1161
- * return [
1162
- * { tag: 'p' },
1163
- * { tag: 'div', priority: 10 },
1164
- * ];
1165
- * }
1166
- */
1167
- parseHTML?: () => NodeParseRule[];
1168
- /**
1169
- * Render this node to DOM
1170
- * Returns DOMOutputSpec (tag, attributes, children)
996
+ * ```ts
997
+ * import { createLowlight, common } from 'lowlight';
998
+ * import { toHtml } from 'hast-util-to-html';
999
+ * const lowlight = createLowlight(common);
1171
1000
  *
1172
- * @example
1173
- * renderHTML({ node, HTMLAttributes }) {
1174
- * return ['p', HTMLAttributes, 0];
1175
- * }
1176
- */
1177
- renderHTML?: (props: NodeRenderHTMLProps) => DOMOutputSpec;
1178
- /**
1179
- * Custom node view constructor
1180
- * For complex interactive nodes
1181
- */
1182
- addNodeView?: () => NodeViewConstructor;
1183
- /**
1184
- * Additional ProseMirror plugins for this node
1185
- * Called during plugin collection
1001
+ * inlineStyles(html, {
1002
+ * codeHighlighter: (code, language) => {
1003
+ * if (language && lowlight.registered(language)) {
1004
+ * return toHtml(lowlight.highlight(language, code));
1005
+ * }
1006
+ * return null; // no highlighting
1007
+ * },
1008
+ * });
1009
+ * ```
1186
1010
  */
1187
- addProseMirrorPlugins?: () => Plugin[];
1011
+ codeHighlighter?: (code: string, language: string | null) => string | null;
1188
1012
  }
1189
1013
  /**
1190
- * Configuration for Node extensions
1191
- * Combines ExtensionConfig base with node-specific schema properties.
1192
- * Uses ThisType<NodeContext> to provide proper typing for `this` in config methods.
1014
+ * Applies inline styles to all elements in a container.
1015
+ * Exported for use in clipboardSerializer (operates on DOM directly).
1016
+ */
1017
+ declare function applyInlineStyles(container: HTMLElement, overrides?: InlineStyleOverrides): void;
1018
+ /**
1019
+ * Takes an HTML string and returns it with inline CSS styles applied
1020
+ * to all elements, so it renders correctly outside the editor.
1193
1021
  *
1194
- * @typeParam Options - Node options type
1195
- * @typeParam Storage - Node storage type
1022
+ * @param html - Serialized HTML string from editor.getHTML()
1023
+ * @param overrides - Optional style overrides for custom theming
1196
1024
  *
1197
1025
  * @example
1198
- * const Paragraph = Node.create({
1199
- * name: 'paragraph',
1200
- * group: 'block',
1201
- * content: 'inline*',
1202
- * parseHTML() {
1203
- * // `this` is properly typed here!
1204
- * return [{ tag: 'p' }];
1205
- * },
1206
- * renderHTML({ HTMLAttributes }) {
1207
- * return ['p', HTMLAttributes, 0];
1208
- * },
1026
+ * ```ts
1027
+ * // Default light-theme styles
1028
+ * const styled = inlineStyles(editor.getHTML());
1029
+ *
1030
+ * // With custom overrides
1031
+ * const styled = inlineStyles(editor.getHTML(), {
1032
+ * blockquoteBorder: '5px solid red',
1033
+ * linkColor: '#ff6600',
1209
1034
  * });
1035
+ * ```
1210
1036
  */
1211
- type NodeConfig<Options = unknown, Storage = unknown> = ExtensionConfigBase<Options, Storage> & NodeSchemaProperties & ThisType<NodeContext<Options, Storage>>;
1037
+ declare function inlineStyles(html: string, overrides?: InlineStyleOverrides): string;
1212
1038
 
1213
1039
  /**
1214
- * Mark configuration types
1040
+ * Editor - Main editor class wrapping ProseMirror
1215
1041
  *
1216
- * These types define the configuration object passed to Mark.create()
1217
- * for creating ProseMirror mark extensions.
1042
+ * Manages extensions, schema, commands, and the ProseMirror EditorView/State.
1218
1043
  */
1219
1044
 
1220
1045
  /**
1221
- * Editor interface for Mark context
1222
- * Includes schema with marks for MarkType getter
1223
- */
1224
- interface MarkEditorContext {
1225
- readonly state: EditorState;
1226
- readonly view: EditorView;
1227
- readonly schema: {
1228
- marks: Record<string, MarkType>;
1229
- };
1230
- readonly commands: Record<string, (...args: unknown[]) => boolean>;
1231
- }
1232
- /**
1233
- * Context interface for Mark config methods.
1234
- * Extends ExtensionContext with mark-specific properties.
1235
- * This enables proper typing of `this.options`, `this.markType`, etc.
1046
+ * Main editor class
1236
1047
  *
1237
- * @typeParam Options - Mark options type
1238
- * @typeParam Storage - Mark storage type
1239
- */
1240
- interface MarkContext<Options = unknown, Storage = unknown> extends Omit<ExtensionContext<Options, Storage>, 'editor' | 'type'> {
1241
- /** Mark type identifier */
1242
- readonly type: 'mark';
1243
- /** Editor instance with schema access */
1244
- editor: MarkEditorContext | null;
1245
- /** ProseMirror MarkType (null until editor is initialized) */
1246
- readonly markType: MarkType | null;
1247
- }
1248
- /**
1249
- * Parse rule for converting HTML to ProseMirror mark
1250
- * Simplified version of ProseMirror's ParseRule for marks
1048
+ * Wraps ProseMirror's EditorView and EditorState with a cleaner API.
1049
+ *
1050
+ * @example
1051
+ * ```ts
1052
+ * import { Editor } from '@domternal/core';
1053
+ * import { Schema } from '@domternal/pm/model';
1054
+ *
1055
+ * const schema = new Schema({
1056
+ * nodes: { doc: { content: 'paragraph+' }, paragraph: { content: 'text*' }, text: {} }
1057
+ * });
1058
+ *
1059
+ * const editor = new Editor({
1060
+ * schema,
1061
+ * element: document.getElementById('editor'),
1062
+ * content: '<p>Hello world</p>',
1063
+ * });
1064
+ *
1065
+ * // Get content
1066
+ * const json = editor.getJSON();
1067
+ * const html = editor.getHTML();
1068
+ *
1069
+ * // Set content
1070
+ * editor.commands.setContent('<p>New content</p>');
1071
+ *
1072
+ * // Cleanup
1073
+ * editor.destroy();
1074
+ * ```
1251
1075
  */
1252
- interface MarkParseRule {
1076
+ declare class Editor extends EventEmitter<EditorEvents> {
1253
1077
  /**
1254
- * CSS selector or tag name to match
1255
- * @example 'strong', 'b', 'span.bold'
1078
+ * Editor configuration options
1256
1079
  */
1257
- tag?: string;
1080
+ private options;
1258
1081
  /**
1259
- * Match by CSS style property
1260
- * Can include expected value after '='
1261
- * @example 'font-weight', 'font-weight=bold'
1082
+ * Manages extensions and schema
1083
+ * @internal Exposed for CommandManager, not for public use
1262
1084
  */
1263
- style?: string;
1085
+ private _extensionManager;
1264
1086
  /**
1265
- * Priority for this rule (higher = checked first)
1266
- * @default 50
1087
+ * Gets the extension manager
1088
+ * @internal For CommandManager use only
1267
1089
  */
1268
- priority?: number;
1090
+ get extensionManager(): ExtensionManager;
1269
1091
  /**
1270
- * Whether to consume the matched element
1271
- * @default true
1092
+ * Manages commands
1272
1093
  */
1273
- consuming?: boolean;
1094
+ private commandManager;
1274
1095
  /**
1275
- * Get attributes from the matched element
1276
- * Return null/undefined to skip this rule
1277
- * Return false to explicitly not match
1278
- *
1279
- * For style rules, receives the style value as string
1096
+ * ProseMirror EditorView instance
1280
1097
  */
1281
- getAttrs?: ((node: HTMLElement | string) => Record<string, unknown> | false | null) | null;
1282
- }
1283
- /**
1284
- * Props passed to renderHTML function for marks
1285
- */
1286
- interface MarkRenderHTMLProps {
1098
+ view: EditorView;
1287
1099
  /**
1288
- * The ProseMirror mark being rendered
1100
+ * Whether the editor has been destroyed
1289
1101
  */
1290
- mark: Mark$1;
1102
+ private _isDestroyed;
1291
1103
  /**
1292
- * Merged HTML attributes from all sources
1104
+ * Timer for autofocus (cleared on destroy to prevent memory leaks)
1293
1105
  */
1294
- HTMLAttributes: Record<string, unknown>;
1295
- }
1296
- /**
1297
- * Mark-specific configuration properties (schema-related)
1298
- */
1299
- interface MarkSchemaProperties {
1106
+ private _autofocusTimer;
1300
1107
  /**
1301
- * Whether this mark should be active when the cursor
1302
- * is at its end (or beginning for marks that open).
1303
- *
1304
- * When true, typing at the mark's boundary continues the mark.
1305
- * When false, typing creates unmarked content.
1108
+ * Creates a new Editor instance
1306
1109
  *
1307
- * @default true
1110
+ * @param options - Editor configuration
1111
+ * @throws Error if running in SSR environment (no window)
1112
+ * @throws Error if schema is not provided
1308
1113
  */
1309
- inclusive?: boolean | (() => boolean);
1114
+ constructor(options: EditorOptions);
1310
1115
  /**
1311
- * Marks that this mark excludes (cannot coexist with)
1312
- *
1313
- * - '_' excludes all marks
1314
- * - Space-separated mark names exclude specific marks
1315
- * - Empty string or undefined means no exclusions
1316
- *
1317
- * @example 'code' - excludes code mark
1318
- * @example 'bold italic' - excludes bold and italic
1319
- * @example '_' - excludes all other marks
1116
+ * Gets the current EditorState
1320
1117
  */
1321
- excludes?: string;
1118
+ get state(): EditorState;
1322
1119
  /**
1323
- * Mark group(s) this mark belongs to
1324
- * Used in node's marks property
1325
- *
1326
- * @example 'formatting', 'inline'
1120
+ * Gets the ProseMirror schema
1327
1121
  */
1328
- group?: string;
1122
+ get schema(): Schema;
1329
1123
  /**
1330
- * Whether this mark can span multiple nodes
1331
- *
1332
- * When true (default), the mark persists across inline nodes.
1333
- * When false, the mark only applies within a single text node.
1334
- *
1335
- * @default true
1124
+ * Checks if the editor is editable
1336
1125
  */
1337
- spanning?: boolean;
1126
+ get isEditable(): boolean;
1338
1127
  /**
1339
- * Whether this mark represents visual formatting.
1128
+ * Checks if the editor content is empty
1129
+ */
1130
+ get isEmpty(): boolean;
1131
+ /**
1132
+ * Checks if the editor has focus
1133
+ */
1134
+ get isFocused(): boolean;
1135
+ /**
1136
+ * Checks if the editor has been destroyed
1137
+ */
1138
+ get isDestroyed(): boolean;
1139
+ /**
1140
+ * Gets single commands for immediate execution
1141
+ * @example editor.commands.focus('end')
1142
+ */
1143
+ get commands(): SingleCommands;
1144
+ /**
1145
+ * Creates a command chain for batched execution
1146
+ * @example editor.chain().focus().insertText('Hello').run()
1147
+ */
1148
+ chain(): ChainedCommands;
1149
+ /**
1150
+ * Checks if commands can be executed (dry-run)
1151
+ * @example if (editor.can().toggleBold()) { ... }
1152
+ */
1153
+ can(): CanCommands;
1154
+ /**
1155
+ * Gets extension storage
1156
+ * Access via: editor.storage.extensionName.propertyName
1157
+ */
1158
+ get storage(): Record<string, unknown>;
1159
+ /**
1160
+ * Toolbar items registered by all extensions.
1161
+ */
1162
+ get toolbarItems(): ToolbarItem[];
1163
+ /**
1164
+ * Floating-menu items registered by all extensions, rendered as the
1165
+ * block-insert menu shown on empty paragraphs.
1166
+ */
1167
+ get floatingMenuItems(): FloatingMenuItem[];
1168
+ /**
1169
+ * Checks if a node or mark is currently active
1340
1170
  *
1341
- * Used by `unsetAllMarks` to decide which marks to remove.
1342
- * Marks with `isFormatting: false` survive clear formatting
1343
- * (e.g., links, comments, annotations).
1171
+ * For toolbar button states - returns true if:
1172
+ * - For marks: the current selection has the mark applied
1173
+ * - For nodes: the cursor is inside that node type
1344
1174
  *
1345
- * Can be overridden via `.configure()`:
1346
- * ```ts
1347
- * Link.configure({ isFormatting: true }) // make links clearable
1348
- * ```
1175
+ * @param nameOrAttributes - Extension name, or object with name and attributes
1176
+ * @param attributes - Optional attributes to match (for node/mark specific states)
1349
1177
  *
1350
- * @default true
1178
+ * @example
1179
+ * editor.isActive('bold') // → true if bold mark is active
1180
+ * editor.isActive('heading', { level: 2 }) // → true if in h2
1181
+ * editor.isActive({ name: 'textAlign', attributes: { align: 'center' } })
1351
1182
  */
1352
- isFormatting?: boolean;
1183
+ isActive(nameOrAttributes: string | {
1184
+ name: string;
1185
+ attributes?: Record<string, unknown>;
1186
+ }, attributes?: Record<string, unknown>): boolean;
1353
1187
  /**
1354
- * Define mark attributes
1355
- * Returns attribute specifications
1188
+ * Gets attributes of the currently active node or mark
1189
+ *
1190
+ * Returns empty object if the node/mark is not found or not active.
1191
+ *
1192
+ * @param name - Extension name (node or mark)
1356
1193
  *
1357
1194
  * @example
1358
- * addAttributes() {
1359
- * return {
1360
- * color: { default: null },
1361
- * };
1362
- * }
1195
+ * editor.getAttributes('heading') // → { level: 2 }
1196
+ * editor.getAttributes('link') // → { href: 'https://...', target: '_blank' }
1363
1197
  */
1364
- addAttributes?: () => AttributeSpecs;
1198
+ getAttributes(name: string): Record<string, unknown>;
1365
1199
  /**
1366
- * Parse rules for converting HTML to this mark
1367
- * Each rule defines how to match and parse HTML elements
1200
+ * Helper to match attributes
1201
+ * Returns true if target contains all key/value pairs from source
1202
+ */
1203
+ private matchAttributes;
1204
+ /**
1205
+ * Gets the document content as JSON
1206
+ */
1207
+ getJSON(): JSONContent;
1208
+ /**
1209
+ * Gets the document content as HTML string
1368
1210
  *
1369
- * @example
1370
- * parseHTML() {
1371
- * return [
1372
- * { tag: 'strong' },
1373
- * { tag: 'b' },
1374
- * { style: 'font-weight', getAttrs: (value) => /bold|[5-9]\d{2}/.test(value) && null },
1375
- * ];
1376
- * }
1211
+ * @param options - Optional settings
1212
+ * @param options.styled - When true (or an override object), applies inline CSS
1213
+ * styles so the HTML renders correctly outside the editor (email, CMS, etc.)
1377
1214
  */
1378
- parseHTML?: () => MarkParseRule[];
1215
+ getHTML(options?: {
1216
+ styled?: boolean | InlineStyleOverrides;
1217
+ }): string;
1379
1218
  /**
1380
- * Render this mark to DOM
1381
- * Returns DOMOutputSpec (tag, attributes, hole)
1219
+ * Gets the document content as plain text
1382
1220
  *
1383
- * The 0 in the return array indicates where child content goes.
1221
+ * @param options - Options for text extraction
1222
+ * @param options.blockSeparator - String to insert between blocks (default: '\n\n')
1223
+ */
1224
+ getText(options?: {
1225
+ blockSeparator?: string;
1226
+ }): string;
1227
+ /**
1228
+ * Executes a command with proper CommandProps
1229
+ * @internal
1230
+ */
1231
+ private runCommand;
1232
+ /**
1233
+ * Sets the editor content
1384
1234
  *
1385
- * @example
1386
- * renderHTML({ mark, HTMLAttributes }) {
1387
- * return ['strong', HTMLAttributes, 0];
1388
- * }
1235
+ * @param content - JSON or HTML content
1236
+ * @param emitUpdate - Whether to emit update event (default: true)
1237
+ * @returns true if content was set successfully, false if content was invalid
1389
1238
  */
1390
- renderHTML?: (props: MarkRenderHTMLProps) => DOMOutputSpec;
1239
+ setContent(content: Content, emitUpdate?: boolean): boolean;
1240
+ /**
1241
+ * Clears the editor content
1242
+ *
1243
+ * @param emitUpdate - Whether to emit update event (default: true)
1244
+ * @returns true if content was cleared successfully
1245
+ */
1246
+ clearContent(emitUpdate?: boolean): boolean;
1247
+ /**
1248
+ * Sets whether the editor is editable
1249
+ *
1250
+ * @param editable - Whether the editor should be editable
1251
+ */
1252
+ setEditable(editable: boolean): this;
1253
+ /**
1254
+ * Focuses the editor
1255
+ *
1256
+ * @param position - Where to place cursor (default: null = just focus)
1257
+ */
1258
+ focus(position?: FocusPosition): this;
1259
+ /**
1260
+ * Removes focus from the editor
1261
+ */
1262
+ blur(): this;
1263
+ /**
1264
+ * Registers a ProseMirror plugin dynamically at runtime, after the editor
1265
+ * has been created. Safe to call repeatedly with the same plugin key.
1266
+ */
1267
+ registerPlugin(plugin: Plugin): void;
1268
+ /**
1269
+ * Unregisters a ProseMirror plugin by its PluginKey.
1270
+ * Uses PluginKey.get() to identify the plugin to remove.
1271
+ */
1272
+ unregisterPlugin(key: PluginKey): void;
1273
+ /**
1274
+ * Destroys the editor and cleans up resources
1275
+ *
1276
+ * After calling destroy(), the editor instance should not be used.
1277
+ */
1278
+ destroy(): void;
1279
+ /**
1280
+ * Builds a clipboardSerializer that applies a transform function to HTML on copy/cut.
1281
+ */
1282
+ private buildClipboardSerializer;
1283
+ /**
1284
+ * Creates the editor instance
1285
+ */
1286
+ private createEditor;
1287
+ /**
1288
+ * Handles ProseMirror transactions
1289
+ */
1290
+ private dispatchTransaction;
1291
+ /**
1292
+ * Emit method - needed for CommandManager interface
1293
+ */
1294
+ emit<E extends keyof EditorEvents>(event: E, ...args: EditorEvents[E] extends undefined ? [] : [EditorEvents[E]]): this;
1391
1295
  }
1296
+
1392
1297
  /**
1393
- * Configuration for Mark extensions
1394
- * Combines ExtensionConfigBase with mark-specific schema properties.
1395
- * Uses ThisType<MarkContext> to provide proper typing for `this` in config methods.
1298
+ * FloatingMenu configuration types
1396
1299
  *
1397
- * @typeParam Options - Mark options type
1398
- * @typeParam Storage - Mark storage type
1399
- *
1400
- * @example
1401
- * const Bold = Mark.create({
1402
- * name: 'bold',
1403
- * parseHTML() {
1404
- * // `this` is properly typed here!
1405
- * return [{ tag: 'strong' }, { tag: 'b' }];
1406
- * },
1407
- * renderHTML({ HTMLAttributes }) {
1408
- * return ['strong', HTMLAttributes, 0];
1409
- * },
1410
- * });
1300
+ * Types for block-insert items contributed by extensions via the
1301
+ * `addFloatingMenuItems()` hook. Framework wrappers read these items
1302
+ * and render a WAI-ARIA menu shown on empty paragraphs.
1411
1303
  */
1412
- type MarkConfig<Options = unknown, Storage = unknown> = ExtensionConfigBase<Options, Storage> & MarkSchemaProperties & ThisType<MarkContext<Options, Storage>>;
1413
1304
 
1414
1305
  /**
1415
- * Callback type for event handlers
1416
- * - Events with payload: (data: T) => void
1417
- * - Events without payload (undefined): () => void
1418
- */
1419
- type EventCallback<T> = T extends undefined ? () => void : (data: T) => void;
1420
- /**
1421
- * Generic, type-safe event emitter
1306
+ * A single entry in the floating menu.
1307
+ *
1308
+ * Unlike `ToolbarButton`, floating-menu items represent one-shot insert
1309
+ * actions (no toggle / active state), carry an optional `description` for
1310
+ * Notion-style two-line rendering, and expose `keywords` for future
1311
+ * slash-command filtering.
1422
1312
  *
1423
1313
  * @example
1424
- * ```typescript
1425
- * interface MyEvents {
1426
- * update: { value: number };
1427
- * destroy: undefined;
1314
+ * {
1315
+ * name: 'heading-1',
1316
+ * label: 'Heading 1',
1317
+ * description: 'Big section heading',
1318
+ * icon: 'textHOne',
1319
+ * group: 'Basic',
1320
+ * shortcut: '# ',
1321
+ * command: 'toggleHeading',
1322
+ * commandArgs: [{ level: 1 }],
1428
1323
  * }
1429
- *
1430
- * const emitter = new EventEmitter<MyEvents>();
1431
- * emitter.on('update', ({ value }) => console.log(value));
1432
- * emitter.on('destroy', () => console.log('destroyed'));
1433
- * ```
1434
1324
  */
1435
- declare class EventEmitter<Events extends {
1436
- [K in keyof Events]: unknown;
1437
- } = Record<string, never>> {
1438
- protected callbacks: Map<keyof Events, Set<(data: unknown) => void>>;
1439
- /**
1440
- * Register an event listener
1441
- */
1442
- on<E extends keyof Events>(event: E, callback: EventCallback<Events[E]>): this;
1443
- /**
1444
- * Remove an event listener, or all listeners for an event if no callback specified
1445
- */
1446
- off<E extends keyof Events>(event: E, callback?: EventCallback<Events[E]>): this;
1447
- /**
1448
- * Emit an event to all registered listeners
1449
- * Uses .call(this) to preserve context for callbacks
1450
- */
1451
- emit<E extends keyof Events>(event: E, ...args: Events[E] extends undefined ? [] : [Events[E]]): this;
1325
+ interface FloatingMenuItem {
1326
+ /** Unique identifier (used as React key, aria id, and dedup key when merging). */
1327
+ name: string;
1328
+ /** Primary label shown to the user. */
1329
+ label: string;
1330
+ /** Optional secondary line shown under the label. */
1331
+ description?: string;
1332
+ /** Icon key resolved against the editor's IconSet. Optional. */
1333
+ icon?: string;
1452
1334
  /**
1453
- * Register an event listener that fires only once
1335
+ * Group heading. Items with the same group render under a shared
1336
+ * `<div role="group" aria-label={group}>` section.
1337
+ * @default '' (no group heading)
1454
1338
  */
1455
- once<E extends keyof Events>(event: E, callback: EventCallback<Events[E]>): this;
1339
+ group?: string;
1340
+ /** Sort order within group (higher first). @default 100 */
1341
+ priority?: number;
1342
+ /** Keywords for future slash-command fuzzy filtering. */
1343
+ keywords?: string[];
1344
+ /** Visible keyboard-shortcut hint (e.g. `# `, `Mod-Alt-1`). */
1345
+ shortcut?: string;
1456
1346
  /**
1457
- * Remove all listeners for a specific event, or all events if no event specified
1347
+ * Command to execute on activation.
1348
+ *
1349
+ * - String: key of `editor.commands`; invoked with `commandArgs`.
1350
+ * - Function: arbitrary callback receiving the editor. Use when the
1351
+ * action is not a registered command (e.g. opening a popover).
1458
1352
  */
1459
- removeAllListeners(event?: keyof Events): this;
1353
+ command: string | ((editor: Editor) => void);
1354
+ /** Arguments for string commands. Ignored when `command` is a function. */
1355
+ commandArgs?: unknown[];
1460
1356
  /**
1461
- * Get the number of listeners for a specific event
1357
+ * Custom disabled predicate. When omitted, the controller dry-runs the
1358
+ * command via `editor.can()` (string commands only) to detect availability.
1462
1359
  */
1463
- listenerCount(event: keyof Events): number;
1360
+ isDisabled?: (editor: Editor) => boolean;
1464
1361
  /**
1465
- * Get all event names that have listeners
1362
+ * Node-type names that, when present as ancestors of the cursor, cause
1363
+ * the slash menu to hide this item. Useful for list/task-list entries:
1364
+ * e.g. `['bulletList']` removes "Bulleted list" from the menu while the
1365
+ * cursor is already inside a bullet list, so picking it doesn't lift
1366
+ * the user out of the list unexpectedly.
1466
1367
  */
1467
- eventNames(): (keyof Events)[];
1368
+ hideWhenInside?: string[];
1468
1369
  }
1469
-
1470
1370
  /**
1471
- * ExtensionManager - Manages extensions and schema
1371
+ * Override form accepted by `FloatingMenu.configure({ items })`.
1472
1372
  *
1473
- * Handles:
1474
- * - Extension lifecycle (flatten, resolve, bind)
1475
- * - Schema building from Node/Mark extensions
1476
- * - Plugin collection from all extensions
1477
- * - Extension storage management
1478
- * - Conflict detection (AD-7)
1373
+ * - Array: replaces the collected defaults entirely.
1374
+ * - Function: transforms the defaults (filter / reorder / extend).
1479
1375
  */
1376
+ type FloatingMenuItemsOverride = FloatingMenuItem[] | ((defaults: FloatingMenuItem[], editor: Editor) => FloatingMenuItem[]);
1480
1377
 
1481
1378
  /**
1482
- * Error event props for safeCall
1379
+ * Extension configuration types
1380
+ *
1381
+ * These types define the configuration object passed to Extension.create(),
1382
+ * Node.create(), and Mark.create() factory methods.
1483
1383
  */
1484
- interface ErrorEventProps {
1485
- error: Error;
1486
- context: string;
1487
- }
1384
+
1488
1385
  /**
1489
- * Editor interface for ExtensionManager
1490
- * Forward declaration to avoid circular dependency
1386
+ * Editor instance type (forward declaration)
1491
1387
  */
1492
- interface ExtensionManagerEditor {
1493
- readonly schema: Schema;
1494
- emit?(event: 'error', props: ErrorEventProps): void;
1388
+ interface ExtensionEditor {
1389
+ readonly state: EditorState;
1390
+ readonly view: EditorView;
1391
+ readonly schema: unknown;
1392
+ readonly commands: SingleCommands;
1495
1393
  }
1496
1394
  /**
1497
- * Context attached to node view constructors for framework wrappers.
1498
- * Accessible via `(constructor as any).__domternalContext`.
1395
+ * Any extension type (forward declaration)
1499
1396
  */
1500
- interface NodeViewContext {
1501
- editor: ExtensionManagerEditor;
1502
- extension: {
1503
- name: string;
1504
- options: Record<string, unknown>;
1505
- };
1397
+ interface AnyExtensionConfig {
1398
+ name: string;
1399
+ type?: 'extension' | 'node' | 'mark';
1506
1400
  }
1507
1401
  /**
1508
- * Options for ExtensionManager constructor
1402
+ * Global attribute specification for injecting attributes into multiple node/mark types.
1403
+ * Used by extensions like TextAlign to add alignment to heading, paragraph, etc.
1509
1404
  */
1510
- interface ExtensionManagerOptions {
1511
- /**
1512
- * Extensions to process
1513
- * If provided, schema is built from extensions
1514
- */
1515
- extensions?: AnyExtension[] | undefined;
1516
- /**
1517
- * Direct schema (backward compatibility with Step 1.3)
1518
- * If provided, extensions are ignored for schema building
1519
- */
1520
- schema?: Schema | undefined;
1521
- }
1522
- declare class ExtensionManager {
1405
+ interface GlobalAttributeSpec {
1523
1406
  /**
1524
- * Processed extensions (flattened, sorted by priority)
1407
+ * Default value for the attribute.
1525
1408
  */
1526
- private readonly _extensions;
1409
+ default?: unknown;
1527
1410
  /**
1528
- * ProseMirror schema (built from extensions or passed directly)
1411
+ * Parse attribute value from HTML element.
1412
+ * @param element - The DOM element to parse from
1413
+ * @returns The parsed attribute value
1529
1414
  */
1530
- private readonly _schema;
1415
+ parseHTML?: (element: HTMLElement) => unknown;
1531
1416
  /**
1532
- * Reference to the editor instance
1417
+ * Render attribute value to HTML attributes.
1418
+ * @param attributes - The node/mark attributes
1419
+ * @returns Object of HTML attributes to set, or null/empty to skip
1533
1420
  */
1534
- readonly editor: ExtensionManagerEditor;
1421
+ renderHTML?: (attributes: Record<string, unknown>) => Record<string, string> | null;
1422
+ }
1423
+ /**
1424
+ * Global attributes definition for injecting into node/mark types.
1425
+ */
1426
+ interface GlobalAttributes {
1535
1427
  /**
1536
- * Extension storage (keyed by extension name)
1428
+ * Node or mark type names to add these attributes to.
1429
+ * @example ['heading', 'paragraph']
1537
1430
  */
1538
- private readonly _storage;
1431
+ types: string[];
1539
1432
  /**
1540
- * Whether the manager has been destroyed
1433
+ * Attribute specifications to add.
1434
+ * @example { textAlign: { default: 'left', parseHTML: (el) => el.style.textAlign } }
1541
1435
  */
1542
- private isDestroyed;
1436
+ attributes: Record<string, GlobalAttributeSpec>;
1437
+ }
1438
+ /**
1439
+ * Context interface that describes what `this` will be in config methods.
1440
+ * This enables proper typing of `this.options`, `this.editor`, etc.
1441
+ * without creating circular dependencies.
1442
+ *
1443
+ * @typeParam Options - Extension options type
1444
+ * @typeParam Storage - Extension storage type
1445
+ */
1446
+ interface ExtensionContext<Options = unknown, Storage = unknown> {
1447
+ /** Extension type identifier */
1448
+ readonly type: 'extension' | 'node' | 'mark';
1449
+ /** Unique extension name */
1450
+ readonly name: string;
1451
+ /** Extension options (immutable after creation) */
1452
+ readonly options: Options;
1453
+ /** Extension storage (mutable state) */
1454
+ storage: Storage;
1455
+ /** Editor instance (null until bound by ExtensionManager) */
1456
+ editor: ExtensionEditor | null;
1543
1457
  /**
1544
- * Cached plugins (built lazily)
1458
+ * Reference to the parent config method when using extend().
1459
+ * Available only inside overridden config methods.
1460
+ * Use `this.parent?.()` to call the parent's version of the current method.
1545
1461
  */
1546
- private _plugins;
1547
- /**
1548
- * Cached commands (collected lazily)
1549
- */
1550
- private _commands;
1462
+ parent?: ((...args: unknown[]) => unknown) | undefined;
1463
+ }
1464
+ /**
1465
+ * Base configuration properties for all extension types.
1466
+ * This interface contains just the properties without ThisType,
1467
+ * allowing Node and Mark configs to use their own context types.
1468
+ *
1469
+ * @typeParam Options - Extension options type
1470
+ * @typeParam Storage - Extension storage type
1471
+ */
1472
+ interface ExtensionConfigBase<Options = unknown, Storage = unknown> {
1551
1473
  /**
1552
- * Cached toolbar items (collected lazily)
1474
+ * Unique extension name
1475
+ * Used for identification, storage access, and error messages
1553
1476
  */
1554
- private _toolbarItems;
1477
+ name: string;
1555
1478
  /**
1556
- * Cached node views (collected lazily)
1479
+ * Extension priority (higher = loaded first)
1480
+ *
1481
+ * Reserved ranges:
1482
+ * - 900-1000: Core nodes (Document, Text, Paragraph)
1483
+ * - 500-899: Standard extensions (Heading, Bold, etc.)
1484
+ * - 100-499: Default range for user extensions
1485
+ * - 0-99: Low priority extensions (run after everything)
1486
+ *
1487
+ * @default 100
1557
1488
  */
1558
- private _nodeViews;
1489
+ priority?: number;
1559
1490
  /**
1560
- * Creates a new ExtensionManager
1491
+ * Required extensions that must be present
1492
+ * ExtensionManager throws if any dependency is missing
1561
1493
  *
1562
- * @param options - Extensions or direct schema
1563
- * @param editor - Editor instance
1494
+ * @example
1495
+ * dependencies: ['bulletList', 'orderedList']
1564
1496
  */
1565
- constructor(options: ExtensionManagerOptions, editor: ExtensionManagerEditor);
1497
+ dependencies?: string[];
1566
1498
  /**
1567
- * Gets the processed extensions array
1499
+ * Default options for this extension
1500
+ * Called during extension creation with `this` bound to the extension
1568
1501
  */
1569
- get extensions(): readonly AnyExtension[];
1502
+ addOptions?: () => Options;
1570
1503
  /**
1571
- * Gets the ProseMirror schema
1504
+ * Initial storage state for this extension
1505
+ * Storage is mutable and accessible via editor.storage[extensionName]
1572
1506
  */
1573
- get schema(): Schema;
1507
+ addStorage?: () => Storage;
1574
1508
  /**
1575
- * Gets extension storage (accessed via editor.storage)
1509
+ * Commands this extension provides
1510
+ * Commands are accessible via editor.commands.commandName()
1511
+ *
1512
+ * @example
1513
+ * addCommands() {
1514
+ * return {
1515
+ * toggleBold: () => ({ commands }) => commands.toggleMark('bold'),
1516
+ * };
1517
+ * }
1576
1518
  */
1577
- get storage(): Record<string, unknown>;
1519
+ addCommands?: () => Record<string, (...args: never[]) => Command>;
1578
1520
  /**
1579
- * Gets plugins from all extensions
1580
- * Cached after first call
1521
+ * Keyboard shortcuts for this extension
1522
+ * Keys are shortcut strings (e.g., 'Mod-b'), values are handler functions
1523
+ *
1524
+ * @example
1525
+ * addKeyboardShortcuts() {
1526
+ * return {
1527
+ * 'Mod-b': () => this.editor.commands.toggleBold(),
1528
+ * };
1529
+ * }
1581
1530
  */
1582
- get plugins(): Plugin[];
1531
+ addKeyboardShortcuts?: () => Record<string, KeyboardShortcutCommand>;
1583
1532
  /**
1584
- * Gets commands from all extensions
1533
+ * Input rules (markdown-style shortcuts)
1534
+ * Triggered when user types matching patterns
1535
+ *
1536
+ * @example
1537
+ * addInputRules() {
1538
+ * return [
1539
+ * textblockTypeInputRule(/^## $/, this.type),
1540
+ * ];
1541
+ * }
1585
1542
  */
1586
- get commands(): CommandMap;
1543
+ addInputRules?: () => InputRule[];
1587
1544
  /**
1588
- * Gets toolbar items from all extensions
1589
- * Cached after first call
1545
+ * ProseMirror plugins for this extension
1590
1546
  */
1591
- get toolbarItems(): ToolbarItem[];
1547
+ addProseMirrorPlugins?: () => Plugin[];
1592
1548
  /**
1593
- * Gets node views from all Node extensions that define addNodeView
1549
+ * Nested extensions (for extension bundles like StarterKit)
1550
+ * These extensions are flattened and processed like top-level extensions
1594
1551
  */
1595
- get nodeViews(): Record<string, NodeViewConstructor>;
1552
+ addExtensions?: () => AnyExtensionConfig[];
1596
1553
  /**
1597
- * Clears all caches (plugins, commands)
1598
- * Call when extensions change dynamically
1554
+ * Global attributes to add to multiple node/mark types.
1555
+ * Useful for extensions like TextAlign that need to add attributes
1556
+ * to several nodes (heading, paragraph, etc.) without modifying each.
1557
+ *
1558
+ * @example
1559
+ * addGlobalAttributes() {
1560
+ * return [{
1561
+ * types: ['heading', 'paragraph'],
1562
+ * attributes: {
1563
+ * textAlign: {
1564
+ * default: 'left',
1565
+ * parseHTML: (element) => element.style.textAlign || 'left',
1566
+ * renderHTML: (attrs) => attrs.textAlign !== 'left'
1567
+ * ? { style: `text-align: ${attrs.textAlign}` }
1568
+ * : null,
1569
+ * },
1570
+ * },
1571
+ * }];
1572
+ * }
1599
1573
  */
1600
- clearAllCaches(): void;
1574
+ addGlobalAttributes?: () => GlobalAttributes[];
1601
1575
  /**
1602
- * Recursively flattens extensions by expanding addExtensions()
1603
- * This allows extension bundles like StarterKit to work
1576
+ * Toolbar items this extension contributes.
1577
+ * Framework toolbar components read these to auto-generate buttons.
1578
+ *
1579
+ * @example
1580
+ * addToolbarItems() {
1581
+ * return [{
1582
+ * type: 'button',
1583
+ * name: 'bold',
1584
+ * command: 'toggleBold',
1585
+ * isActive: 'bold',
1586
+ * icon: 'textB',
1587
+ * label: 'Bold',
1588
+ * shortcut: 'Mod-b',
1589
+ * group: 'format',
1590
+ * }];
1591
+ * }
1604
1592
  */
1605
- private flattenExtensions;
1593
+ addToolbarItems?: () => ToolbarItem[];
1606
1594
  /**
1607
- * Removes duplicate extensions by name, keeping the last occurrence.
1608
- * This allows parent extensions to auto-include children via addExtensions()
1609
- * while letting users override with explicitly configured versions.
1595
+ * Floating-menu items this extension contributes.
1596
+ * Block-insert menu shown on empty paragraphs aggregates these items
1597
+ * across all extensions. Framework wrappers render them as a WAI-ARIA
1598
+ * menu with groups, arrow-key navigation, and Enter to execute.
1599
+ *
1600
+ * @example
1601
+ * addFloatingMenuItems() {
1602
+ * return [{
1603
+ * name: 'heading-1',
1604
+ * label: 'Heading 1',
1605
+ * description: 'Big section heading',
1606
+ * icon: 'textHOne',
1607
+ * group: 'Basic',
1608
+ * command: 'toggleHeading',
1609
+ * commandArgs: [{ level: 1 }],
1610
+ * }];
1611
+ * }
1610
1612
  */
1611
- private deduplicateExtensions;
1613
+ addFloatingMenuItems?: () => FloatingMenuItem[];
1612
1614
  /**
1613
- * Sorts extensions by priority (higher priority first)
1614
- * Default priority is 100
1615
+ * Called before editor is created
1616
+ * Can be used to modify editor options
1615
1617
  */
1616
- private resolveExtensions;
1618
+ onBeforeCreate?: () => void;
1617
1619
  /**
1618
- * Detects duplicate extension names (AD-7: Schema Conflict Detection)
1619
- * @throws Error if duplicate names found
1620
+ * Called when editor is fully initialized and ready
1620
1621
  */
1621
- private detectConflicts;
1622
+ onCreate?: () => void;
1622
1623
  /**
1623
- * Validates that all extension dependencies are present
1624
- * @throws Error if required dependency is missing
1624
+ * Called when document content changes
1625
1625
  */
1626
- private checkDependencies;
1626
+ onUpdate?: () => void;
1627
1627
  /**
1628
- * Sets editor reference on all extensions
1628
+ * Called when selection changes (without content change)
1629
1629
  */
1630
- private bindEditorToExtensions;
1630
+ onSelectionUpdate?: () => void;
1631
1631
  /**
1632
- * Collects global attributes from all extensions.
1633
- * Returns a map of type name -> attribute specs to merge.
1632
+ * Called on every transaction
1633
+ * Can be used to intercept or modify transactions
1634
1634
  */
1635
- private collectGlobalAttributes;
1635
+ onTransaction?: (props: {
1636
+ transaction: Transaction;
1637
+ }) => void;
1636
1638
  /**
1637
- * Applies global attributes to a node or mark spec.
1638
- * Merges extra attrs into spec.attrs, wraps parseDOM getAttrs to parse
1639
- * global attributes from DOM elements, and wraps toDOM to inject rendered
1640
- * global HTML attributes into the output.
1639
+ * Called when editor receives focus
1641
1640
  */
1642
- private applyGlobalAttributes;
1641
+ onFocus?: (props: {
1642
+ event: FocusEvent;
1643
+ }) => void;
1643
1644
  /**
1644
- * Builds ProseMirror Schema from Node and Mark extensions
1645
+ * Called when editor loses focus
1645
1646
  */
1646
- private buildSchema;
1647
+ onBlur?: (props: {
1648
+ event: FocusEvent;
1649
+ }) => void;
1647
1650
  /**
1648
- * Initializes storage for all extensions
1651
+ * Called when editor is being destroyed
1652
+ * Use for cleanup (remove event listeners, etc.)
1649
1653
  */
1650
- private initializeStorage;
1654
+ onDestroy?: () => void;
1655
+ }
1656
+ /**
1657
+ * Full configuration type for Extension.create()
1658
+ * Combines base properties with ThisType for proper `this` typing.
1659
+ *
1660
+ * @typeParam Options - Extension options type
1661
+ * @typeParam Storage - Extension storage type
1662
+ */
1663
+ type ExtensionConfig<Options = unknown, Storage = unknown> = ExtensionConfigBase<Options, Storage> & ThisType<ExtensionContext<Options, Storage>>;
1664
+
1665
+ /**
1666
+ * Attribute specification for Node and Mark extensions
1667
+ *
1668
+ * Used by addAttributes() to define node/mark attributes
1669
+ * with parsing, rendering, and validation rules.
1670
+ *
1671
+ * @example
1672
+ * addAttributes() {
1673
+ * return {
1674
+ * level: {
1675
+ * default: 1,
1676
+ * parseHTML: (element) => parseInt(element.tagName.charAt(1), 10),
1677
+ * renderHTML: (attributes) => ({ 'data-level': attributes.level }),
1678
+ * },
1679
+ * };
1680
+ * }
1681
+ */
1682
+ /**
1683
+ * Specification for a single attribute
1684
+ */
1685
+ interface AttributeSpec {
1651
1686
  /**
1652
- * Builds all ProseMirror plugins from extensions
1687
+ * Default value for the attribute
1688
+ * Used when the attribute is not explicitly set
1653
1689
  */
1654
- private buildPlugins;
1690
+ default?: unknown;
1655
1691
  /**
1656
- * Collects keyboard shortcuts from all extensions
1657
- * Returns ProseMirror-compatible commands for keymap plugin
1658
- *
1659
- * Note: Extensions should return PM-compatible commands from addKeyboardShortcuts()
1692
+ * Whether this attribute is rendered to the DOM
1693
+ * @default true
1660
1694
  */
1661
- private collectKeyboardShortcuts;
1695
+ rendered?: boolean;
1662
1696
  /**
1663
- * Collects input rules from all extensions
1697
+ * Keep this attribute when splitting the node
1698
+ * For example, heading level should be kept when pressing Enter
1699
+ * @default true
1664
1700
  */
1665
- private collectInputRules;
1701
+ keepOnSplit?: boolean;
1666
1702
  /**
1667
- * Collects commands from all extensions
1703
+ * Validate attribute value (ProseMirror 1.22.0+).
1704
+ * When a string, a `|`-separated list of primitive types
1705
+ * (`"number"`, `"string"`, `"boolean"`, `"null"`, `"undefined"`).
1706
+ * When a function, it should throw if the value is invalid.
1668
1707
  *
1669
- * Note: Commands with the same name will be overwritten by later extensions
1670
- * (lower priority extensions override higher priority). This is intentional
1671
- * to allow customization of built-in commands.
1672
- */
1673
- private collectCommands;
1674
- /**
1675
- * Collects toolbar items from all extensions
1708
+ * @example
1709
+ * validate: 'number'
1710
+ * validate: 'string|null'
1711
+ * validate: (value) => { if (typeof value !== 'number') throw new Error('expected number'); }
1676
1712
  */
1677
- private collectToolbarItems;
1713
+ validate?: string | ((value: unknown) => void);
1678
1714
  /**
1679
- * Collects node views from all Node extensions.
1680
- * Returns a map of node name to NodeViewConstructor for EditorView.
1715
+ * Parse attribute value from HTML element
1716
+ * Called during HTML parsing to extract attribute value
1681
1717
  *
1682
- * Each constructor is annotated with `__domternalContext` containing
1683
- * the editor and extension metadata so framework wrappers (React, Vue)
1684
- * can access them without changing the ProseMirror calling convention.
1685
- */
1686
- private collectNodeViews;
1687
- /**
1688
- * Validates that the schema has required nodes
1689
- * @throws Error if schema is missing 'doc' or 'text' nodes
1690
- */
1691
- validateSchema(): void;
1692
- /**
1693
- * Cleans up the extension manager
1694
- * Calls onDestroy on all extensions and clears all caches
1718
+ * @param element - The HTML element being parsed
1719
+ * @returns The attribute value
1720
+ *
1721
+ * @example
1722
+ * parseHTML: (element) => element.getAttribute('data-level')
1695
1723
  */
1696
- destroy(): void;
1724
+ parseHTML?: (element: HTMLElement) => unknown;
1697
1725
  /**
1698
- * Safely executes a function, catching and reporting errors
1699
- * Prevents a single extension error from crashing the entire editor
1726
+ * Render attribute to HTML attributes object
1727
+ * Called during HTML serialization
1700
1728
  *
1701
- * Handles both synchronous errors and async promise rejections.
1729
+ * @param attributes - All attributes of the node/mark
1730
+ * @returns HTML attributes object or null to skip
1702
1731
  *
1703
- * @param fn - Function to execute
1704
- * @param context - Context for error reporting (e.g., 'Bold.onUpdate')
1705
- * @returns The function result, or undefined if an error occurred
1706
- */
1707
- safeCall<T>(fn: () => T, context: string): T | undefined;
1708
- /**
1709
- * Calls onBeforeCreate on all extensions
1732
+ * @example
1733
+ * renderHTML: (attributes) => ({ 'data-level': attributes.level })
1710
1734
  */
1711
- callOnBeforeCreate(): void;
1735
+ renderHTML?: (attributes: Record<string, unknown>) => Record<string, string | number | boolean | null | undefined> | null;
1736
+ }
1737
+ /**
1738
+ * Collection of attribute specifications
1739
+ * Keyed by attribute name
1740
+ */
1741
+ type AttributeSpecs = Record<string, AttributeSpec>;
1742
+
1743
+ /**
1744
+ * Node configuration types
1745
+ *
1746
+ * These types define the configuration object passed to Node.create()
1747
+ * for creating ProseMirror node extensions.
1748
+ */
1749
+
1750
+ /**
1751
+ * Editor interface for Node context
1752
+ * Includes schema with nodes for NodeType getter
1753
+ */
1754
+ interface NodeEditorContext {
1755
+ readonly state: EditorState;
1756
+ readonly view: EditorView;
1757
+ readonly schema: {
1758
+ nodes: Record<string, NodeType>;
1759
+ };
1760
+ readonly commands: Record<string, (...args: unknown[]) => boolean>;
1761
+ }
1762
+ /**
1763
+ * Context interface for Node config methods.
1764
+ * Extends ExtensionContext with node-specific properties.
1765
+ * This enables proper typing of `this.options`, `this.nodeType`, etc.
1766
+ *
1767
+ * @typeParam Options - Node options type
1768
+ * @typeParam Storage - Node storage type
1769
+ */
1770
+ interface NodeContext<Options = unknown, Storage = unknown> extends Omit<ExtensionContext<Options, Storage>, 'editor' | 'type'> {
1771
+ /** Node type identifier */
1772
+ readonly type: 'node';
1773
+ /** Editor instance with schema access */
1774
+ editor: NodeEditorContext | null;
1775
+ /** ProseMirror NodeType (null until editor is initialized) */
1776
+ readonly nodeType: NodeType | null;
1777
+ }
1778
+ /**
1779
+ * Parse rule for converting HTML to ProseMirror node
1780
+ * Simplified version of ProseMirror's ParseRule
1781
+ */
1782
+ interface NodeParseRule {
1712
1783
  /**
1713
- * Calls onCreate on all extensions
1784
+ * CSS selector or tag name to match
1785
+ * @example 'p', 'h1', 'div.my-class'
1714
1786
  */
1715
- callOnCreate(): void;
1787
+ tag?: string;
1716
1788
  /**
1717
- * Calls onUpdate on all extensions
1789
+ * Match by CSS style
1790
+ * @example 'font-weight'
1718
1791
  */
1719
- callOnUpdate(): void;
1792
+ style?: string;
1720
1793
  /**
1721
- * Calls onSelectionUpdate on all extensions
1794
+ * Priority for this rule (higher = checked first)
1795
+ * @default 50
1722
1796
  */
1723
- callOnSelectionUpdate(): void;
1797
+ priority?: number;
1724
1798
  /**
1725
- * Calls onTransaction on all extensions
1726
- * @param props - Transaction props
1799
+ * Whether to consume the matched element
1800
+ * @default true
1727
1801
  */
1728
- callOnTransaction(props: {
1729
- transaction: Transaction;
1730
- }): void;
1802
+ consuming?: boolean;
1731
1803
  /**
1732
- * Calls onFocus on all extensions
1733
- * @param props - Focus event props
1804
+ * Context required for this rule to match
1805
+ * ProseMirror content expression
1734
1806
  */
1735
- callOnFocus(props: {
1736
- event: FocusEvent;
1737
- }): void;
1807
+ context?: string;
1738
1808
  /**
1739
- * Calls onBlur on all extensions
1740
- * @param props - Blur event props
1809
+ * Get attributes from the matched element
1810
+ * Return null/undefined to skip this rule
1741
1811
  */
1742
- callOnBlur(props: {
1743
- event: FocusEvent;
1744
- }): void;
1745
- }
1746
-
1747
- /**
1748
- * Inline Styles Utility
1749
- *
1750
- * Applies inline CSS styles to serialized HTML so it renders correctly
1751
- * when pasted outside the editor (email clients, CMS, Google Docs, etc.).
1752
- *
1753
- * Uses hardcoded light-theme defaults (same approach as Google Docs, Notion,
1754
- * TinyMCE). Optionally accepts overrides for custom styling.
1755
- *
1756
- * Only structural styles are inlined (borders, padding, margins, fonts).
1757
- * Colors are NOT inlined — explicit colors (TextColor, Highlight, cell bg)
1758
- * are already inline from renderHTML, and default text color is browser default.
1759
- */
1760
- interface InlineStyleOverrides {
1761
- blockquoteBorder?: string;
1762
- blockquoteColor?: string;
1763
- tableBorder?: string;
1764
- tableHeaderBg?: string;
1765
- codeBg?: string;
1766
- codeFont?: string;
1767
- codeBorder?: string;
1768
- codeBlockBg?: string;
1769
- codeBlockFont?: string;
1770
- hrBorder?: string;
1771
- linkColor?: string;
1772
- detailsBorder?: string;
1773
- detailsBg?: string;
1812
+ getAttrs?: ((node: HTMLElement) => Record<string, unknown> | null | undefined) | null;
1774
1813
  /**
1775
- * How to export table column widths from `data-colwidth` attributes.
1776
- * - `'percent'` (default): convert to percentage widths on first-row cells
1777
- * - `'pixel'`: convert to pixel widths on first-row cells, table gets fixed width
1778
- * - `'none'`: leave `data-colwidth` as-is, no width styles applied
1814
+ * Get content from the matched element
1815
+ * Return false to use default content parsing
1779
1816
  */
1780
- tableColumnWidths?: 'percent' | 'pixel' | 'none';
1817
+ getContent?: (node: HTMLElement, schema: unknown) => unknown;
1781
1818
  /**
1782
- * Optional callback to syntax-highlight code blocks.
1783
- * Receives the raw text content and optional language, returns highlighted HTML
1784
- * with `<span class="hljs-*">` markup (or any spans with inline styles).
1785
- *
1786
- * @example
1787
- * ```ts
1788
- * import { createLowlight, common } from 'lowlight';
1789
- * import { toHtml } from 'hast-util-to-html';
1790
- * const lowlight = createLowlight(common);
1791
- *
1792
- * inlineStyles(html, {
1793
- * codeHighlighter: (code, language) => {
1794
- * if (language && lowlight.registered(language)) {
1795
- * return toHtml(lowlight.highlight(language, code));
1796
- * }
1797
- * return null; // no highlighting
1798
- * },
1799
- * });
1800
- * ```
1819
+ * How to preserve whitespace
1801
1820
  */
1802
- codeHighlighter?: (code: string, language: string | null) => string | null;
1821
+ preserveWhitespace?: boolean | 'full';
1803
1822
  }
1804
1823
  /**
1805
- * Applies inline styles to all elements in a container.
1806
- * Exported for use in clipboardSerializer (operates on DOM directly).
1807
- */
1808
- declare function applyInlineStyles(container: HTMLElement, overrides?: InlineStyleOverrides): void;
1809
- /**
1810
- * Takes an HTML string and returns it with inline CSS styles applied
1811
- * to all elements, so it renders correctly outside the editor.
1812
- *
1813
- * @param html - Serialized HTML string from editor.getHTML()
1814
- * @param overrides - Optional style overrides for custom theming
1815
- *
1816
- * @example
1817
- * ```ts
1818
- * // Default light-theme styles
1819
- * const styled = inlineStyles(editor.getHTML());
1820
- *
1821
- * // With custom overrides
1822
- * const styled = inlineStyles(editor.getHTML(), {
1823
- * blockquoteBorder: '5px solid red',
1824
- * linkColor: '#ff6600',
1825
- * });
1826
- * ```
1827
- */
1828
- declare function inlineStyles(html: string, overrides?: InlineStyleOverrides): string;
1829
-
1830
- /**
1831
- * Editor - Main editor class wrapping ProseMirror
1832
- *
1833
- * Manages extensions, schema, commands, and the ProseMirror EditorView/State.
1834
- */
1835
-
1836
- /**
1837
- * Main editor class
1838
- *
1839
- * Wraps ProseMirror's EditorView and EditorState with a cleaner API.
1840
- *
1841
- * @example
1842
- * ```ts
1843
- * import { Editor } from '@domternal/core';
1844
- * import { Schema } from '@domternal/pm/model';
1845
- *
1846
- * const schema = new Schema({
1847
- * nodes: { doc: { content: 'paragraph+' }, paragraph: { content: 'text*' }, text: {} }
1848
- * });
1849
- *
1850
- * const editor = new Editor({
1851
- * schema,
1852
- * element: document.getElementById('editor'),
1853
- * content: '<p>Hello world</p>',
1854
- * });
1855
- *
1856
- * // Get content
1857
- * const json = editor.getJSON();
1858
- * const html = editor.getHTML();
1859
- *
1860
- * // Set content
1861
- * editor.commands.setContent('<p>New content</p>');
1862
- *
1863
- * // Cleanup
1864
- * editor.destroy();
1865
- * ```
1866
- */
1867
- declare class Editor extends EventEmitter<EditorEvents> {
1868
- /**
1869
- * Editor configuration options
1870
- */
1871
- private options;
1872
- /**
1873
- * Manages extensions and schema
1874
- * @internal Exposed for CommandManager, not for public use
1875
- */
1876
- private _extensionManager;
1824
+ * Props passed to renderHTML function
1825
+ */
1826
+ interface NodeRenderHTMLProps {
1877
1827
  /**
1878
- * Gets the extension manager
1879
- * @internal For CommandManager use only
1828
+ * The ProseMirror node being rendered
1880
1829
  */
1881
- get extensionManager(): ExtensionManager;
1830
+ node: Node$1;
1882
1831
  /**
1883
- * Manages commands
1832
+ * Merged HTML attributes from all sources
1884
1833
  */
1885
- private commandManager;
1834
+ HTMLAttributes: Record<string, unknown>;
1835
+ }
1836
+ /**
1837
+ * Node-specific configuration properties (schema-related)
1838
+ */
1839
+ interface NodeSchemaProperties {
1886
1840
  /**
1887
- * ProseMirror EditorView instance
1841
+ * Node group(s) this node belongs to
1842
+ * Used in content expressions
1843
+ *
1844
+ * @example 'block', 'inline', 'block list'
1845
+ * Can be a function that returns the group string (useful for dynamic group based on options)
1888
1846
  */
1889
- view: EditorView;
1847
+ group?: string | (() => string);
1890
1848
  /**
1891
- * Whether the editor has been destroyed
1849
+ * Content expression defining allowed children
1850
+ * Uses ProseMirror content expression syntax
1851
+ *
1852
+ * @example 'inline*', 'block+', 'paragraph block*'
1892
1853
  */
1893
- private _isDestroyed;
1854
+ content?: string;
1894
1855
  /**
1895
- * Timer for autofocus (cleared on destroy to prevent memory leaks)
1856
+ * Whether this is an inline node
1857
+ * Can be a function for dynamic inline based on options
1858
+ * @default false
1896
1859
  */
1897
- private _autofocusTimer;
1860
+ inline?: boolean | (() => boolean);
1898
1861
  /**
1899
- * Creates a new Editor instance
1862
+ * Whether this node is an atom (no direct content editing)
1863
+ * Cursor moves around atoms, not into them
1900
1864
  *
1901
- * @param options - Editor configuration
1902
- * @throws Error if running in SSR environment (no window)
1903
- * @throws Error if schema is not provided
1865
+ * @example true for images, mentions, emoji
1904
1866
  */
1905
- constructor(options: EditorOptions);
1867
+ atom?: boolean;
1906
1868
  /**
1907
- * Gets the current EditorState
1869
+ * Whether the node can be selected as a whole
1870
+ * @default true for leaf nodes, false for others
1908
1871
  */
1909
- get state(): EditorState;
1872
+ selectable?: boolean;
1910
1873
  /**
1911
- * Gets the ProseMirror schema
1874
+ * Whether the node can be dragged
1875
+ * @default false
1912
1876
  */
1913
- get schema(): Schema;
1877
+ draggable?: boolean;
1914
1878
  /**
1915
- * Checks if the editor is editable
1879
+ * Whether this node represents code
1880
+ * Affects text input handling (disables smart quotes, etc.)
1916
1881
  */
1917
- get isEditable(): boolean;
1882
+ code?: boolean;
1918
1883
  /**
1919
- * Checks if the editor content is empty
1884
+ * How whitespace is handled in this node
1885
+ * - 'pre': preserve whitespace (like <pre>)
1886
+ * - 'normal': collapse whitespace (default)
1920
1887
  */
1921
- get isEmpty(): boolean;
1888
+ whitespace?: 'pre' | 'normal';
1922
1889
  /**
1923
- * Checks if the editor has focus
1890
+ * Whether this node isolates marks at its boundaries
1891
+ * Marks don't extend across isolating boundaries
1924
1892
  */
1925
- get isFocused(): boolean;
1893
+ isolating?: boolean;
1926
1894
  /**
1927
- * Checks if the editor has been destroyed
1895
+ * Whether a gap cursor is allowed inside this node.
1896
+ * Set to false to prevent gapcursor from appearing inside this node.
1897
+ * Set to true to force gapcursor even if the default heuristic disallows it.
1898
+ * When undefined, uses ProseMirror's default heuristic.
1928
1899
  */
1929
- get isDestroyed(): boolean;
1900
+ allowGapCursor?: boolean;
1930
1901
  /**
1931
- * Gets single commands for immediate execution
1932
- * @example editor.commands.focus('end')
1902
+ * Table role for prosemirror-tables integration.
1903
+ * Nodes with a tableRole are discovered by prosemirror-tables via spec.tableRole.
1904
+ * One of: 'table', 'row', 'cell', 'header_cell'
1933
1905
  */
1934
- get commands(): SingleCommands;
1906
+ tableRole?: 'table' | 'row' | 'cell' | 'header_cell';
1935
1907
  /**
1936
- * Creates a command chain for batched execution
1937
- * @example editor.chain().focus().insertText('Hello').run()
1908
+ * Whether this is a top-level node (document root)
1909
+ * Only one node should have this set to true
1938
1910
  */
1939
- chain(): ChainedCommands;
1911
+ topNode?: boolean;
1940
1912
  /**
1941
- * Checks if commands can be executed (dry-run)
1942
- * @example if (editor.can().toggleBold()) { ... }
1913
+ * Whether this node defines its own scope
1914
+ * Content and marks don't leak out of defining nodes
1943
1915
  */
1944
- can(): CanCommands;
1916
+ defining?: boolean;
1945
1917
  /**
1946
- * Gets extension storage
1947
- * Access via: editor.storage.extensionName.propertyName
1918
+ * Which marks are allowed in this node
1919
+ * Empty string means no marks allowed
1920
+ *
1921
+ * @example '', '_', 'bold italic'
1948
1922
  */
1949
- get storage(): Record<string, unknown>;
1923
+ marks?: string;
1950
1924
  /**
1951
- * Gets toolbar items registered by all extensions.
1952
- * Used by framework toolbar components to auto-generate UI.
1925
+ * Custom text for leaf nodes returned by `getText()` / `textContent`.
1926
+ *
1927
+ * @example '\n' for hard break
1953
1928
  */
1954
- get toolbarItems(): ToolbarItem[];
1929
+ leafText?: string | ((node: Node$1) => string);
1955
1930
  /**
1956
- * Checks if a node or mark is currently active
1957
- *
1958
- * For toolbar button states - returns true if:
1959
- * - For marks: the current selection has the mark applied
1960
- * - For nodes: the cursor is inside that node type
1961
- *
1962
- * @param nameOrAttributes - Extension name, or object with name and attributes
1963
- * @param attributes - Optional attributes to match (for node/mark specific states)
1931
+ * Define node attributes
1932
+ * Returns attribute specifications
1964
1933
  *
1965
1934
  * @example
1966
- * editor.isActive('bold') // → true if bold mark is active
1967
- * editor.isActive('heading', { level: 2 }) // → true if in h2
1968
- * editor.isActive({ name: 'textAlign', attributes: { align: 'center' } })
1935
+ * addAttributes() {
1936
+ * return {
1937
+ * level: { default: 1 },
1938
+ * };
1939
+ * }
1969
1940
  */
1970
- isActive(nameOrAttributes: string | {
1971
- name: string;
1972
- attributes?: Record<string, unknown>;
1973
- }, attributes?: Record<string, unknown>): boolean;
1941
+ addAttributes?: () => AttributeSpecs;
1974
1942
  /**
1975
- * Gets attributes of the currently active node or mark
1976
- *
1977
- * Returns empty object if the node/mark is not found or not active.
1978
- *
1979
- * @param name - Extension name (node or mark)
1943
+ * Parse rules for converting HTML to this node
1944
+ * Each rule defines how to match and parse HTML elements
1980
1945
  *
1981
1946
  * @example
1982
- * editor.getAttributes('heading') // → { level: 2 }
1983
- * editor.getAttributes('link') // → { href: 'https://...', target: '_blank' }
1947
+ * parseHTML() {
1948
+ * return [
1949
+ * { tag: 'p' },
1950
+ * { tag: 'div', priority: 10 },
1951
+ * ];
1952
+ * }
1984
1953
  */
1985
- getAttributes(name: string): Record<string, unknown>;
1954
+ parseHTML?: () => NodeParseRule[];
1986
1955
  /**
1987
- * Helper to match attributes
1988
- * Returns true if target contains all key/value pairs from source
1956
+ * Render this node to DOM
1957
+ * Returns DOMOutputSpec (tag, attributes, children)
1958
+ *
1959
+ * @example
1960
+ * renderHTML({ node, HTMLAttributes }) {
1961
+ * return ['p', HTMLAttributes, 0];
1962
+ * }
1989
1963
  */
1990
- private matchAttributes;
1964
+ renderHTML?: (props: NodeRenderHTMLProps) => DOMOutputSpec;
1991
1965
  /**
1992
- * Gets the document content as JSON
1966
+ * Custom node view constructor
1967
+ * For complex interactive nodes
1993
1968
  */
1994
- getJSON(): JSONContent;
1969
+ addNodeView?: () => NodeViewConstructor;
1995
1970
  /**
1996
- * Gets the document content as HTML string
1997
- *
1998
- * @param options - Optional settings
1999
- * @param options.styled - When true (or an override object), applies inline CSS
2000
- * styles so the HTML renders correctly outside the editor (email, CMS, etc.)
1971
+ * Additional ProseMirror plugins for this node
1972
+ * Called during plugin collection
2001
1973
  */
2002
- getHTML(options?: {
2003
- styled?: boolean | InlineStyleOverrides;
2004
- }): string;
1974
+ addProseMirrorPlugins?: () => Plugin[];
1975
+ }
1976
+ /**
1977
+ * Configuration for Node extensions
1978
+ * Combines ExtensionConfig base with node-specific schema properties.
1979
+ * Uses ThisType<NodeContext> to provide proper typing for `this` in config methods.
1980
+ *
1981
+ * @typeParam Options - Node options type
1982
+ * @typeParam Storage - Node storage type
1983
+ *
1984
+ * @example
1985
+ * const Paragraph = Node.create({
1986
+ * name: 'paragraph',
1987
+ * group: 'block',
1988
+ * content: 'inline*',
1989
+ * parseHTML() {
1990
+ * // `this` is properly typed here!
1991
+ * return [{ tag: 'p' }];
1992
+ * },
1993
+ * renderHTML({ HTMLAttributes }) {
1994
+ * return ['p', HTMLAttributes, 0];
1995
+ * },
1996
+ * });
1997
+ */
1998
+ type NodeConfig<Options = unknown, Storage = unknown> = ExtensionConfigBase<Options, Storage> & NodeSchemaProperties & ThisType<NodeContext<Options, Storage>>;
1999
+
2000
+ /**
2001
+ * Mark configuration types
2002
+ *
2003
+ * These types define the configuration object passed to Mark.create()
2004
+ * for creating ProseMirror mark extensions.
2005
+ */
2006
+
2007
+ /**
2008
+ * Editor interface for Mark context
2009
+ * Includes schema with marks for MarkType getter
2010
+ */
2011
+ interface MarkEditorContext {
2012
+ readonly state: EditorState;
2013
+ readonly view: EditorView;
2014
+ readonly schema: {
2015
+ marks: Record<string, MarkType>;
2016
+ };
2017
+ readonly commands: Record<string, (...args: unknown[]) => boolean>;
2018
+ }
2019
+ /**
2020
+ * Context interface for Mark config methods.
2021
+ * Extends ExtensionContext with mark-specific properties.
2022
+ * This enables proper typing of `this.options`, `this.markType`, etc.
2023
+ *
2024
+ * @typeParam Options - Mark options type
2025
+ * @typeParam Storage - Mark storage type
2026
+ */
2027
+ interface MarkContext<Options = unknown, Storage = unknown> extends Omit<ExtensionContext<Options, Storage>, 'editor' | 'type'> {
2028
+ /** Mark type identifier */
2029
+ readonly type: 'mark';
2030
+ /** Editor instance with schema access */
2031
+ editor: MarkEditorContext | null;
2032
+ /** ProseMirror MarkType (null until editor is initialized) */
2033
+ readonly markType: MarkType | null;
2034
+ }
2035
+ /**
2036
+ * Parse rule for converting HTML to ProseMirror mark
2037
+ * Simplified version of ProseMirror's ParseRule for marks
2038
+ */
2039
+ interface MarkParseRule {
2005
2040
  /**
2006
- * Gets the document content as plain text
2007
- *
2008
- * @param options - Options for text extraction
2009
- * @param options.blockSeparator - String to insert between blocks (default: '\n\n')
2041
+ * CSS selector or tag name to match
2042
+ * @example 'strong', 'b', 'span.bold'
2010
2043
  */
2011
- getText(options?: {
2012
- blockSeparator?: string;
2013
- }): string;
2044
+ tag?: string;
2045
+ /**
2046
+ * Match by CSS style property
2047
+ * Can include expected value after '='
2048
+ * @example 'font-weight', 'font-weight=bold'
2049
+ */
2050
+ style?: string;
2014
2051
  /**
2015
- * Executes a command with proper CommandProps
2016
- * @internal
2052
+ * Priority for this rule (higher = checked first)
2053
+ * @default 50
2017
2054
  */
2018
- private runCommand;
2055
+ priority?: number;
2019
2056
  /**
2020
- * Sets the editor content
2021
- *
2022
- * @param content - JSON or HTML content
2023
- * @param emitUpdate - Whether to emit update event (default: true)
2024
- * @returns true if content was set successfully, false if content was invalid
2057
+ * Whether to consume the matched element
2058
+ * @default true
2025
2059
  */
2026
- setContent(content: Content, emitUpdate?: boolean): boolean;
2060
+ consuming?: boolean;
2027
2061
  /**
2028
- * Clears the editor content
2062
+ * Get attributes from the matched element
2063
+ * Return null/undefined to skip this rule
2064
+ * Return false to explicitly not match
2029
2065
  *
2030
- * @param emitUpdate - Whether to emit update event (default: true)
2031
- * @returns true if content was cleared successfully
2066
+ * For style rules, receives the style value as string
2032
2067
  */
2033
- clearContent(emitUpdate?: boolean): boolean;
2068
+ getAttrs?: ((node: HTMLElement | string) => Record<string, unknown> | false | null) | null;
2069
+ }
2070
+ /**
2071
+ * Props passed to renderHTML function for marks
2072
+ */
2073
+ interface MarkRenderHTMLProps {
2034
2074
  /**
2035
- * Sets whether the editor is editable
2036
- *
2037
- * @param editable - Whether the editor should be editable
2075
+ * The ProseMirror mark being rendered
2038
2076
  */
2039
- setEditable(editable: boolean): this;
2077
+ mark: Mark$1;
2040
2078
  /**
2041
- * Focuses the editor
2042
- *
2043
- * @param position - Where to place cursor (default: null = just focus)
2079
+ * Merged HTML attributes from all sources
2044
2080
  */
2045
- focus(position?: FocusPosition): this;
2081
+ HTMLAttributes: Record<string, unknown>;
2082
+ }
2083
+ /**
2084
+ * Mark-specific configuration properties (schema-related)
2085
+ */
2086
+ interface MarkSchemaProperties {
2046
2087
  /**
2047
- * Removes focus from the editor
2088
+ * Whether this mark should be active when the cursor
2089
+ * is at its end (or beginning for marks that open).
2090
+ *
2091
+ * When true, typing at the mark's boundary continues the mark.
2092
+ * When false, typing creates unmarked content.
2093
+ *
2094
+ * @default true
2048
2095
  */
2049
- blur(): this;
2096
+ inclusive?: boolean | (() => boolean);
2050
2097
  /**
2051
- * Registers a ProseMirror plugin dynamically at runtime.
2052
- * Used by framework wrappers (e.g. Angular BubbleMenu component) to add
2053
- * plugins after the editor is created.
2098
+ * Marks that this mark excludes (cannot coexist with)
2099
+ *
2100
+ * - '_' excludes all marks
2101
+ * - Space-separated mark names exclude specific marks
2102
+ * - Empty string or undefined means no exclusions
2103
+ *
2104
+ * @example 'code' - excludes code mark
2105
+ * @example 'bold italic' - excludes bold and italic
2106
+ * @example '_' - excludes all other marks
2054
2107
  */
2055
- registerPlugin(plugin: Plugin): void;
2108
+ excludes?: string;
2056
2109
  /**
2057
- * Unregisters a ProseMirror plugin by its PluginKey.
2058
- * Uses PluginKey.get() to identify the plugin to remove.
2110
+ * Mark group(s) this mark belongs to
2111
+ * Used in node's marks property
2112
+ *
2113
+ * @example 'formatting', 'inline'
2059
2114
  */
2060
- unregisterPlugin(key: PluginKey): void;
2115
+ group?: string;
2061
2116
  /**
2062
- * Destroys the editor and cleans up resources
2117
+ * Whether this mark can span multiple nodes
2063
2118
  *
2064
- * After calling destroy(), the editor instance should not be used.
2119
+ * When true (default), the mark persists across inline nodes.
2120
+ * When false, the mark only applies within a single text node.
2121
+ *
2122
+ * @default true
2065
2123
  */
2066
- destroy(): void;
2124
+ spanning?: boolean;
2067
2125
  /**
2068
- * Builds a clipboardSerializer that applies a transform function to HTML on copy/cut.
2126
+ * Whether this mark represents visual formatting.
2127
+ *
2128
+ * Used by `unsetAllMarks` to decide which marks to remove.
2129
+ * Marks with `isFormatting: false` survive clear formatting
2130
+ * (e.g., links, comments, annotations).
2131
+ *
2132
+ * Can be overridden via `.configure()`:
2133
+ * ```ts
2134
+ * Link.configure({ isFormatting: true }) // make links clearable
2135
+ * ```
2136
+ *
2137
+ * @default true
2069
2138
  */
2070
- private buildClipboardSerializer;
2139
+ isFormatting?: boolean;
2071
2140
  /**
2072
- * Creates the editor instance
2141
+ * Define mark attributes
2142
+ * Returns attribute specifications
2143
+ *
2144
+ * @example
2145
+ * addAttributes() {
2146
+ * return {
2147
+ * color: { default: null },
2148
+ * };
2149
+ * }
2073
2150
  */
2074
- private createEditor;
2151
+ addAttributes?: () => AttributeSpecs;
2075
2152
  /**
2076
- * Handles ProseMirror transactions
2153
+ * Parse rules for converting HTML to this mark
2154
+ * Each rule defines how to match and parse HTML elements
2155
+ *
2156
+ * @example
2157
+ * parseHTML() {
2158
+ * return [
2159
+ * { tag: 'strong' },
2160
+ * { tag: 'b' },
2161
+ * { style: 'font-weight', getAttrs: (value) => /bold|[5-9]\d{2}/.test(value) && null },
2162
+ * ];
2163
+ * }
2077
2164
  */
2078
- private dispatchTransaction;
2165
+ parseHTML?: () => MarkParseRule[];
2079
2166
  /**
2080
- * Emit method - needed for CommandManager interface
2167
+ * Render this mark to DOM
2168
+ * Returns DOMOutputSpec (tag, attributes, hole)
2169
+ *
2170
+ * The 0 in the return array indicates where child content goes.
2171
+ *
2172
+ * @example
2173
+ * renderHTML({ mark, HTMLAttributes }) {
2174
+ * return ['strong', HTMLAttributes, 0];
2175
+ * }
2081
2176
  */
2082
- emit<E extends keyof EditorEvents>(event: E, ...args: EditorEvents[E] extends undefined ? [] : [EditorEvents[E]]): this;
2177
+ renderHTML?: (props: MarkRenderHTMLProps) => DOMOutputSpec;
2083
2178
  }
2179
+ /**
2180
+ * Configuration for Mark extensions
2181
+ * Combines ExtensionConfigBase with mark-specific schema properties.
2182
+ * Uses ThisType<MarkContext> to provide proper typing for `this` in config methods.
2183
+ *
2184
+ * @typeParam Options - Mark options type
2185
+ * @typeParam Storage - Mark storage type
2186
+ *
2187
+ * @example
2188
+ * const Bold = Mark.create({
2189
+ * name: 'bold',
2190
+ * parseHTML() {
2191
+ * // `this` is properly typed here!
2192
+ * return [{ tag: 'strong' }, { tag: 'b' }];
2193
+ * },
2194
+ * renderHTML({ HTMLAttributes }) {
2195
+ * return ['strong', HTMLAttributes, 0];
2196
+ * },
2197
+ * });
2198
+ */
2199
+ type MarkConfig<Options = unknown, Storage = unknown> = ExtensionConfigBase<Options, Storage> & MarkSchemaProperties & ThisType<MarkContext<Options, Storage>>;
2084
2200
 
2085
2201
  /**
2086
2202
  * Focus command - focuses the editor at the specified position
@@ -2168,7 +2284,7 @@ declare const insertText: CommandSpec$1<[text: string]>;
2168
2284
  declare const insertContent: CommandSpec$1<[content: Content]>;
2169
2285
 
2170
2286
  /**
2171
- * Mark commands toggleMark, setMark, unsetMark, unsetAllMarks
2287
+ * Mark commands - toggleMark, setMark, unsetMark, unsetAllMarks
2172
2288
  */
2173
2289
 
2174
2290
  /**
@@ -2271,7 +2387,7 @@ declare const lift: CommandSpec$1;
2271
2387
  declare const toggleList: CommandSpec$1<[listNodeName: string, listItemNodeName: string, attributes?: Attrs]>;
2272
2388
 
2273
2389
  /**
2274
- * Attribute commands updateAttributes, resetAttributes
2390
+ * Attribute commands - updateAttributes, resetAttributes
2275
2391
  */
2276
2392
 
2277
2393
  /**
@@ -2370,8 +2486,8 @@ declare class CommandManager {
2370
2486
  */
2371
2487
  private buildCommandProps;
2372
2488
  /**
2373
- * Single commands that execute immediately
2374
- * Uses Proxy to dynamically generate command methods (ID-1)
2489
+ * Single commands that execute immediately.
2490
+ * Uses a Proxy to dynamically generate command methods.
2375
2491
  *
2376
2492
  * @example
2377
2493
  * editor.commands.focus('end');
@@ -2430,61 +2546,237 @@ interface PositionFloatingOptions {
2430
2546
  trackScroll?: boolean;
2431
2547
  }
2432
2548
  /**
2433
- * Positions a floating element relative to a reference element or virtual rect,
2434
- * and keeps it positioned on scroll, resize, and layout shifts.
2435
- *
2436
- * Uses `autoUpdate` from floating-ui with `animationFrame` polling for
2437
- * jitter-free scroll tracking (rAF syncs with browser paint).
2438
- *
2439
- * Includes `hide` middleware when the reference element is scrolled out of
2440
- * view, the floating element is hidden via `visibility: hidden`.
2441
- *
2442
- * The floating element must have `position: fixed`.
2443
- *
2444
- * Returns a cleanup function. **Always call it** when hiding or destroying
2445
- * the floating element to stop listeners and prevent memory leaks.
2446
- *
2447
- * @example
2448
- * ```ts
2449
- * // Start auto-positioning (follows scroll/resize)
2450
- * const cleanup = positionFloating(buttonEl, dropdownEl, {
2451
- * placement: 'bottom-start',
2452
- * });
2453
- *
2454
- * // Virtual reference (e.g. cursor position must return fresh coords)
2455
- * const virtualEl = {
2456
- * getBoundingClientRect: () => {
2457
- * const coords = view.coordsAtPos(pos);
2458
- * return new DOMRect(coords.left, coords.top, 0, coords.bottom - coords.top);
2459
- * },
2460
- * };
2461
- * const cleanup = positionFloating(virtualEl, tooltipEl, { placement: 'top' });
2549
+ * Positions a floating element relative to a reference element or virtual rect,
2550
+ * and keeps it positioned on scroll, resize, and layout shifts.
2551
+ *
2552
+ * Uses `autoUpdate` from floating-ui with `animationFrame` polling for
2553
+ * jitter-free scroll tracking (rAF syncs with browser paint).
2554
+ *
2555
+ * Includes `hide` middleware - when the reference element is scrolled out of
2556
+ * view, the floating element is hidden via `visibility: hidden`.
2557
+ *
2558
+ * The floating element must have `position: fixed`.
2559
+ *
2560
+ * Returns a cleanup function. **Always call it** when hiding or destroying
2561
+ * the floating element to stop listeners and prevent memory leaks.
2562
+ *
2563
+ * @example
2564
+ * ```ts
2565
+ * // Start auto-positioning (follows scroll/resize)
2566
+ * const cleanup = positionFloating(buttonEl, dropdownEl, {
2567
+ * placement: 'bottom-start',
2568
+ * });
2569
+ *
2570
+ * // Virtual reference (e.g. cursor position - must return fresh coords)
2571
+ * const virtualEl = {
2572
+ * getBoundingClientRect: () => {
2573
+ * const coords = view.coordsAtPos(pos);
2574
+ * return new DOMRect(coords.left, coords.top, 0, coords.bottom - coords.top);
2575
+ * },
2576
+ * };
2577
+ * const cleanup = positionFloating(virtualEl, tooltipEl, { placement: 'top' });
2578
+ *
2579
+ * // Stop when done
2580
+ * cleanup();
2581
+ * ```
2582
+ */
2583
+ declare function positionFloating(reference: Element | {
2584
+ getBoundingClientRect: () => DOMRect;
2585
+ }, floating: HTMLElement, options?: PositionFloatingOptions): () => void;
2586
+ /**
2587
+ * Positions a floating element using `strategy: 'absolute'` so it scrolls
2588
+ * together with its offsetParent - zero jitter by design.
2589
+ *
2590
+ * Ideal for dropdowns inside scroll containers (e.g. emoji suggestion inside
2591
+ * `.dm-editor`) and toolbar dropdowns. The absolute coordinates are stable
2592
+ * across scrolls - only `flip`/`shift` decisions change on scroll, producing
2593
+ * a discrete jump rather than continuous jitter.
2594
+ *
2595
+ * The floating element must have `position: absolute` and its offsetParent
2596
+ * must have `position: relative`.
2597
+ *
2598
+ * Returns a cleanup function - call it when hiding or destroying the
2599
+ * floating element.
2600
+ */
2601
+ declare function positionFloatingOnce(reference: Element | {
2602
+ getBoundingClientRect: () => DOMRect;
2603
+ }, floating: HTMLElement, options?: PositionFloatingOptions): () => void;
2604
+
2605
+ /**
2606
+ * Clipboard helper - writes plain text to the system clipboard.
2607
+ *
2608
+ * Tries the modern async Clipboard API first; if unavailable or denied
2609
+ * (Safari private mode, insecure context, missing user gesture), falls
2610
+ * back to a hidden textarea + `document.execCommand('copy')`.
2611
+ *
2612
+ * Returns `true` on success, `false` on failure. Never throws - callers
2613
+ * decide how to surface failure (toast, re-throw, silent).
2614
+ *
2615
+ * @example
2616
+ * ```ts
2617
+ * const ok = await writeToClipboard('https://example.com#block-abc123');
2618
+ * if (!ok) showError('Copy failed');
2619
+ * ```
2620
+ */
2621
+ declare function writeToClipboard(text: string): Promise<boolean>;
2622
+
2623
+ /**
2624
+ * Default `contexts` map for a bubble menu when the consumer has not
2625
+ * supplied one. Returns a richer item set when the editor (or any
2626
+ * ancestor) carries the `.dm-notion-mode` class - that class is the
2627
+ * project-wide signal that the host is rendering Notion-style UX, so
2628
+ * the bubble menu mirrors it by including `link` and `textAlign`.
2629
+ *
2630
+ * Consumers can always override by passing their own `contexts` prop.
2631
+ */
2632
+ declare function defaultBubbleContexts(editor: Editor): Record<string, string[]>;
2633
+
2634
+ interface InsertAsListItemChildArgs {
2635
+ /** Existing transaction to mutate. Caller dispatches. */
2636
+ tr: Transaction;
2637
+ /** Position of the list wrapper node (bulletList / orderedList / taskList) in `tr.doc`. */
2638
+ wrapperPos: number;
2639
+ /**
2640
+ * Position of the target list item node (= position right BEFORE the
2641
+ * item) in `tr.doc`. When omitted, the wrapper's LAST child is used,
2642
+ * matching the Tab keyboard behaviour ("indent into last item").
2643
+ */
2644
+ targetItemPos?: number;
2645
+ /** Block node to append as the LAST child of the target item. */
2646
+ blockNode: Node$1;
2647
+ /**
2648
+ * Optional source range to delete in the SAME transaction (when
2649
+ * MOVING an existing block instead of creating a new one). Position
2650
+ * math handles source-before-vs-after-target ordering automatically.
2651
+ */
2652
+ sourceRange?: {
2653
+ from: number;
2654
+ to: number;
2655
+ };
2656
+ }
2657
+ interface InsertAsListItemChildResult {
2658
+ /** True when schema accepted the insertion and `tr` was mutated. */
2659
+ ok: boolean;
2660
+ /**
2661
+ * Position right BEFORE the inserted block in the resulting doc.
2662
+ * Caller can resolve `insertedAt + 1` to place the caret inside the
2663
+ * inserted block. Only present when `ok === true`.
2664
+ */
2665
+ insertedAt?: number;
2666
+ }
2667
+ /**
2668
+ * Insert `blockNode` as the LAST child of a list item (target item or, when
2669
+ * omitted, the wrapper's last item). When `sourceRange` is set, the source
2670
+ * range is removed in the same transaction so the op is a clean MOVE.
2671
+ * Returns `{ ok: false }` WITHOUT mutating `tr` on schema reject so callers
2672
+ * can fall through to a sibling-mode fallback.
2673
+ */
2674
+ declare function insertAsListItemChild(args: InsertAsListItemChildArgs): InsertAsListItemChildResult;
2675
+
2676
+ /**
2677
+ * Cursor context relative to the INNERMOST containing list/task item,
2678
+ * resolved only when the cursor's parent paragraph is a DIRECT child of
2679
+ * that item. Innermost so nested lists hand control to the deepest item;
2680
+ * direct-child so paragraphs inside blockquote/table-cell/etc. inside a
2681
+ * list item fall through to the container's own Enter/Backspace.
2682
+ */
2683
+ interface ListItemCursorContext {
2684
+ /** Depth of the containing list item ancestor in the resolved pos. */
2685
+ itemDepth: number;
2686
+ /** Position right BEFORE the containing list item in the doc. */
2687
+ itemPos: number;
2688
+ /** Position right BEFORE the containing list wrapper (one depth above the item). */
2689
+ wrapperPos: number;
2690
+ /** `true` when the parent paragraph is not the first child of the list item. */
2691
+ isInChildrenZone: boolean;
2692
+ /** `true` when the parent paragraph has no inline content. */
2693
+ paragraphIsEmpty: boolean;
2694
+ /** `true` when the parent paragraph is the LAST child of the list item. */
2695
+ isLastChild: boolean;
2696
+ /** Index of the parent paragraph within the list item (0 = label). */
2697
+ childIndex: number;
2698
+ /** Index of the containing list item within its wrapper. */
2699
+ itemIndexInWrapper: number;
2700
+ }
2701
+ /**
2702
+ * Resolve list-item cursor context. Returns `null` when the cursor's
2703
+ * shape does not match `(wrapper > item > paragraph)`; callers should
2704
+ * fall through to default Enter/Backspace in that case.
2705
+ */
2706
+ declare function getListItemCursorContext($from: ResolvedPos): ListItemCursorContext | null;
2707
+
2708
+ /**
2709
+ * Lift an empty children-zone paragraph out of its list/task item as a
2710
+ * top-level sibling. PM's `liftTarget` covers the common case; we fall
2711
+ * back to manual delete-and-insert when it returns null (empty p sandwiched
2712
+ * before a non-paragraph would leave the after-cut half violating the
2713
+ * `paragraph block*` schema).
2714
+ */
2715
+ declare function liftEmptyChildrenZoneParagraph(state: EditorState, dispatch: ((tr: Transaction) => void) | undefined, ctx: ListItemCursorContext): boolean;
2716
+
2717
+ /**
2718
+ * Insert an empty paragraph as next sibling inside the same list/task item,
2719
+ * keeping the cursor in the children-zone so Enter accumulates blocks at the
2720
+ * same nesting level (Notion semantics). Exit paths are Backspace on empty,
2721
+ * Shift+Tab, or Enter on the empty label paragraph.
2722
+ */
2723
+ declare function insertChildrenZoneSibling(state: EditorState, dispatch: ((tr: Transaction) => void) | undefined, ctx: ListItemCursorContext): boolean;
2724
+
2725
+ /**
2726
+ * Activation conditions (single-cursor only):
2727
+ * - selection is empty (single caret)
2728
+ * - cursor's nearest list-item ancestor exists
2729
+ * - cursor's containing block is at index 0 (label slot) of that item
2730
+ * - `tr` has no prior steps (lift steps captured from a fresh state must
2731
+ * replay against the same starting state as `tr`)
2732
+ *
2733
+ * Returns true if the lift was applied to `tr`. The caller can keep
2734
+ * chaining work on `tr` (e.g. `setBlockType`, `wrapIn`) on the new
2735
+ * top-level paragraph.
2736
+ */
2737
+ declare function liftCurrentListItem(state: EditorState, tr: Transaction): boolean;
2738
+
2739
+ interface SplitListForInsertRange {
2740
+ from: number;
2741
+ to: number;
2742
+ }
2743
+ /**
2744
+ * Activation conditions (single-cursor only):
2745
+ * - selection is empty
2746
+ * - cursor's nearest list-item ancestor exists
2747
+ * - cursor's containing block is at index 0 (label slot) of that item
2748
+ * - `tr` has no prior steps
2462
2749
  *
2463
- * // Stop when done
2464
- * cleanup();
2465
- * ```
2750
+ * @returns A range where the caller should call `tr.replaceWith(from, to,
2751
+ * [block, optionalTrailingParagraph])`, or `null` if the cursor isn't
2752
+ * in a list-item label.
2466
2753
  */
2467
- declare function positionFloating(reference: Element | {
2468
- getBoundingClientRect: () => DOMRect;
2469
- }, floating: HTMLElement, options?: PositionFloatingOptions): () => void;
2754
+ declare function splitListForInsert(state: EditorState, tr: Transaction): SplitListForInsertRange | null;
2755
+
2470
2756
  /**
2471
- * Positions a floating element using `strategy: 'absolute'` so it scrolls
2472
- * together with its offsetParent zero jitter by design.
2473
- *
2474
- * Ideal for dropdowns inside scroll containers (e.g. emoji suggestion inside
2475
- * `.dm-editor`) and toolbar dropdowns. The absolute coordinates are stable
2476
- * across scrolls — only `flip`/`shift` decisions change on scroll, producing
2477
- * a discrete jump rather than continuous jitter.
2478
- *
2479
- * The floating element must have `position: absolute` and its offsetParent
2480
- * must have `position: relative`.
2757
+ * Shared helpers for locating a list/task item ancestor of a resolved
2758
+ * position. Centralises the `LIST_ITEM_TYPE_NAMES` constant + ancestor
2759
+ * walk so the four+ consumers (liftCurrentListItem, splitListForInsert,
2760
+ * TaskItem keymap, Details proactive lift, slash menu filtering) stay
2761
+ * in sync.
2762
+ */
2763
+
2764
+ declare const LIST_ITEM_TYPE_NAMES: Set<string>;
2765
+ /**
2766
+ * Returns the depth at which the nearest list/task item ancestor sits,
2767
+ * or -1 if none.
2481
2768
  *
2482
- * Returns a cleanup function call it when hiding or destroying the
2483
- * floating element.
2769
+ * Walks UP from `$pos.depth` toward 1 (we skip depth 0 = doc itself).
2484
2770
  */
2485
- declare function positionFloatingOnce(reference: Element | {
2486
- getBoundingClientRect: () => DOMRect;
2487
- }, floating: HTMLElement, options?: PositionFloatingOptions): () => void;
2771
+ declare function findListItemAncestorDepth($pos: ResolvedPos): number;
2772
+ /** Convenience: true when the cursor has a list/task item ancestor. */
2773
+ declare function isInsideListItem($pos: ResolvedPos): boolean;
2774
+ /**
2775
+ * True when the cursor sits in the LABEL paragraph (index 0) of its
2776
+ * nearest list/task item ancestor. Used to gate Notion-style "operate
2777
+ * on the label" commands (slash menu CONVERT, INSERT, etc.).
2778
+ */
2779
+ declare function isInListItemLabel($pos: ResolvedPos): boolean;
2488
2780
 
2489
2781
  /**
2490
2782
  * Create a ProseMirror document from content
@@ -3160,7 +3452,7 @@ declare class Extension<Options = unknown, Storage = unknown> {
3160
3452
  * // Shallow merge behavior with nested objects:
3161
3453
  * // Given: options = { nested: { a: 1, b: 2 } }
3162
3454
  * // configure({ nested: { b: 3 } })
3163
- * // Result: { nested: { b: 3 } } 'a' is lost!
3455
+ * // Result: { nested: { b: 3 } } - 'a' is lost!
3164
3456
  * // To preserve nested values, spread manually:
3165
3457
  * // configure({ nested: { ...original.options.nested, b: 3 } })
3166
3458
  */
@@ -3289,25 +3581,12 @@ declare class Node<Options = unknown, Storage = unknown> extends Extension<Optio
3289
3581
  */
3290
3582
  static create<O = unknown, S = unknown>(config: NodeConfig<O, S>): Node<O, S>;
3291
3583
  /**
3292
- * Creates a new node with merged options
3293
- * Original node is not modified
3294
- *
3295
- * **Note:** Options are merged shallowly using object spread (`...`).
3296
- * Nested objects are replaced entirely, not deeply merged.
3297
- *
3298
- * @param options - Options to merge with existing options
3299
- * @returns New node instance with merged options
3584
+ * Creates a new node with merged options. Original node is not modified.
3585
+ * Options merge shallowly (object spread); see {@link Extension.configure}
3586
+ * for the nested-object gotcha and a workaround.
3300
3587
  *
3301
3588
  * @example
3302
3589
  * const CustomParagraph = Paragraph.configure({ HTMLAttributes: { class: 'custom' } });
3303
- *
3304
- * @example
3305
- * // Shallow merge behavior with nested objects:
3306
- * // Given: options = { HTMLAttributes: { class: 'a', id: 'b' } }
3307
- * // configure({ HTMLAttributes: { class: 'c' } })
3308
- * // Result: { HTMLAttributes: { class: 'c' } } — 'id' is lost!
3309
- * // To preserve nested values, spread manually:
3310
- * // configure({ HTMLAttributes: { ...original.options.HTMLAttributes, class: 'c' } })
3311
3590
  */
3312
3591
  configure(options: Partial<Options>): Node<Options, Storage>;
3313
3592
  /**
@@ -3351,7 +3630,7 @@ declare class Node<Options = unknown, Storage = unknown> extends Extension<Optio
3351
3630
  }
3352
3631
 
3353
3632
  /**
3354
- * ToolbarController Headless, framework-agnostic toolbar state machine
3633
+ * ToolbarController - Headless, framework-agnostic toolbar state machine
3355
3634
  *
3356
3635
  * Manages toolbar item collection, grouping, active state tracking,
3357
3636
  * dropdown state, and keyboard navigation. Framework wrappers (Angular,
@@ -3359,7 +3638,7 @@ declare class Node<Options = unknown, Storage = unknown> extends Extension<Optio
3359
3638
  *
3360
3639
  * @example
3361
3640
  * const controller = new ToolbarController(editor, () => {
3362
- * // Called on every state change trigger framework re-render
3641
+ * // Called on every state change - trigger framework re-render
3363
3642
  * });
3364
3643
  * controller.subscribe();
3365
3644
  * // ... use controller.groups, controller.activeMap, etc.
@@ -3419,7 +3698,7 @@ declare class ToolbarController {
3419
3698
  private _activeMap;
3420
3699
  /** Disabled state for each button (keyed by item.name) */
3421
3700
  private _disabledMap;
3422
- /** Expanded state for emitEvent buttons true when their panel is open */
3701
+ /** Expanded state for emitEvent buttons - true when their panel is open */
3423
3702
  private _expandedMap;
3424
3703
  /** Currently open dropdown name (null = none) */
3425
3704
  private _openDropdown;
@@ -3518,6 +3797,124 @@ declare class ToolbarController {
3518
3797
  private checkButtonExpanded;
3519
3798
  }
3520
3799
 
3800
+ /**
3801
+ * A group of floating-menu items sharing the same `group` value.
3802
+ */
3803
+ interface FloatingMenuGroup {
3804
+ name: string;
3805
+ items: FloatingMenuItem[];
3806
+ }
3807
+ /**
3808
+ * Groups items by their `group` property, preserving extension insertion
3809
+ * order of groups. Within each group, items are sorted by `priority`
3810
+ * descending (higher first, default 100).
3811
+ *
3812
+ * Shared between `FloatingMenuController` (which renders grouped item lists
3813
+ * for FloatingMenu + framework wrappers) and `createSlashSuggestionRenderer`
3814
+ * (the popup shown by SlashCommand). Having one implementation keeps visual
3815
+ * grouping consistent across every trigger mechanism.
3816
+ */
3817
+ declare function groupFloatingMenuItems(items: FloatingMenuItem[]): FloatingMenuGroup[];
3818
+
3819
+ /**
3820
+ * FloatingMenuController - Headless, framework-agnostic state machine for the
3821
+ * block-insert floating menu.
3822
+ *
3823
+ * Mirrors the shape of ToolbarController: collects items from extensions,
3824
+ * groups them, tracks focused index for roving-tabindex keyboard navigation,
3825
+ * and executes commands. Framework wrappers (Angular, React, Vue) bind their
3826
+ * templates to this controller.
3827
+ *
3828
+ * @example
3829
+ * const controller = new FloatingMenuController(editor, () => {
3830
+ * // onChange - re-render
3831
+ * });
3832
+ * controller.subscribe();
3833
+ * // ... controller.groups, controller.focusedIndex, controller.execute(item)
3834
+ * controller.destroy();
3835
+ */
3836
+
3837
+ /** -1 means no item is focused (menu not entered via keyboard). */
3838
+ declare const FLOATING_MENU_NO_FOCUS = -1;
3839
+ declare class FloatingMenuController {
3840
+ /**
3841
+ * Resolves an `items` option (array | function) against the editor's
3842
+ * default floating-menu items. Exposed as static so the plugin can
3843
+ * resolve once at init without constructing a controller.
3844
+ */
3845
+ static resolveItems(editor: Editor, override?: FloatingMenuItemsOverride): FloatingMenuItem[];
3846
+ /**
3847
+ * Executes a floating-menu item's command on the editor.
3848
+ * String commands are dispatched via `editor.commands[name](...args)`;
3849
+ * function commands are called directly.
3850
+ */
3851
+ static executeItem(editor: Editor, item: FloatingMenuItem): void;
3852
+ private editor;
3853
+ private onChange;
3854
+ private override;
3855
+ private transactionHandler;
3856
+ private _groups;
3857
+ private _flatItems;
3858
+ private _disabledMap;
3859
+ private _focusedIndex;
3860
+ constructor(editor: Editor, onChange: () => void, override?: FloatingMenuItemsOverride);
3861
+ get groups(): FloatingMenuGroup[];
3862
+ get flatItems(): FloatingMenuItem[];
3863
+ get disabledMap(): ReadonlyMap<string, boolean>;
3864
+ get focusedIndex(): number;
3865
+ /** True when keyboard focus is inside the menu (at least one item focused). */
3866
+ get isEntered(): boolean;
3867
+ get itemCount(): number;
3868
+ isDisabled(item: FloatingMenuItem): boolean;
3869
+ /**
3870
+ * Executes an item's command, then closes the menu keyboard focus.
3871
+ * Callers should refocus the editor after calling this.
3872
+ */
3873
+ execute(item: FloatingMenuItem): void;
3874
+ /**
3875
+ * Rebuilds items from the editor. Call when the editor's extensions
3876
+ * change (rare) or on explicit refresh. Notification is delegated to
3877
+ * `updateDisabledStates` which fires `onChange` only when a disabled
3878
+ * state flipped - wrappers that need to react to pure group-structure
3879
+ * changes do so by bumping their own render signal after constructing
3880
+ * / re-using the controller (see framework wrapper usage).
3881
+ */
3882
+ rebuild(): void;
3883
+ /** Enter keyboard focus on the menu (first item). Called by the plugin's keymap. */
3884
+ enterMenu(): number;
3885
+ /** Leave keyboard focus (Escape). Refocus of the editor is the caller's job. */
3886
+ leaveMenu(): void;
3887
+ /** ArrowDown - wrap to first at end. */
3888
+ next(): number;
3889
+ /** ArrowUp - wrap to last at start. */
3890
+ prev(): number;
3891
+ first(): number;
3892
+ last(): number;
3893
+ /** Set focused index directly (e.g. on pointer hover). */
3894
+ setFocusedIndex(index: number): void;
3895
+ /** Get focused item (or null). */
3896
+ focusedItem(): FloatingMenuItem | null;
3897
+ /** Get flat index of item by name (for wrappers binding roving tabindex). */
3898
+ getFlatIndex(name: string): number;
3899
+ /** Subscribes to editor transactions for disabled-state tracking. */
3900
+ subscribe(): void;
3901
+ /** Unsubscribes and clears internal state. */
3902
+ destroy(): void;
3903
+ /**
3904
+ * Groups items by `group` preserving insertion order, then sorts by
3905
+ * priority (higher first) within each group. Delegates to the shared
3906
+ * utility so `SlashCommand`'s renderer and this controller always
3907
+ * produce identical ordering.
3908
+ */
3909
+ private groupItems;
3910
+ /**
3911
+ * Updates disabled state for each item. Uses custom predicate when
3912
+ * provided; otherwise tries a dry-run against `editor.can()[command]`.
3913
+ * Only notifies on change to avoid noisy re-renders.
3914
+ */
3915
+ private updateDisabledStates;
3916
+ }
3917
+
3521
3918
  declare const defaultIcons: IconSet;
3522
3919
 
3523
3920
  /**
@@ -3619,25 +4016,12 @@ declare class Mark<Options = unknown, Storage = unknown> extends Extension<Optio
3619
4016
  */
3620
4017
  static create<O = unknown, S = unknown>(config: MarkConfig<O, S>): Mark<O, S>;
3621
4018
  /**
3622
- * Creates a new mark with merged options
3623
- * Original mark is not modified
3624
- *
3625
- * **Note:** Options are merged shallowly using object spread (`...`).
3626
- * Nested objects are replaced entirely, not deeply merged.
3627
- *
3628
- * @param options - Options to merge with existing options
3629
- * @returns New mark instance with merged options
4019
+ * Creates a new mark with merged options. Original mark is not modified.
4020
+ * Options merge shallowly (object spread); see {@link Extension.configure}
4021
+ * for the nested-object gotcha and a workaround.
3630
4022
  *
3631
4023
  * @example
3632
4024
  * const CustomBold = Bold.configure({ HTMLAttributes: { class: 'custom-bold' } });
3633
- *
3634
- * @example
3635
- * // Shallow merge behavior with nested objects:
3636
- * // Given: options = { HTMLAttributes: { class: 'a', id: 'b' } }
3637
- * // configure({ HTMLAttributes: { class: 'c' } })
3638
- * // Result: { HTMLAttributes: { class: 'c' } } — 'id' is lost!
3639
- * // To preserve nested values, spread manually:
3640
- * // configure({ HTMLAttributes: { ...original.options.HTMLAttributes, class: 'c' } })
3641
4025
  */
3642
4026
  configure(options: Partial<Options> & {
3643
4027
  isFormatting?: boolean;
@@ -4086,14 +4470,19 @@ interface OrderedListOptions {
4086
4470
  declare const OrderedList: Node<OrderedListOptions, unknown>;
4087
4471
 
4088
4472
  /**
4089
- * ListItem Node
4473
+ * Enter / Backspace state machine for listItem (cursor's parent must be a paragraph):
4090
4474
  *
4091
- * Individual list item that can contain paragraphs and nested blocks.
4092
- * Used by BulletList and OrderedList.
4475
+ * LABEL paragraph (childIndex 0):
4476
+ * Enter, empty -> liftListItem (exit empty bullet)
4477
+ * Enter, non-empty -> splitListItem (new sibling listItem)
4478
+ * Backspace, empty -> falls through to ListKeymap (liftListItem)
4093
4479
  *
4094
- * Keyboard shortcuts:
4095
- * - Enter: Split list item at cursor, or lift out of list if item is empty
4096
- * - Tab/Shift-Tab: Handled by ListKeymap extension (included via addExtensions)
4480
+ * CHILDREN-ZONE paragraph (childIndex > 0):
4481
+ * Enter, empty -> insertChildrenZoneSibling (accumulate inside the item)
4482
+ * Enter, non-empty -> splitBlock (both halves stay inside the same item)
4483
+ * Backspace, empty -> liftEmptyChildrenZoneParagraph (exit as top-level)
4484
+ *
4485
+ * Tab / Shift-Tab handled by the ListKeymap extension.
4097
4486
  */
4098
4487
 
4099
4488
  interface ListItemOptions {
@@ -4155,17 +4544,21 @@ interface TaskListOptions {
4155
4544
  declare const TaskList: Node<TaskListOptions, unknown>;
4156
4545
 
4157
4546
  /**
4158
- * TaskItem Node
4547
+ * Enter / Backspace state machine for taskItem (cursor's parent must be a paragraph):
4159
4548
  *
4160
- * Individual task/checkbox item that can contain paragraphs and nested blocks.
4161
- * Used by TaskList.
4549
+ * LABEL paragraph (childIndex 0):
4550
+ * Enter, empty -> splitListItem fall-through, then taskItem-in-listItem
4551
+ * promotion (nested orderedList > listItem > taskList > taskItem
4552
+ * context), else liftListItem.
4553
+ * Enter, non-empty -> splitListItem (new sibling taskItem)
4554
+ * Backspace, empty -> liftListItem (exit empty checkbox)
4162
4555
  *
4163
- * Keyboard shortcuts:
4164
- * - Enter: Split task item at cursor
4165
- * - Tab: Sink (indent) task item
4166
- * - Shift-Tab: Lift (outdent) task item
4167
- * - Backspace: Lift task item when at start of empty-ish item (converts to paragraph)
4168
- * - Mod-Enter: Toggle task checked state
4556
+ * CHILDREN-ZONE paragraph (childIndex > 0):
4557
+ * Enter, empty -> insertChildrenZoneSibling (accumulate inside the item)
4558
+ * Enter, non-empty -> splitBlock (both halves stay inside the same item)
4559
+ * Backspace, empty -> liftEmptyChildrenZoneParagraph (exit as top-level)
4560
+ *
4561
+ * Tab / Shift-Tab sink / lift the item; Mod-Enter toggles checked state.
4169
4562
  */
4170
4563
 
4171
4564
  declare module '@domternal/core' {
@@ -4376,9 +4769,6 @@ declare module '@domternal/core' {
4376
4769
  }
4377
4770
  }
4378
4771
 
4379
- /**
4380
- * Options for the Link mark
4381
- */
4382
4772
  interface LinkOptions {
4383
4773
  /**
4384
4774
  * HTML attributes to add to the rendered element
@@ -4882,6 +5272,61 @@ interface ListKeymapOptions {
4882
5272
  }
4883
5273
  declare const ListKeymap: Extension<ListKeymapOptions, unknown>;
4884
5274
 
5275
+ /**
5276
+ * Keyboard indent across list boundaries. `Tab` on a top-level block whose
5277
+ * previous sibling is a list moves the block INTO the last item as a nested
5278
+ * child; `Shift-Tab` reverses for a block sitting as the last child of the
5279
+ * last item.
5280
+ *
5281
+ * Trigger is intentionally narrow so ListKeymap retains in-list Tab/Shift-Tab
5282
+ * (`sinkListItem` / `liftListItem`). Schema is validated via `canReplaceWith`;
5283
+ * invalid placements are a clean no-op so the keymap chain falls through.
5284
+ *
5285
+ * Intentional design restrictions (NOT bugs to "fix"):
5286
+ * - Tab indents into the IMMEDIATE last item only, not recursively into a
5287
+ * deeper "deepest last item". Users get deeper nesting via repeated Tab
5288
+ * inside the now-nested context (then ListKeymap takes over).
5289
+ * - Tab only fires for cursors in TOP-LEVEL blocks (depth === 1). Cursors
5290
+ * inside other containers (blockquote, table cell) fall through.
5291
+ * - Shift-Tab only fires when the block is BOTH the last child of its
5292
+ * item AND the item is the last in its wrapper. Mid-position outdent
5293
+ * would require splitting the list item, which is deferred.
5294
+ *
5295
+ * Registration order: must come AFTER ListKeymap so this extension's keymap
5296
+ * runs FIRST and can defer to ListKeymap for in-list flows.
5297
+ */
5298
+
5299
+ /**
5300
+ * `Tab` handler: indent a top-level block as the last child of the
5301
+ * last item of the immediately-preceding list wrapper. Returns true
5302
+ * when the operation succeeded (and dispatched, if `dispatch` is
5303
+ * provided), false when any precondition fails so the keymap chain
5304
+ * can fall through to the next handler.
5305
+ */
5306
+ declare function indentBlockAsListChild(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
5307
+ /**
5308
+ * `Shift-Tab` handler: lift a non-label nested block out of its list
5309
+ * item to become a top-level sibling AFTER the list wrapper. Only
5310
+ * fires for the strict end-of-end case (last child of last item) so
5311
+ * the lift is deterministic and never needs to split a list item.
5312
+ *
5313
+ * Precondition table:
5314
+ * - cursor empty
5315
+ * - cursor textblock parent is NOT a paragraph (so we never
5316
+ * accidentally outdent the label; ListKeymap.Shift-Tab handles
5317
+ * in-label cases via liftListItem)
5318
+ * - the cursor's enclosing list item exists in the ancestry
5319
+ * - the cursor's containing block is a DIRECT child of the list
5320
+ * item (depth = listItemDepth + 1) - keeps the math local to
5321
+ * immediate li children rather than reaching deeper into nested
5322
+ * containers
5323
+ * - the block is the LAST child of the list item
5324
+ * - the list item is the LAST child of its wrapper
5325
+ * - the wrapper's parent accepts the block as a sibling (schema)
5326
+ */
5327
+ declare function outdentBlockFromListItem(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
5328
+ declare const ListIndent: Extension<unknown, unknown>;
5329
+
4885
5330
  /**
4886
5331
  * CharacterCount Extension
4887
5332
  *
@@ -5113,25 +5558,8 @@ interface LineHeightOptions {
5113
5558
  declare const LineHeight: Extension<LineHeightOptions, unknown>;
5114
5559
 
5115
5560
  /**
5116
- * UniqueID Extension
5117
- *
5118
- * Automatically assigns unique IDs to specified node types.
5119
- * Useful for collaborative editing, linking, and history tracking.
5120
- *
5121
- * @example
5122
- * ```ts
5123
- * import { UniqueID } from '@domternal/core';
5124
- *
5125
- * const editor = new Editor({
5126
- * extensions: [
5127
- * // ... other extensions
5128
- * UniqueID.configure({
5129
- * types: ['paragraph', 'heading'],
5130
- * attributeName: 'id',
5131
- * }),
5132
- * ],
5133
- * });
5134
- * ```
5561
+ * Canonical block-id system. Assigns ids to configured node types and is
5562
+ * read by TableOfContents and BlockContextMenu.
5135
5563
  */
5136
5564
 
5137
5565
  declare const uniqueIDPluginKey: PluginKey<any>;
@@ -5159,6 +5587,70 @@ interface UniqueIDOptions {
5159
5587
  }
5160
5588
  declare const UniqueID: Extension<UniqueIDOptions, unknown>;
5161
5589
 
5590
+ /**
5591
+ * Tints whole blocks via `bgColor` / `textColor` attributes rendered as
5592
+ * `data-bg-color` / `data-text-color`. Theme's `_block-colors.scss` maps
5593
+ * names to CSS custom properties with light/dark variants. `null` clears
5594
+ * the attribute; unknown palette names are rejected by commands.
5595
+ */
5596
+
5597
+ declare module '@domternal/core' {
5598
+ interface RawCommands {
5599
+ setBlockBgColor: CommandSpec<[color: string | null]>;
5600
+ setBlockTextColor: CommandSpec<[color: string | null]>;
5601
+ unsetBlockColors: CommandSpec;
5602
+ }
5603
+ }
5604
+ /**
5605
+ * Notion's public palette. Names are semantic (not tied to specific hex);
5606
+ * the stylesheet owns the actual colors so themes can customize them.
5607
+ * `'default'` is implicit - represented by `null` (no data attribute).
5608
+ */
5609
+ declare const DEFAULT_BLOCK_COLORS: string[];
5610
+ /**
5611
+ * Default set of block types that receive the color attributes. `codeBlock`
5612
+ * is intentionally excluded - `<pre><code>` already has its own background
5613
+ * that would conflict visually. Similarly, details-content blocks have
5614
+ * their own affordance. Callers can extend via `types` option.
5615
+ */
5616
+ declare const DEFAULT_BLOCK_COLOR_TYPES: string[];
5617
+ interface BlockColorOptions {
5618
+ /**
5619
+ * Node types that receive `bgColor` / `textColor` attributes.
5620
+ * @default DEFAULT_BLOCK_COLOR_TYPES
5621
+ */
5622
+ types: string[];
5623
+ /**
5624
+ * Palette used by the `setBlockBgColor` command. Commands reject values
5625
+ * outside this list (no-op + false return) so host apps can curate
5626
+ * what's selectable.
5627
+ * @default DEFAULT_BLOCK_COLORS
5628
+ */
5629
+ bgColors: string[];
5630
+ /**
5631
+ * Palette used by the `setBlockTextColor` command.
5632
+ * @default DEFAULT_BLOCK_COLORS
5633
+ */
5634
+ textColors: string[];
5635
+ }
5636
+ /**
5637
+ * Strip any inline `textStyle` marks inside [from, to] that carry the inline
5638
+ * counterpart of a block-level color attribute. Mutates the transaction in
5639
+ * place. This makes "last action wins": applying a block color erases
5640
+ * conflicting inline colors so the new block tint isn't visually hidden by
5641
+ * old span overrides.
5642
+ *
5643
+ * `which` may be 'text', 'bg', or 'both' - 'both' handles the unset case
5644
+ * in a single pass so the two strip operations don't step on each other's
5645
+ * replaced mark instances.
5646
+ *
5647
+ * Exported so `BlockContextMenu` (which writes node attrs directly via
5648
+ * `setNodeMarkup` instead of going through `setBlockBgColor` /
5649
+ * `setBlockTextColor`) shares the same conflict-stripping behavior.
5650
+ */
5651
+ declare function stripInlineColorConflicts(tr: Transaction, state: EditorState, from: number, to: number, which: 'text' | 'bg' | 'both'): void;
5652
+ declare const BlockColor: Extension<BlockColorOptions, unknown>;
5653
+
5162
5654
  /**
5163
5655
  * Selection Extension
5164
5656
  *
@@ -5237,7 +5729,7 @@ declare const Selection: Extension<SelectionOptions, SelectionStorage>;
5237
5729
  * clicks outside the editor (approach A - same as Google Docs / Notion).
5238
5730
  *
5239
5731
  * Toolbar and bubble-menu buttons call `event.preventDefault()` on
5240
- * `mousedown`, so they never trigger blur the selection stays intact
5732
+ * `mousedown`, so they never trigger blur - the selection stays intact
5241
5733
  * while the user interacts with editor UI.
5242
5734
  */
5243
5735
 
@@ -5358,6 +5850,8 @@ declare module '@domternal/core' {
5358
5850
  interface RawCommands {
5359
5851
  setTextColor: CommandSpec<[color: string]>;
5360
5852
  unsetTextColor: CommandSpec;
5853
+ setTextColorToken: CommandSpec<[token: string | null]>;
5854
+ unsetTextColorToken: CommandSpec;
5361
5855
  }
5362
5856
  }
5363
5857
  /**
@@ -5419,6 +5913,8 @@ declare module '@domternal/core' {
5419
5913
  toggleHighlight: CommandSpec<[attributes?: {
5420
5914
  color?: string;
5421
5915
  }]>;
5916
+ setBackgroundColorToken: CommandSpec<[token: string | null]>;
5917
+ unsetBackgroundColorToken: CommandSpec;
5422
5918
  }
5423
5919
  }
5424
5920
  /**
@@ -5534,6 +6030,59 @@ interface FontSizeOptions {
5534
6030
  }
5535
6031
  declare const FontSize: Extension<FontSizeOptions, unknown>;
5536
6032
 
6033
+ /**
6034
+ * NotionColorPicker Extension
6035
+ *
6036
+ * Provides toolbar registration for a Notion-style inline color picker that
6037
+ * drives the `colorToken` / `backgroundColorToken` attributes on the textStyle
6038
+ * mark (added by TextColor and Highlight).
6039
+ *
6040
+ * Owns:
6041
+ * - The shared named-token palette (default: 9 Notion-style colors).
6042
+ * - A bubble-menu-only toolbar item (`notionColor`) that emits a custom
6043
+ * `notionColorOpen` event when clicked. Framework wrappers listen for the
6044
+ * event and render the actual picker panel.
6045
+ *
6046
+ * The panel renders the full palette every time it opens; no MRU/recent
6047
+ * tracking is kept because all 18 swatches (9 text + 9 bg) fit in one view.
6048
+ *
6049
+ * @example
6050
+ * ```ts
6051
+ * import { TextStyle, TextColor, Highlight, NotionColorPicker } from '@domternal/core';
6052
+ *
6053
+ * const editor = new Editor({
6054
+ * extensions: [
6055
+ * TextStyle,
6056
+ * TextColor,
6057
+ * Highlight,
6058
+ * NotionColorPicker,
6059
+ * ],
6060
+ * });
6061
+ * ```
6062
+ */
6063
+
6064
+ /**
6065
+ * Default named tokens. Match the BlockColor palette + theme tokens
6066
+ * (`--dm-block-text-<token>`, `--dm-block-bg-<token>`) so light and dark
6067
+ * themes render the right hex without any extra CSS work.
6068
+ */
6069
+ declare const DEFAULT_NOTION_COLOR_PALETTE: readonly string[];
6070
+ interface NotionColorPickerOptions {
6071
+ /**
6072
+ * Named tokens shown in the picker. Each token must have matching
6073
+ * `--dm-block-text-<token>` and `--dm-block-bg-<token>` CSS variables in
6074
+ * the active theme; tokens with no theme support render as transparent
6075
+ * swatches.
6076
+ * @default DEFAULT_NOTION_COLOR_PALETTE
6077
+ */
6078
+ palette: readonly string[];
6079
+ }
6080
+ interface NotionColorPickerStorage {
6081
+ /** Whether the picker panel is currently open. UI sets this. */
6082
+ isOpen: boolean;
6083
+ }
6084
+ declare const NotionColorPicker: Extension<NotionColorPickerOptions, NotionColorPickerStorage>;
6085
+
5537
6086
  /**
5538
6087
  * ClearFormatting Extension
5539
6088
  *
@@ -5569,33 +6118,7 @@ interface LinkPopoverOptions {
5569
6118
  declare const LinkPopover: Extension<LinkPopoverOptions, unknown>;
5570
6119
 
5571
6120
  /**
5572
- * BubbleMenu Extension
5573
- *
5574
- * Shows a floating menu when text is selected in the editor.
5575
- * Useful for formatting toolbars that appear contextually.
5576
- *
5577
- * Styles are included automatically via `@domternal/theme` (`_bubble-menu.scss`).
5578
- *
5579
- * @example
5580
- * ```ts
5581
- * import { BubbleMenu } from '@domternal/core';
5582
- *
5583
- * // Create menu element
5584
- * const menuElement = document.getElementById('bubble-menu');
5585
- *
5586
- * const editor = new Editor({
5587
- * extensions: [
5588
- * // ... other extensions
5589
- * BubbleMenu.configure({
5590
- * element: menuElement,
5591
- * shouldShow: ({ editor, state, from, to }) => {
5592
- * // Only show for text selections
5593
- * return !state.selection.empty;
5594
- * },
5595
- * }),
5596
- * ],
5597
- * });
5598
- * ```
6121
+ * Floating menu shown when text is selected. Contextual formatting toolbar.
5599
6122
  */
5600
6123
 
5601
6124
  declare const bubbleMenuPluginKey: PluginKey<any>;
@@ -5649,76 +6172,6 @@ interface CreateBubbleMenuPluginOptions {
5649
6172
  declare function createBubbleMenuPlugin(options: CreateBubbleMenuPluginOptions): Plugin;
5650
6173
  declare const BubbleMenu: Extension<BubbleMenuOptions, unknown>;
5651
6174
 
5652
- /**
5653
- * FloatingMenu Extension
5654
- *
5655
- * Shows a floating menu when the cursor is in an empty paragraph.
5656
- * Useful for showing block-level insertion options.
5657
- *
5658
- * @example
5659
- * ```ts
5660
- * import { FloatingMenu } from '@domternal/core';
5661
- *
5662
- * // Create menu element
5663
- * const menuElement = document.getElementById('floating-menu');
5664
- *
5665
- * const editor = new Editor({
5666
- * extensions: [
5667
- * // ... other extensions
5668
- * FloatingMenu.configure({
5669
- * element: menuElement,
5670
- * shouldShow: ({ editor, state }) => {
5671
- * const { $from, empty } = state.selection;
5672
- * // Show in empty paragraphs
5673
- * return empty &&
5674
- * $from.parent.type.name === 'paragraph' &&
5675
- * $from.parent.content.size === 0;
5676
- * },
5677
- * }),
5678
- * ],
5679
- * });
5680
- * ```
5681
- *
5682
- * Styles are included automatically via `@domternal/theme` (`_floating-menu.scss`).
5683
- */
5684
-
5685
- declare const floatingMenuPluginKey: PluginKey<any>;
5686
- interface FloatingMenuOptions {
5687
- /**
5688
- * The HTML element that contains the menu.
5689
- * Must be provided by the user.
5690
- */
5691
- element: HTMLElement | null;
5692
- /**
5693
- * Function to determine if the menu should be shown.
5694
- * By default, shows when the cursor is in an empty paragraph.
5695
- */
5696
- shouldShow: (props: {
5697
- editor: Editor;
5698
- view: EditorView;
5699
- state: EditorState;
5700
- }) => boolean;
5701
- /**
5702
- * Offset in pixels from the cursor position.
5703
- * @default 0
5704
- */
5705
- offset: number;
5706
- }
5707
- interface CreateFloatingMenuPluginOptions {
5708
- pluginKey: PluginKey;
5709
- editor: Editor;
5710
- element: HTMLElement;
5711
- shouldShow?: FloatingMenuOptions['shouldShow'];
5712
- offset?: number;
5713
- }
5714
- /**
5715
- * Creates a standalone FloatingMenu ProseMirror plugin.
5716
- * Can be used by framework wrappers (Angular, React, Vue) to create the plugin
5717
- * independently of the extension system.
5718
- */
5719
- declare function createFloatingMenuPlugin(options: CreateFloatingMenuPluginOptions): Plugin;
5720
- declare const FloatingMenu: Extension<FloatingMenuOptions, unknown>;
5721
-
5722
6175
  /**
5723
6176
  * StarterKit Extension
5724
6177
  *
@@ -5827,6 +6280,13 @@ interface StarterKitOptions {
5827
6280
  * Set to false to disable the ListKeymap extension, or pass options to configure it.
5828
6281
  */
5829
6282
  listKeymap?: false | Partial<ListKeymapOptions>;
6283
+ /**
6284
+ * Set to false to disable the ListIndent extension. ListIndent adds
6285
+ * Tab / Shift-Tab keymap that indents a top-level block under the
6286
+ * previous list (and outdents back). Registered AFTER ListKeymap so
6287
+ * the in-list-item Tab/Shift-Tab keep priority.
6288
+ */
6289
+ listIndent?: false;
5830
6290
  /**
5831
6291
  * Set to false to disable the LinkPopover extension, or pass options to configure it.
5832
6292
  */
@@ -5846,4 +6306,4 @@ declare const StarterKit: Extension<StarterKitOptions, unknown>;
5846
6306
  */
5847
6307
  declare const VERSION = "0.1.0";
5848
6308
 
5849
- export { type AnyExtension, type AnyExtensionConfig, type AttributeSpec, type AttributeSpecs, type AutolinkPluginOptions, BaseKeymap, type BaseKeymapOptions, Blockquote, type BlockquoteOptions, Bold, type BoldOptions, BubbleMenu, type BubbleMenuOptions, type BuildCommandPropsOptions, BulletList, type BulletListOptions, type CanChainedCommands, CanChecker, type CanCheckerEditor, type CanCheckerOptions, type CanCommands, ChainBuilder, type ChainBuilderEditor, type ChainBuilderOptions, type ChainFailure, type ChainedCommands, CharacterCount, type CharacterCountOptions, type CharacterCountStorage, type ClearContentOptions, ClearFormatting, Code, CodeBlock, type CodeBlockOptions, type CodeOptions, type Command, type CommandEditor, CommandManager, type CommandManagerEditor, type CommandMap, type CommandProps, type CommandPropsEditor, type CommandSpec$1 as CommandSpec, type Content, type ContentErrorProps, type CreateBubbleMenuPluginOptions, type CreateDocumentOptions, type CreateEventProps, type CreateFloatingMenuPluginOptions, DEFAULT_HIGHLIGHT_COLORS, DEFAULT_TEXT_COLORS, type DeleteEventProps, Document$1 as Document, type DropEventProps, Dropcursor, type DropcursorOptions, Editor, type EditorEventName, type EditorEvents, type EditorInstance, type EditorOptions, EventEmitter, Extension, type ExtensionConfig, type ExtensionEditor, ExtensionManager, type ExtensionManagerEditor, type ExtensionManagerOptions, type FindChildResult, type FindParentNodeResult, FloatingMenu, type FloatingMenuOptions, Focus, type FocusEventProps, type FocusOptions, type FocusPosition, FontFamily, type FontFamilyOptions, FontSize, type FontSizeOptions, Gapcursor, type GenerateHTMLOptions, type GenerateJSONOptions, type GenerateTextOptions, type GlobalAttributeSpec, type GlobalAttributes, HardBreak, type HardBreakOptions, Heading, type HeadingOptions, Highlight, type HighlightOptions, History, type HistoryOptions, HorizontalRule, type HorizontalRuleOptions, type IconSet, type InlineStyleOverrides, InvisibleChars, type InvisibleCharsOptions, type InvisibleCharsStorage, type IsNodeEmptyOptions, type IsValidUrlOptions, Italic, type ItalicOptions, type JSONAttribute, type JSONContent, type JSONMark, type KeyboardShortcutCommand, LineHeight, type LineHeightOptions, Link, type LinkAttributes, type LinkClickPluginOptions, type LinkExitPluginOptions, type LinkOptions, type LinkPastePluginOptions, LinkPopover, type LinkPopoverOptions, ListItem, type ListItemOptions, ListKeymap, type ListKeymapOptions, Mark, type MarkConfig, type MarkInputRuleOptions, type MarkParseRule, type MarkRange, type MarkRenderHTMLProps, type MountEventProps, Node, type NodeConfig, type NodeInputRuleOptions, type NodeParseRule, type NodeRenderHTMLProps, type NodeViewContext, OrderedList, type OrderedListOptions, Paragraph, type ParagraphOptions, type PasteEventProps, Placeholder, type PlaceholderOptions, type PositionFloatingOptions, type Range, type RawCommands, Selection, SelectionDecoration, type SelectionDecorationOptions, type SelectionOptions, type SelectionStorage, type SetContentOptions, type SingleCommands, StarterKit, type StarterKitOptions, Strike, type StrikeOptions, Subscript, type SubscriptOptions, Superscript, type SuperscriptOptions, TaskItem, type TaskItemOptions, TaskList, type TaskListOptions, Text, TextAlign, type TextAlignOptions, TextColor, type TextColorOptions, type TextInputRuleOptions, TextStyle, type TextStyleOptions, type TextblockTypeInputRuleOptions, type ToolbarButton, ToolbarController, type ToolbarControllerEditor, type ToolbarDropdown, type ToolbarGroup, type ToolbarItem, type ToolbarLayoutDropdown, type ToolbarLayoutEntry, type ToolbarSeparator, TrailingNode, type TrailingNodeOptions, type TransactionEventProps, Typography, type TypographyOptions, Underline, type UnderlineOptions, UniqueID, type UniqueIDOptions, VERSION, type WrappingInputRuleOptions, applyInlineStyles, autolinkPlugin, autolinkPluginKey, blur, bubbleMenuPluginKey, buildCommandProps, builtInCommands, callOrReturn, characterCountPluginKey, clearContent, createAccumulatingDispatch, createBubbleMenuPlugin, createCanChecker, createChainBuilder, createDocument, createFloatingMenuPlugin, defaultBlockAt, defaultIcons, deleteSelection, findChildren, findParentNode, floatingMenuPluginKey, focus, focusPluginKey, generateHTML, generateJSON, generateText, getMarkRange, inlineStyles, insertContent, insertText, invisibleCharsPluginKey, isDocumentEmpty, isNodeEmpty, isValidUrl, lift, linkClickPlugin, linkClickPluginKey, linkExitPlugin, linkExitPluginKey, linkPastePlugin, linkPastePluginKey, markInputRule, markInputRulePatterns, nodeInputRule, placeholderPluginKey, positionFloating, positionFloatingOnce, resetAttributes, selectAll, selectNodeBackward, selectionDecorationPluginKey, setBlockType, setContent, setMark, textInputRule, textblockTypeInputRule, toggleBlockType, toggleList, toggleMark, toggleWrap, uniqueIDPluginKey, unsetAllMarks, unsetMark, updateAttributes, wrapIn, wrappingInputRule };
6309
+ export { type AnyExtension, type AnyExtensionConfig, type AttributeSpec, type AttributeSpecs, type AutolinkPluginOptions, BaseKeymap, type BaseKeymapOptions, BlockColor, type BlockColorOptions, Blockquote, type BlockquoteOptions, Bold, type BoldOptions, BubbleMenu, type BubbleMenuOptions, type BuildCommandPropsOptions, BulletList, type BulletListOptions, type CanChainedCommands, CanChecker, type CanCheckerEditor, type CanCheckerOptions, type CanCommands, ChainBuilder, type ChainBuilderEditor, type ChainBuilderOptions, type ChainFailure, type ChainedCommands, CharacterCount, type CharacterCountOptions, type CharacterCountStorage, type ClearContentOptions, ClearFormatting, Code, CodeBlock, type CodeBlockOptions, type CodeOptions, type Command, type CommandEditor, CommandManager, type CommandManagerEditor, type CommandMap, type CommandProps, type CommandPropsEditor, type CommandSpec$1 as CommandSpec, type Content, type ContentErrorProps, type CreateBubbleMenuPluginOptions, type CreateDocumentOptions, type CreateEventProps, DEFAULT_BLOCK_COLORS, DEFAULT_BLOCK_COLOR_TYPES, DEFAULT_HIGHLIGHT_COLORS, DEFAULT_NOTION_COLOR_PALETTE, DEFAULT_TEXT_COLORS, type DeleteEventProps, Document$1 as Document, type DropEventProps, Dropcursor, type DropcursorOptions, Editor, type EditorEventName, type EditorEvents, type EditorInstance, type EditorOptions, EventEmitter, Extension, type ExtensionConfig, type ExtensionEditor, ExtensionManager, type ExtensionManagerEditor, type ExtensionManagerOptions, FLOATING_MENU_NO_FOCUS, type FindChildResult, type FindParentNodeResult, FloatingMenuController, type FloatingMenuGroup, type FloatingMenuItem, type FloatingMenuItemsOverride, Focus, type FocusEventProps, type FocusOptions, type FocusPosition, FontFamily, type FontFamilyOptions, FontSize, type FontSizeOptions, Gapcursor, type GenerateHTMLOptions, type GenerateJSONOptions, type GenerateTextOptions, type GlobalAttributeSpec, type GlobalAttributes, HardBreak, type HardBreakOptions, Heading, type HeadingOptions, Highlight, type HighlightOptions, History, type HistoryOptions, HorizontalRule, type HorizontalRuleOptions, type IconSet, type InlineStyleOverrides, type InsertAsListItemChildArgs, type InsertAsListItemChildResult, InvisibleChars, type InvisibleCharsOptions, type InvisibleCharsStorage, type IsNodeEmptyOptions, type IsValidUrlOptions, Italic, type ItalicOptions, type JSONAttribute, type JSONContent, type JSONMark, type KeyboardShortcutCommand, LIST_ITEM_TYPE_NAMES, LineHeight, type LineHeightOptions, Link, type LinkAttributes, type LinkClickPluginOptions, type LinkExitPluginOptions, type LinkOptions, type LinkPastePluginOptions, LinkPopover, type LinkPopoverOptions, ListIndent, ListItem, type ListItemCursorContext, type ListItemOptions, ListKeymap, type ListKeymapOptions, Mark, type MarkConfig, type MarkInputRuleOptions, type MarkParseRule, type MarkRange, type MarkRenderHTMLProps, type MountEventProps, Node, type NodeConfig, type NodeInputRuleOptions, type NodeParseRule, type NodeRenderHTMLProps, type NodeViewContext, NotionColorPicker, type NotionColorPickerOptions, type NotionColorPickerStorage, OrderedList, type OrderedListOptions, Paragraph, type ParagraphOptions, type PasteEventProps, Placeholder, type PlaceholderOptions, type PositionFloatingOptions, type Range, type RawCommands, Selection, SelectionDecoration, type SelectionDecorationOptions, type SelectionOptions, type SelectionStorage, type SetContentOptions, type SingleCommands, type SplitListForInsertRange, StarterKit, type StarterKitOptions, Strike, type StrikeOptions, Subscript, type SubscriptOptions, Superscript, type SuperscriptOptions, TaskItem, type TaskItemOptions, TaskList, type TaskListOptions, Text, TextAlign, type TextAlignOptions, TextColor, type TextColorOptions, type TextInputRuleOptions, TextStyle, type TextStyleOptions, type TextblockTypeInputRuleOptions, type ToolbarButton, ToolbarController, type ToolbarControllerEditor, type ToolbarDropdown, type ToolbarGroup, type ToolbarItem, type ToolbarLayoutDropdown, type ToolbarLayoutEntry, type ToolbarSeparator, TrailingNode, type TrailingNodeOptions, type TransactionEventProps, Typography, type TypographyOptions, Underline, type UnderlineOptions, UniqueID, type UniqueIDOptions, VERSION, type WrappingInputRuleOptions, applyInlineStyles, autolinkPlugin, autolinkPluginKey, blur, bubbleMenuPluginKey, buildCommandProps, builtInCommands, callOrReturn, characterCountPluginKey, clearContent, createAccumulatingDispatch, createBubbleMenuPlugin, createCanChecker, createChainBuilder, createDocument, defaultBlockAt, defaultBubbleContexts, defaultIcons, deleteSelection, findChildren, findListItemAncestorDepth, findParentNode, focus, focusPluginKey, generateHTML, generateJSON, generateText, getListItemCursorContext, getMarkRange, groupFloatingMenuItems, indentBlockAsListChild, inlineStyles, insertAsListItemChild, insertChildrenZoneSibling, insertContent, insertText, invisibleCharsPluginKey, isDocumentEmpty, isInListItemLabel, isInsideListItem, isNodeEmpty, isValidUrl, lift, liftCurrentListItem, liftEmptyChildrenZoneParagraph, linkClickPlugin, linkClickPluginKey, linkExitPlugin, linkExitPluginKey, linkPastePlugin, linkPastePluginKey, markInputRule, markInputRulePatterns, nodeInputRule, outdentBlockFromListItem, placeholderPluginKey, positionFloating, positionFloatingOnce, resetAttributes, selectAll, selectNodeBackward, selectionDecorationPluginKey, setBlockType, setContent, setMark, splitListForInsert, stripInlineColorConflicts, textInputRule, textblockTypeInputRule, toggleBlockType, toggleList, toggleMark, toggleWrap, uniqueIDPluginKey, unsetAllMarks, unsetMark, updateAttributes, wrapIn, wrappingInputRule, writeToClipboard };