@domternal/core 0.2.1 → 0.4.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.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Node as Node$1, DOMOutputSpec, NodeType, NodeSpec, Mark as Mark$1, MarkType, MarkSpec, Schema, Attrs, DOMParser, ResolvedPos, ContentMatch } from '@domternal/pm/model';
1
+ import { Schema, Node as Node$1, DOMOutputSpec, NodeType, Mark as Mark$1, MarkType, Attrs, DOMParser, ResolvedPos, 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';
@@ -175,6 +175,136 @@ interface EditorEvents {
175
175
  */
176
176
  type EditorEventName = keyof EditorEvents;
177
177
 
178
+ /**
179
+ * Type-erased base for Extension, Node, and Mark.
180
+ *
181
+ * The Extension class has generic Options/Storage parameters, and methods
182
+ * like `configure(options: Partial<Options>)` that make Options contravariant.
183
+ * This means `Extension<MyOptions>` is not assignable to `Extension<unknown>`.
184
+ *
185
+ * AnyExtension uses an interface with only the properties that ExtensionManager
186
+ * and the editor need at runtime, avoiding the generic variance issue. Every
187
+ * Extension<O, S>, Node<O, S>, and Mark<O, S> structurally satisfies this
188
+ * interface regardless of their generic parameters.
189
+ */
190
+ interface AnyExtension {
191
+ readonly type: 'extension' | 'node' | 'mark';
192
+ readonly name: string;
193
+ readonly options: unknown;
194
+ storage: unknown;
195
+ readonly config: unknown;
196
+ editor: unknown;
197
+ parent?: ((...args: unknown[]) => unknown) | undefined;
198
+ }
199
+ /**
200
+ * Autofocus options for the editor
201
+ * - true: Focus at the end
202
+ * - false: Don't autofocus
203
+ * - null: Explicitly no autofocus
204
+ * - 'start': Focus at the beginning
205
+ * - 'end': Focus at the end
206
+ * - 'all': Select all content
207
+ * - number: Focus at specific position
208
+ */
209
+ type FocusPosition = boolean | 'start' | 'end' | 'all' | number | null;
210
+ /**
211
+ * Configuration options for creating an Editor instance
212
+ */
213
+ interface EditorOptions {
214
+ /**
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)
219
+ *
220
+ * The schema must contain at least 'doc' and 'text' nodes.
221
+ */
222
+ schema?: Schema;
223
+ /**
224
+ * HTML element to mount the editor
225
+ * If not provided, creates a detached div (useful for testing/headless mode)
226
+ */
227
+ element?: HTMLElement | null;
228
+ /**
229
+ * Initial content (JSON or HTML string)
230
+ * @default null (empty document)
231
+ */
232
+ content?: Content;
233
+ /**
234
+ * Extensions to load
235
+ * @default []
236
+ */
237
+ extensions?: AnyExtension[];
238
+ /**
239
+ * Whether the editor is editable
240
+ * @default true
241
+ */
242
+ editable?: boolean;
243
+ /**
244
+ * Autofocus behavior on mount
245
+ * @default false
246
+ */
247
+ autofocus?: FocusPosition;
248
+ /**
249
+ * Transform function applied to clipboard HTML on copy/cut.
250
+ * Use with `inlineStyles` from core to auto-apply inline CSS on copy:
251
+ *
252
+ * @example
253
+ * ```ts
254
+ * import { inlineStyles } from '@domternal/core';
255
+ * new Editor({ clipboardHTMLTransform: inlineStyles });
256
+ * ```
257
+ */
258
+ clipboardHTMLTransform?: (html: string) => string;
259
+ /**
260
+ * Called before the editor is created
261
+ * Can be used to modify options
262
+ */
263
+ onBeforeCreate?: (props: CreateEventProps) => void;
264
+ /**
265
+ * Called when the editor is created and ready
266
+ */
267
+ onCreate?: (props: CreateEventProps) => void;
268
+ /**
269
+ * Called when editor view is mounted to DOM
270
+ */
271
+ onMount?: (props: MountEventProps) => void;
272
+ /**
273
+ * Called when the document content changes
274
+ */
275
+ onUpdate?: (props: TransactionEventProps) => void;
276
+ /**
277
+ * Called when selection changes (without content change)
278
+ */
279
+ onSelectionUpdate?: (props: TransactionEventProps) => void;
280
+ /**
281
+ * Called on every transaction
282
+ */
283
+ onTransaction?: (props: TransactionEventProps) => void;
284
+ /**
285
+ * Called when editor receives focus
286
+ */
287
+ onFocus?: (props: FocusEventProps) => void;
288
+ /**
289
+ * Called when editor loses focus
290
+ */
291
+ onBlur?: (props: FocusEventProps) => void;
292
+ /**
293
+ * Called before editor is destroyed
294
+ */
295
+ onDestroy?: () => void;
296
+ /**
297
+ * Called when content doesn't match schema (AD-8)
298
+ * Use this to handle content validation errors gracefully
299
+ */
300
+ onContentError?: (props: ContentErrorProps) => void;
301
+ /**
302
+ * Called when an extension throws an error (2.7: Extension Error Isolation)
303
+ * Allows graceful error handling without crashing the editor
304
+ */
305
+ onError?: (props: ErrorEventProps$1) => void;
306
+ }
307
+
178
308
  /**
179
309
  * Editor instance type (forward declaration)
180
310
  */
@@ -739,226 +869,74 @@ interface ExtensionConfigBase<Options = unknown, Storage = unknown> {
739
869
  type ExtensionConfig<Options = unknown, Storage = unknown> = ExtensionConfigBase<Options, Storage> & ThisType<ExtensionContext<Options, Storage>>;
740
870
 
741
871
  /**
742
- * Extension - Base class for all extensions
743
- *
744
- * Extensions provide functionality without contributing to the schema.
745
- * For schema contributions, use Node (for block/inline nodes) or Mark (for inline formatting).
872
+ * Attribute specification for Node and Mark extensions
746
873
  *
747
- * Three-tier model:
748
- * - Extension (type: 'extension') Pure functionality (History, Placeholder, etc.)
749
- * - Node (type: 'node') → Schema nodes (Paragraph, Heading, etc.)
750
- * - Mark (type: 'mark') → Schema marks (Bold, Italic, etc.)
874
+ * Used by addAttributes() to define node/mark attributes
875
+ * with parsing, rendering, and validation rules.
751
876
  *
752
877
  * @example
753
- * const History = Extension.create({
754
- * name: 'history',
755
- * addOptions() {
756
- * return { depth: 100 };
757
- * },
758
- * addKeyboardShortcuts() {
759
- * return {
760
- * 'Mod-z': () => this.editor.commands.undo(),
761
- * 'Mod-Shift-z': () => this.editor.commands.redo(),
762
- * };
763
- * },
764
- * });
765
- */
766
-
767
- /**
768
- * Editor interface for Extension
769
- * Forward declaration to avoid circular dependency
878
+ * addAttributes() {
879
+ * return {
880
+ * level: {
881
+ * default: 1,
882
+ * parseHTML: (element) => parseInt(element.tagName.charAt(1), 10),
883
+ * renderHTML: (attributes) => ({ 'data-level': attributes.level }),
884
+ * },
885
+ * };
886
+ * }
770
887
  */
771
- interface ExtensionEditorInterface {
772
- readonly state: EditorState;
773
- readonly view: EditorView;
774
- readonly schema: unknown;
775
- readonly commands: SingleCommands;
776
- }
777
888
  /**
778
- * Base class for all extensions
779
- *
780
- * @typeParam Options - Extension options type
781
- * @typeParam Storage - Extension storage type
889
+ * Specification for a single attribute
782
890
  */
783
- declare class Extension<Options = unknown, Storage = unknown> {
784
- /**
785
- * Extension type identifier
786
- * Used to distinguish between Extension, Node, and Mark
787
- * Subclasses override this to 'node' or 'mark'
788
- */
789
- readonly type: 'extension' | 'node' | 'mark';
790
- /**
791
- * Unique extension name
792
- */
793
- readonly name: string;
794
- /**
795
- * Extension options (immutable after creation)
796
- */
797
- readonly options: Options;
798
- /**
799
- * Extension storage (mutable state)
800
- * Accessible via editor.storage[extensionName]
801
- */
802
- storage: Storage;
891
+ interface AttributeSpec {
803
892
  /**
804
- * The original configuration object
893
+ * Default value for the attribute
894
+ * Used when the attribute is not explicitly set
805
895
  */
806
- readonly config: ExtensionConfig<Options, Storage>;
896
+ default?: unknown;
807
897
  /**
808
- * Editor instance (set by ExtensionManager after creation)
809
- * null until ExtensionManager binds it
898
+ * Whether this attribute is rendered to the DOM
899
+ * @default true
810
900
  */
811
- editor: ExtensionEditorInterface | null;
901
+ rendered?: boolean;
812
902
  /**
813
- * Reference to the parent config method when using extend().
814
- * Set temporarily during config method execution so overridden
815
- * methods can call `this.parent?.()` to invoke the original.
903
+ * Keep this attribute when splitting the node
904
+ * For example, heading level should be kept when pressing Enter
905
+ * @default true
816
906
  */
817
- parent?: ((...args: unknown[]) => unknown) | undefined;
907
+ keepOnSplit?: boolean;
818
908
  /**
819
- * Protected constructor - use Extension.create() instead
909
+ * Validate attribute value (ProseMirror 1.22.0+).
910
+ * When a string, a `|`-separated list of primitive types
911
+ * (`"number"`, `"string"`, `"boolean"`, `"null"`, `"undefined"`).
912
+ * When a function, it should throw if the value is invalid.
913
+ *
914
+ * @example
915
+ * validate: 'number'
916
+ * validate: 'string|null'
917
+ * validate: (value) => { if (typeof value !== 'number') throw new Error('expected number'); }
820
918
  */
821
- protected constructor(config: ExtensionConfig<Options, Storage>);
919
+ validate?: string | ((value: unknown) => void);
822
920
  /**
823
- * Creates a new extension instance
921
+ * Parse attribute value from HTML element
922
+ * Called during HTML parsing to extract attribute value
824
923
  *
825
- * @param config - Extension configuration
826
- * @returns New extension instance
924
+ * @param element - The HTML element being parsed
925
+ * @returns The attribute value
827
926
  *
828
927
  * @example
829
- * const MyExtension = Extension.create({
830
- * name: 'myExtension',
831
- * addOptions() {
832
- * return { enabled: true };
833
- * },
834
- * });
928
+ * parseHTML: (element) => element.getAttribute('data-level')
835
929
  */
836
- static create<O = unknown, S = unknown>(config: ExtensionConfig<O, S>): Extension<O, S>;
930
+ parseHTML?: (element: HTMLElement) => unknown;
837
931
  /**
838
- * Creates a new extension with merged options
839
- * Original extension is not modified
840
- *
841
- * **Note:** Options are merged shallowly using object spread (`...`).
842
- * Nested objects are replaced entirely, not deeply merged.
843
- *
844
- * @param options - Options to merge with existing options
845
- * @returns New extension instance with merged options
932
+ * Render attribute to HTML attributes object
933
+ * Called during HTML serialization
846
934
  *
847
- * @example
848
- * const configured = MyExtension.configure({ enabled: false });
935
+ * @param attributes - All attributes of the node/mark
936
+ * @returns HTML attributes object or null to skip
849
937
  *
850
938
  * @example
851
- * // Shallow merge behavior with nested objects:
852
- * // Given: options = { nested: { a: 1, b: 2 } }
853
- * // configure({ nested: { b: 3 } })
854
- * // Result: { nested: { b: 3 } } — 'a' is lost!
855
- * // To preserve nested values, spread manually:
856
- * // configure({ nested: { ...original.options.nested, b: 3 } })
857
- */
858
- configure(options: Partial<Options>): Extension<Options, Storage>;
859
- /**
860
- * Creates a new extension with extended configuration
861
- * Original extension is not modified
862
- *
863
- * **Note:** Config is merged shallowly using object spread (`...`).
864
- * Config properties (like `addCommands`, `addKeyboardShortcuts`) are
865
- * replaced entirely, not combined with the base extension's config.
866
- *
867
- * @param extendedConfig - Configuration to extend/override
868
- * @returns New extension instance with extended config
869
- *
870
- * @example
871
- * const Extended = MyExtension.extend({
872
- * name: 'extendedExtension',
873
- * addCommands() {
874
- * return { customCommand: () => ({ tr }) => true };
875
- * },
876
- * });
877
- *
878
- * @example
879
- * // To preserve base extension's commands while adding new ones:
880
- * const Extended = BaseExtension.extend({
881
- * addCommands() {
882
- * const baseCommands = BaseExtension.config.addCommands?.call(this) ?? {};
883
- * return {
884
- * ...baseCommands,
885
- * newCommand: () => ({ tr }) => true,
886
- * };
887
- * },
888
- * });
889
- */
890
- extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig: Partial<ExtensionConfigBase<ExtendedOptions, ExtendedStorage>> & ThisType<ExtensionContext<ExtendedOptions, ExtendedStorage>>): Extension<ExtendedOptions, ExtendedStorage>;
891
- }
892
-
893
- /**
894
- * Attribute specification for Node and Mark extensions
895
- *
896
- * Used by addAttributes() to define node/mark attributes
897
- * with parsing, rendering, and validation rules.
898
- *
899
- * @example
900
- * addAttributes() {
901
- * return {
902
- * level: {
903
- * default: 1,
904
- * parseHTML: (element) => parseInt(element.tagName.charAt(1), 10),
905
- * renderHTML: (attributes) => ({ 'data-level': attributes.level }),
906
- * },
907
- * };
908
- * }
909
- */
910
- /**
911
- * Specification for a single attribute
912
- */
913
- interface AttributeSpec {
914
- /**
915
- * Default value for the attribute
916
- * Used when the attribute is not explicitly set
917
- */
918
- default?: unknown;
919
- /**
920
- * Whether this attribute is rendered to the DOM
921
- * @default true
922
- */
923
- rendered?: boolean;
924
- /**
925
- * Keep this attribute when splitting the node
926
- * For example, heading level should be kept when pressing Enter
927
- * @default true
928
- */
929
- keepOnSplit?: boolean;
930
- /**
931
- * Validate attribute value (ProseMirror 1.22.0+).
932
- * When a string, a `|`-separated list of primitive types
933
- * (`"number"`, `"string"`, `"boolean"`, `"null"`, `"undefined"`).
934
- * When a function, it should throw if the value is invalid.
935
- *
936
- * @example
937
- * validate: 'number'
938
- * validate: 'string|null'
939
- * validate: (value) => { if (typeof value !== 'number') throw new Error('expected number'); }
940
- */
941
- validate?: string | ((value: unknown) => void);
942
- /**
943
- * Parse attribute value from HTML element
944
- * Called during HTML parsing to extract attribute value
945
- *
946
- * @param element - The HTML element being parsed
947
- * @returns The attribute value
948
- *
949
- * @example
950
- * parseHTML: (element) => element.getAttribute('data-level')
951
- */
952
- parseHTML?: (element: HTMLElement) => unknown;
953
- /**
954
- * Render attribute to HTML attributes object
955
- * Called during HTML serialization
956
- *
957
- * @param attributes - All attributes of the node/mark
958
- * @returns HTML attributes object or null to skip
959
- *
960
- * @example
961
- * renderHTML: (attributes) => ({ 'data-level': attributes.level })
939
+ * renderHTML: (attributes) => ({ 'data-level': attributes.level })
962
940
  */
963
941
  renderHTML?: (attributes: Record<string, unknown>) => Record<string, string | number | boolean | null | undefined> | null;
964
942
  }
@@ -1226,157 +1204,6 @@ interface NodeSchemaProperties {
1226
1204
  */
1227
1205
  type NodeConfig<Options = unknown, Storage = unknown> = ExtensionConfigBase<Options, Storage> & NodeSchemaProperties & ThisType<NodeContext<Options, Storage>>;
1228
1206
 
1229
- /**
1230
- * Node - Base class for node extensions
1231
- *
1232
- * Nodes define document structure elements that contribute to the schema.
1233
- * Examples: Paragraph, Heading, List, Image, etc.
1234
- *
1235
- * Three-tier model:
1236
- * - Extension (type: 'extension') → Pure functionality (History, Placeholder, etc.)
1237
- * - Node (type: 'node') → Schema nodes (Paragraph, Heading, etc.)
1238
- * - Mark (type: 'mark') → Schema marks (Bold, Italic, etc.)
1239
- *
1240
- * @example
1241
- * const Paragraph = Node.create({
1242
- * name: 'paragraph',
1243
- * group: 'block',
1244
- * content: 'inline*',
1245
- * parseHTML() {
1246
- * return [{ tag: 'p' }];
1247
- * },
1248
- * renderHTML({ HTMLAttributes }) {
1249
- * return ['p', HTMLAttributes, 0];
1250
- * },
1251
- * });
1252
- */
1253
-
1254
- /**
1255
- * Extended editor interface for Node
1256
- * Includes schema access for NodeType getter
1257
- */
1258
- interface NodeEditorInterface extends ExtensionEditorInterface {
1259
- readonly schema: {
1260
- nodes: Record<string, NodeType>;
1261
- };
1262
- }
1263
- /**
1264
- * Base class for node extensions
1265
- *
1266
- * @typeParam Options - Node options type
1267
- * @typeParam Storage - Node storage type
1268
- */
1269
- declare class Node<Options = unknown, Storage = unknown> extends Extension<Options, Storage> {
1270
- /**
1271
- * Node type identifier
1272
- * Distinguishes nodes from extensions and marks
1273
- */
1274
- readonly type: "node";
1275
- /**
1276
- * The original configuration object
1277
- * Typed as NodeConfig for node-specific properties
1278
- */
1279
- readonly config: NodeConfig<Options, Storage>;
1280
- /**
1281
- * Editor instance with schema access
1282
- * null until set by ExtensionManager
1283
- */
1284
- editor: NodeEditorInterface | null;
1285
- /**
1286
- * Protected constructor - use Node.create() instead
1287
- */
1288
- protected constructor(config: NodeConfig<Options, Storage>);
1289
- /**
1290
- * Get the ProseMirror NodeType from schema
1291
- *
1292
- * This is a lazy getter because schema doesn't exist at node creation time.
1293
- * Schema is built FROM nodes by ExtensionManager.
1294
- *
1295
- * Returns null if editor is not yet initialized.
1296
- * Always check editor is set before using nodeType.
1297
- */
1298
- get nodeType(): NodeType | null;
1299
- /**
1300
- * Get NodeType or throw if not initialized.
1301
- * Use in contexts where editor is guaranteed to be set (like addCommands).
1302
- */
1303
- get nodeTypeOrThrow(): NodeType;
1304
- /**
1305
- * Creates a new node instance
1306
- *
1307
- * @param config - Node configuration
1308
- * @returns New node instance
1309
- *
1310
- * @example
1311
- * const Paragraph = Node.create({
1312
- * name: 'paragraph',
1313
- * group: 'block',
1314
- * content: 'inline*',
1315
- * });
1316
- */
1317
- static create<O = unknown, S = unknown>(config: NodeConfig<O, S>): Node<O, S>;
1318
- /**
1319
- * Creates a new node with merged options
1320
- * Original node is not modified
1321
- *
1322
- * **Note:** Options are merged shallowly using object spread (`...`).
1323
- * Nested objects are replaced entirely, not deeply merged.
1324
- *
1325
- * @param options - Options to merge with existing options
1326
- * @returns New node instance with merged options
1327
- *
1328
- * @example
1329
- * const CustomParagraph = Paragraph.configure({ HTMLAttributes: { class: 'custom' } });
1330
- *
1331
- * @example
1332
- * // Shallow merge behavior with nested objects:
1333
- * // Given: options = { HTMLAttributes: { class: 'a', id: 'b' } }
1334
- * // configure({ HTMLAttributes: { class: 'c' } })
1335
- * // Result: { HTMLAttributes: { class: 'c' } } — 'id' is lost!
1336
- * // To preserve nested values, spread manually:
1337
- * // configure({ HTMLAttributes: { ...original.options.HTMLAttributes, class: 'c' } })
1338
- */
1339
- configure(options: Partial<Options>): Node<Options, Storage>;
1340
- /**
1341
- * Creates a new node with extended configuration
1342
- * Original node is not modified
1343
- *
1344
- * **Note:** Config is merged shallowly using object spread (`...`).
1345
- * Config properties (like `addAttributes`, `parseHTML`) are
1346
- * replaced entirely, not combined with the base node's config.
1347
- *
1348
- * @param extendedConfig - Configuration to extend/override
1349
- * @returns New node instance with extended config
1350
- *
1351
- * @example
1352
- * const CustomParagraph = Paragraph.extend({
1353
- * name: 'customParagraph',
1354
- * addAttributes() {
1355
- * return { ...this.parent?.(), align: { default: 'left' } };
1356
- * },
1357
- * });
1358
- *
1359
- * @example
1360
- * // To preserve base node's parse rules while adding new ones:
1361
- * const Extended = BaseNode.extend({
1362
- * parseHTML() {
1363
- * const baseRules = BaseNode.config.parseHTML?.call(this) ?? [];
1364
- * return [...baseRules, { tag: 'custom-tag' }];
1365
- * },
1366
- * });
1367
- */
1368
- extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig: Partial<NodeConfig<ExtendedOptions, ExtendedStorage>> & ThisType<NodeContext<ExtendedOptions, ExtendedStorage>>): Node<ExtendedOptions, ExtendedStorage>;
1369
- /**
1370
- * Creates a ProseMirror NodeSpec from this node's configuration
1371
- *
1372
- * Called by ExtensionManager when building the schema.
1373
- * Converts our config format to ProseMirror's NodeSpec format.
1374
- *
1375
- * @returns ProseMirror NodeSpec
1376
- */
1377
- createNodeSpec(): NodeSpec;
1378
- }
1379
-
1380
1207
  /**
1381
1208
  * Mark configuration types
1382
1209
  *
@@ -1579,397 +1406,130 @@ interface MarkSchemaProperties {
1579
1406
  type MarkConfig<Options = unknown, Storage = unknown> = ExtensionConfigBase<Options, Storage> & MarkSchemaProperties & ThisType<MarkContext<Options, Storage>>;
1580
1407
 
1581
1408
  /**
1582
- * Mark - Base class for mark extensions
1583
- *
1584
- * Marks define inline formatting that can be applied to text.
1585
- * Examples: Bold, Italic, Link, Code, etc.
1586
- *
1587
- * Three-tier model:
1588
- * - Extension (type: 'extension') → Pure functionality (History, Placeholder, etc.)
1589
- * - Node (type: 'node') → Schema nodes (Paragraph, Heading, etc.)
1590
- * - Mark (type: 'mark') → Schema marks (Bold, Italic, etc.)
1591
- *
1592
- * @example
1593
- * const Bold = Mark.create({
1594
- * name: 'bold',
1595
- * parseHTML() {
1596
- * return [
1597
- * { tag: 'strong' },
1598
- * { tag: 'b' },
1599
- * { style: 'font-weight=bold' },
1600
- * ];
1601
- * },
1602
- * renderHTML({ HTMLAttributes }) {
1603
- * return ['strong', HTMLAttributes, 0];
1604
- * },
1605
- * });
1606
- */
1607
-
1608
- /**
1609
- * Extended editor interface for Mark
1610
- * Includes schema access for MarkType getter
1409
+ * Callback type for event handlers
1410
+ * - Events with payload: (data: T) => void
1411
+ * - Events without payload (undefined): () => void
1611
1412
  */
1612
- interface MarkEditorInterface extends ExtensionEditorInterface {
1613
- readonly schema: {
1614
- marks: Record<string, MarkType>;
1615
- };
1616
- }
1413
+ type EventCallback<T> = T extends undefined ? () => void : (data: T) => void;
1617
1414
  /**
1618
- * Base class for mark extensions
1415
+ * Generic, type-safe event emitter
1619
1416
  *
1620
- * @typeParam Options - Mark options type
1621
- * @typeParam Storage - Mark storage type
1417
+ * @example
1418
+ * ```typescript
1419
+ * interface MyEvents {
1420
+ * update: { value: number };
1421
+ * destroy: undefined;
1422
+ * }
1423
+ *
1424
+ * const emitter = new EventEmitter<MyEvents>();
1425
+ * emitter.on('update', ({ value }) => console.log(value));
1426
+ * emitter.on('destroy', () => console.log('destroyed'));
1427
+ * ```
1622
1428
  */
1623
- declare class Mark<Options = unknown, Storage = unknown> extends Extension<Options, Storage> {
1624
- /**
1625
- * Mark type identifier
1626
- * Distinguishes marks from extensions and nodes
1627
- */
1628
- readonly type: "mark";
1629
- /**
1630
- * The original configuration object
1631
- * Typed as MarkConfig for mark-specific properties
1632
- */
1633
- readonly config: MarkConfig<Options, Storage>;
1634
- /**
1635
- * Editor instance with schema access
1636
- * null until set by ExtensionManager
1637
- */
1638
- editor: MarkEditorInterface | null;
1639
- /**
1640
- * Protected constructor - use Mark.create() instead
1641
- */
1642
- protected constructor(config: MarkConfig<Options, Storage>);
1429
+ declare class EventEmitter<Events extends {
1430
+ [K in keyof Events]: unknown;
1431
+ } = Record<string, never>> {
1432
+ protected callbacks: Map<keyof Events, Set<(data: unknown) => void>>;
1643
1433
  /**
1644
- * Whether this mark represents visual formatting.
1645
- * Returns false for semantic marks (links, comments) that should
1646
- * survive `unsetAllMarks`. Defaults to true.
1434
+ * Register an event listener
1647
1435
  */
1648
- get isFormatting(): boolean;
1436
+ on<E extends keyof Events>(event: E, callback: EventCallback<Events[E]>): this;
1649
1437
  /**
1650
- * Get the ProseMirror MarkType from schema
1651
- *
1652
- * This is a lazy getter because schema doesn't exist at mark creation time.
1653
- * Schema is built FROM marks by ExtensionManager.
1654
- *
1655
- * Returns null if editor is not yet initialized.
1656
- * Always check editor is set before using markType.
1438
+ * Remove an event listener, or all listeners for an event if no callback specified
1657
1439
  */
1658
- get markType(): MarkType | null;
1440
+ off<E extends keyof Events>(event: E, callback?: EventCallback<Events[E]>): this;
1659
1441
  /**
1660
- * Get MarkType or throw if not initialized.
1661
- * Use in contexts where editor is guaranteed to be set (like addCommands).
1442
+ * Emit an event to all registered listeners
1443
+ * Uses .call(this) to preserve context for callbacks
1662
1444
  */
1663
- get markTypeOrThrow(): MarkType;
1445
+ emit<E extends keyof Events>(event: E, ...args: Events[E] extends undefined ? [] : [Events[E]]): this;
1664
1446
  /**
1665
- * Creates a new mark instance
1666
- *
1667
- * @param config - Mark configuration
1668
- * @returns New mark instance
1669
- *
1670
- * @example
1671
- * const Bold = Mark.create({
1672
- * name: 'bold',
1673
- * parseHTML() {
1674
- * return [{ tag: 'strong' }, { tag: 'b' }];
1675
- * },
1676
- * });
1447
+ * Register an event listener that fires only once
1677
1448
  */
1678
- static create<O = unknown, S = unknown>(config: MarkConfig<O, S>): Mark<O, S>;
1449
+ once<E extends keyof Events>(event: E, callback: EventCallback<Events[E]>): this;
1679
1450
  /**
1680
- * Creates a new mark with merged options
1681
- * Original mark is not modified
1682
- *
1683
- * **Note:** Options are merged shallowly using object spread (`...`).
1684
- * Nested objects are replaced entirely, not deeply merged.
1685
- *
1686
- * @param options - Options to merge with existing options
1687
- * @returns New mark instance with merged options
1688
- *
1689
- * @example
1690
- * const CustomBold = Bold.configure({ HTMLAttributes: { class: 'custom-bold' } });
1691
- *
1692
- * @example
1693
- * // Shallow merge behavior with nested objects:
1694
- * // Given: options = { HTMLAttributes: { class: 'a', id: 'b' } }
1695
- * // configure({ HTMLAttributes: { class: 'c' } })
1696
- * // Result: { HTMLAttributes: { class: 'c' } } — 'id' is lost!
1697
- * // To preserve nested values, spread manually:
1698
- * // configure({ HTMLAttributes: { ...original.options.HTMLAttributes, class: 'c' } })
1451
+ * Remove all listeners for a specific event, or all events if no event specified
1699
1452
  */
1700
- configure(options: Partial<Options> & {
1701
- isFormatting?: boolean;
1702
- }): Mark<Options, Storage>;
1453
+ removeAllListeners(event?: keyof Events): this;
1703
1454
  /**
1704
- * Creates a new mark with extended configuration
1705
- * Original mark is not modified
1706
- *
1707
- * **Note:** Config is merged shallowly using object spread (`...`).
1708
- * Config properties (like `addAttributes`, `parseHTML`) are
1709
- * replaced entirely, not combined with the base mark's config.
1710
- *
1711
- * @param extendedConfig - Configuration to extend/override
1712
- * @returns New mark instance with extended config
1713
- *
1714
- * @example
1715
- * const CustomBold = Bold.extend({
1716
- * name: 'customBold',
1717
- * addAttributes() {
1718
- * return { ...this.parent?.(), weight: { default: 'bold' } };
1719
- * },
1720
- * });
1721
- *
1722
- * @example
1723
- * // To preserve base mark's parse rules while adding new ones:
1724
- * const Extended = BaseMark.extend({
1725
- * parseHTML() {
1726
- * const baseRules = BaseMark.config.parseHTML?.call(this) ?? [];
1727
- * return [...baseRules, { tag: 'custom-tag' }];
1728
- * },
1729
- * });
1455
+ * Get the number of listeners for a specific event
1730
1456
  */
1731
- extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig: Partial<MarkConfig<ExtendedOptions, ExtendedStorage>> & ThisType<MarkContext<ExtendedOptions, ExtendedStorage>>): Mark<ExtendedOptions, ExtendedStorage>;
1457
+ listenerCount(event: keyof Events): number;
1732
1458
  /**
1733
- * Creates a ProseMirror MarkSpec from this mark's configuration
1734
- *
1735
- * Called by ExtensionManager when building the schema.
1736
- * Converts our config format to ProseMirror's MarkSpec format.
1737
- *
1738
- * @returns ProseMirror MarkSpec
1459
+ * Get all event names that have listeners
1739
1460
  */
1740
- createMarkSpec(): MarkSpec;
1461
+ eventNames(): (keyof Events)[];
1741
1462
  }
1742
1463
 
1743
1464
  /**
1744
- * Union type for all extension types
1745
- * - Extension: Pure functionality (History, Placeholder)
1746
- * - Node: Schema nodes (Paragraph, Heading)
1747
- * - Mark: Schema marks (Bold, Italic)
1465
+ * ExtensionManager - Manages extensions and schema
1466
+ *
1467
+ * Handles:
1468
+ * - Extension lifecycle (flatten, resolve, bind)
1469
+ * - Schema building from Node/Mark extensions
1470
+ * - Plugin collection from all extensions
1471
+ * - Extension storage management
1472
+ * - Conflict detection (AD-7)
1748
1473
  */
1749
- type AnyExtension = Extension | Node | Mark;
1474
+
1750
1475
  /**
1751
- * Autofocus options for the editor
1752
- * - true: Focus at the end
1753
- * - false: Don't autofocus
1754
- * - null: Explicitly no autofocus
1755
- * - 'start': Focus at the beginning
1756
- * - 'end': Focus at the end
1757
- * - 'all': Select all content
1758
- * - number: Focus at specific position
1476
+ * Error event props for safeCall
1759
1477
  */
1760
- type FocusPosition = boolean | 'start' | 'end' | 'all' | number | null;
1478
+ interface ErrorEventProps {
1479
+ error: Error;
1480
+ context: string;
1481
+ }
1761
1482
  /**
1762
- * Configuration options for creating an Editor instance
1483
+ * Editor interface for ExtensionManager
1484
+ * Forward declaration to avoid circular dependency
1763
1485
  */
1764
- interface EditorOptions {
1486
+ interface ExtensionManagerEditor {
1487
+ readonly schema: Schema;
1488
+ emit?(event: 'error', props: ErrorEventProps): void;
1489
+ }
1490
+ /**
1491
+ * Context attached to node view constructors for framework wrappers.
1492
+ * Accessible via `(constructor as any).__domternalContext`.
1493
+ */
1494
+ interface NodeViewContext {
1495
+ editor: ExtensionManagerEditor;
1496
+ extension: {
1497
+ name: string;
1498
+ options: Record<string, unknown>;
1499
+ };
1500
+ }
1501
+ /**
1502
+ * Options for ExtensionManager constructor
1503
+ */
1504
+ interface ExtensionManagerOptions {
1765
1505
  /**
1766
- * ProseMirror Schema for the editor
1767
- *
1768
- * Step 1.3: Required (no extensions system yet)
1769
- * Step 2+: Optional if extensions are provided (schema built from extensions)
1770
- *
1771
- * The schema must contain at least 'doc' and 'text' nodes.
1506
+ * Extensions to process
1507
+ * If provided, schema is built from extensions
1772
1508
  */
1773
- schema?: Schema;
1509
+ extensions?: AnyExtension[] | undefined;
1774
1510
  /**
1775
- * HTML element to mount the editor
1776
- * If not provided, creates a detached div (useful for testing/headless mode)
1511
+ * Direct schema (backward compatibility with Step 1.3)
1512
+ * If provided, extensions are ignored for schema building
1777
1513
  */
1778
- element?: HTMLElement | null;
1514
+ schema?: Schema | undefined;
1515
+ }
1516
+ declare class ExtensionManager {
1779
1517
  /**
1780
- * Initial content (JSON or HTML string)
1781
- * @default null (empty document)
1518
+ * Processed extensions (flattened, sorted by priority)
1782
1519
  */
1783
- content?: Content;
1520
+ private readonly _extensions;
1784
1521
  /**
1785
- * Extensions to load
1786
- * @default []
1522
+ * ProseMirror schema (built from extensions or passed directly)
1787
1523
  */
1788
- extensions?: AnyExtension[];
1524
+ private readonly _schema;
1789
1525
  /**
1790
- * Whether the editor is editable
1791
- * @default true
1526
+ * Reference to the editor instance
1792
1527
  */
1793
- editable?: boolean;
1528
+ readonly editor: ExtensionManagerEditor;
1794
1529
  /**
1795
- * Autofocus behavior on mount
1796
- * @default false
1530
+ * Extension storage (keyed by extension name)
1797
1531
  */
1798
- autofocus?: FocusPosition;
1799
- /**
1800
- * Transform function applied to clipboard HTML on copy/cut.
1801
- * Use with `inlineStyles` from core to auto-apply inline CSS on copy:
1802
- *
1803
- * @example
1804
- * ```ts
1805
- * import { inlineStyles } from '@domternal/core';
1806
- * new Editor({ clipboardHTMLTransform: inlineStyles });
1807
- * ```
1808
- */
1809
- clipboardHTMLTransform?: (html: string) => string;
1810
- /**
1811
- * Called before the editor is created
1812
- * Can be used to modify options
1813
- */
1814
- onBeforeCreate?: (props: CreateEventProps) => void;
1815
- /**
1816
- * Called when the editor is created and ready
1817
- */
1818
- onCreate?: (props: CreateEventProps) => void;
1819
- /**
1820
- * Called when editor view is mounted to DOM
1821
- */
1822
- onMount?: (props: MountEventProps) => void;
1823
- /**
1824
- * Called when the document content changes
1825
- */
1826
- onUpdate?: (props: TransactionEventProps) => void;
1827
- /**
1828
- * Called when selection changes (without content change)
1829
- */
1830
- onSelectionUpdate?: (props: TransactionEventProps) => void;
1831
- /**
1832
- * Called on every transaction
1833
- */
1834
- onTransaction?: (props: TransactionEventProps) => void;
1835
- /**
1836
- * Called when editor receives focus
1837
- */
1838
- onFocus?: (props: FocusEventProps) => void;
1839
- /**
1840
- * Called when editor loses focus
1841
- */
1842
- onBlur?: (props: FocusEventProps) => void;
1843
- /**
1844
- * Called before editor is destroyed
1845
- */
1846
- onDestroy?: () => void;
1847
- /**
1848
- * Called when content doesn't match schema (AD-8)
1849
- * Use this to handle content validation errors gracefully
1850
- */
1851
- onContentError?: (props: ContentErrorProps) => void;
1852
- /**
1853
- * Called when an extension throws an error (2.7: Extension Error Isolation)
1854
- * Allows graceful error handling without crashing the editor
1855
- */
1856
- onError?: (props: ErrorEventProps$1) => void;
1857
- }
1858
-
1859
- /**
1860
- * Callback type for event handlers
1861
- * - Events with payload: (data: T) => void
1862
- * - Events without payload (undefined): () => void
1863
- */
1864
- type EventCallback<T> = T extends undefined ? () => void : (data: T) => void;
1865
- /**
1866
- * Generic, type-safe event emitter
1867
- *
1868
- * @example
1869
- * ```typescript
1870
- * interface MyEvents {
1871
- * update: { value: number };
1872
- * destroy: undefined;
1873
- * }
1874
- *
1875
- * const emitter = new EventEmitter<MyEvents>();
1876
- * emitter.on('update', ({ value }) => console.log(value));
1877
- * emitter.on('destroy', () => console.log('destroyed'));
1878
- * ```
1879
- */
1880
- declare class EventEmitter<Events extends {
1881
- [K in keyof Events]: unknown;
1882
- } = Record<string, never>> {
1883
- protected callbacks: Map<keyof Events, Set<(data: unknown) => void>>;
1884
- /**
1885
- * Register an event listener
1886
- */
1887
- on<E extends keyof Events>(event: E, callback: EventCallback<Events[E]>): this;
1888
- /**
1889
- * Remove an event listener, or all listeners for an event if no callback specified
1890
- */
1891
- off<E extends keyof Events>(event: E, callback?: EventCallback<Events[E]>): this;
1892
- /**
1893
- * Emit an event to all registered listeners
1894
- * Uses .call(this) to preserve context for callbacks
1895
- */
1896
- emit<E extends keyof Events>(event: E, ...args: Events[E] extends undefined ? [] : [Events[E]]): this;
1897
- /**
1898
- * Register an event listener that fires only once
1899
- */
1900
- once<E extends keyof Events>(event: E, callback: EventCallback<Events[E]>): this;
1901
- /**
1902
- * Remove all listeners for a specific event, or all events if no event specified
1903
- */
1904
- removeAllListeners(event?: keyof Events): this;
1905
- /**
1906
- * Get the number of listeners for a specific event
1907
- */
1908
- listenerCount(event: keyof Events): number;
1909
- /**
1910
- * Get all event names that have listeners
1911
- */
1912
- eventNames(): (keyof Events)[];
1913
- }
1914
-
1915
- /**
1916
- * ExtensionManager - Manages extensions and schema
1917
- *
1918
- * Handles:
1919
- * - Extension lifecycle (flatten, resolve, bind)
1920
- * - Schema building from Node/Mark extensions
1921
- * - Plugin collection from all extensions
1922
- * - Extension storage management
1923
- * - Conflict detection (AD-7)
1924
- */
1925
-
1926
- /**
1927
- * Error event props for safeCall
1928
- */
1929
- interface ErrorEventProps {
1930
- error: Error;
1931
- context: string;
1932
- }
1933
- /**
1934
- * Editor interface for ExtensionManager
1935
- * Forward declaration to avoid circular dependency
1936
- */
1937
- interface ExtensionManagerEditor {
1938
- readonly schema: Schema;
1939
- emit?(event: 'error', props: ErrorEventProps): void;
1940
- }
1941
- /**
1942
- * Options for ExtensionManager constructor
1943
- */
1944
- interface ExtensionManagerOptions {
1945
- /**
1946
- * Extensions to process
1947
- * If provided, schema is built from extensions
1948
- */
1949
- extensions?: AnyExtension[] | undefined;
1950
- /**
1951
- * Direct schema (backward compatibility with Step 1.3)
1952
- * If provided, extensions are ignored for schema building
1953
- */
1954
- schema?: Schema | undefined;
1955
- }
1956
- declare class ExtensionManager {
1957
- /**
1958
- * Processed extensions (flattened, sorted by priority)
1959
- */
1960
- private readonly _extensions;
1961
- /**
1962
- * ProseMirror schema (built from extensions or passed directly)
1963
- */
1964
- private readonly _schema;
1965
- /**
1966
- * Reference to the editor instance
1967
- */
1968
- readonly editor: ExtensionManagerEditor;
1969
- /**
1970
- * Extension storage (keyed by extension name)
1971
- */
1972
- private readonly _storage;
1532
+ private readonly _storage;
1973
1533
  /**
1974
1534
  * Whether the manager has been destroyed
1975
1535
  */
@@ -2110,8 +1670,12 @@ declare class ExtensionManager {
2110
1670
  */
2111
1671
  private collectToolbarItems;
2112
1672
  /**
2113
- * Collects node views from all Node extensions
2114
- * Returns a map of node name NodeViewConstructor for EditorView
1673
+ * Collects node views from all Node extensions.
1674
+ * Returns a map of node name to NodeViewConstructor for EditorView.
1675
+ *
1676
+ * Each constructor is annotated with `__domternalContext` containing
1677
+ * the editor and extension metadata so framework wrappers (React, Vue)
1678
+ * can access them without changing the ProseMirror calling convention.
2115
1679
  */
2116
1680
  private collectNodeViews;
2117
1681
  /**
@@ -3068,6 +2632,11 @@ interface MarkInputRuleOptions {
3068
2632
  * The mark type to apply
3069
2633
  */
3070
2634
  type: MarkType;
2635
+ /**
2636
+ * Whether Backspace can undo this input rule immediately after it fires.
2637
+ * @default true
2638
+ */
2639
+ undoable?: boolean;
3071
2640
  /**
3072
2641
  * Optional: get attributes from the match
3073
2642
  *
@@ -3127,67 +2696,238 @@ declare const markInputRulePatterns: {
3127
2696
  };
3128
2697
 
3129
2698
  /**
3130
- * URL Validation Helper
2699
+ * Wrapping Input Rule Helper
3131
2700
  *
3132
- * Provides a utility for validating URLs.
2701
+ * Drop-in replacement for ProseMirror's wrappingInputRule that exposes
2702
+ * the `undoable` option so extension authors can opt out of Backspace undo.
3133
2703
  */
3134
- /**
3135
- * Options for URL validation
3136
- */
3137
- interface IsValidUrlOptions {
2704
+
2705
+ interface WrappingInputRuleOptions {
3138
2706
  /**
3139
- * List of allowed URL protocols
3140
- * @default ['http:', 'https:']
2707
+ * The regex pattern to match. Should start with `^` so it only
2708
+ * fires at the start of a textblock.
3141
2709
  */
3142
- protocols?: string[];
2710
+ find: RegExp;
2711
+ /**
2712
+ * The node type to wrap in.
2713
+ */
2714
+ type: NodeType;
2715
+ /**
2716
+ * Whether Backspace can undo this input rule immediately after it fires.
2717
+ * @default true
2718
+ */
2719
+ undoable?: boolean;
2720
+ /**
2721
+ * Node attributes, or a function that computes them from the match.
2722
+ */
2723
+ getAttributes?: Attrs | null | ((match: RegExpMatchArray) => Attrs | null);
2724
+ /**
2725
+ * Predicate that decides whether to join with an adjacent node of
2726
+ * the same type above the newly wrapped node.
2727
+ */
2728
+ joinPredicate?: (match: RegExpMatchArray, node: Node$1) => boolean;
2729
+ /**
2730
+ * Optional guard predicate. When provided, the rule only fires if
2731
+ * this returns true. Use this to prevent wrapping in certain contexts
2732
+ * (e.g. don't create a nested list inside an existing list item).
2733
+ */
2734
+ guard?: (state: EditorState) => boolean;
3143
2735
  }
3144
2736
  /**
3145
- * Checks if a string is a valid URL
2737
+ * Creates an input rule that wraps a textblock in a given node type.
3146
2738
  *
3147
- * @param url - The string to validate
3148
- * @param options - Validation options
3149
- * @returns True if the string is a valid URL with an allowed protocol
2739
+ * @example
2740
+ * // `> ` at start of line wraps in blockquote
2741
+ * wrappingInputRule({
2742
+ * find: /^\s*>\s$/,
2743
+ * type: schema.nodes.blockquote,
2744
+ * });
3150
2745
  *
3151
2746
  * @example
3152
- * ```ts
3153
- * isValidUrl('https://example.com'); // true
3154
- * isValidUrl('javascript:alert(1)'); // false (protocol not allowed)
3155
- * isValidUrl('not a url'); // false
3156
- * ```
2747
+ * // Non-undoable wrapping rule
2748
+ * wrappingInputRule({
2749
+ * find: /^\s*>\s$/,
2750
+ * type: schema.nodes.blockquote,
2751
+ * undoable: false,
2752
+ * });
3157
2753
  */
3158
- declare function isValidUrl(url: string, options?: IsValidUrlOptions): boolean;
2754
+ declare function wrappingInputRule(options: WrappingInputRuleOptions): InputRule;
3159
2755
 
3160
- interface GenerateHTMLOptions {
2756
+ /**
2757
+ * Textblock Type Input Rule Helper
2758
+ *
2759
+ * Drop-in replacement for ProseMirror's textblockTypeInputRule that exposes
2760
+ * the `undoable` option so extension authors can opt out of Backspace undo.
2761
+ */
2762
+
2763
+ interface TextblockTypeInputRuleOptions {
3161
2764
  /**
3162
- * Custom document implementation. If not provided, uses native document
3163
- * in browser or linkedom in Node.js.
2765
+ * The regex pattern to match. Should start with `^` so it only
2766
+ * fires at the start of a textblock.
3164
2767
  */
3165
- document?: Document;
2768
+ find: RegExp;
2769
+ /**
2770
+ * The node type to change the textblock to.
2771
+ */
2772
+ type: NodeType;
2773
+ /**
2774
+ * Whether Backspace can undo this input rule immediately after it fires.
2775
+ * @default true
2776
+ */
2777
+ undoable?: boolean;
2778
+ /**
2779
+ * Node attributes, or a function that computes them from the match.
2780
+ */
2781
+ getAttributes?: Attrs | null | ((match: RegExpMatchArray) => Attrs | null);
3166
2782
  }
3167
2783
  /**
3168
- * Generate HTML string from JSON content.
3169
- *
3170
- * @param content - The JSON content to convert
3171
- * @param extensions - Extensions that define the schema
3172
- * @param options - Optional configuration
3173
- * @returns HTML string
2784
+ * Creates an input rule that changes the type of a textblock.
3174
2785
  *
3175
2786
  * @example
3176
- * ```ts
3177
- * const html = generateHTML(
3178
- * { type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hello' }] }] },
3179
- * [Document, Paragraph, Text]
3180
- * );
3181
- * // Returns: '<p>Hello</p>'
3182
- * ```
2787
+ * // `## ` at start of line converts to heading level 2
2788
+ * textblockTypeInputRule({
2789
+ * find: /^(#{1,4})\s$/,
2790
+ * type: schema.nodes.heading,
2791
+ * getAttributes: (match) => ({ level: match[1].length }),
2792
+ * });
3183
2793
  */
3184
- declare function generateHTML(content: JSONContent, extensions: AnyExtension[], options?: GenerateHTMLOptions): string;
3185
- interface GenerateJSONOptions {
2794
+ declare function textblockTypeInputRule(options: TextblockTypeInputRuleOptions): InputRule;
2795
+
2796
+ /**
2797
+ * Text Input Rule Helper
2798
+ *
2799
+ * Creates input rules for simple text replacements (e.g. `--` to em dash).
2800
+ */
2801
+
2802
+ interface TextInputRuleOptions {
3186
2803
  /**
3187
- * Custom document implementation. If not provided, uses native document
3188
- * in browser or linkedom in Node.js.
2804
+ * The regex pattern to match.
3189
2805
  */
3190
- document?: Document;
2806
+ find: RegExp;
2807
+ /**
2808
+ * The replacement text.
2809
+ */
2810
+ replace: string;
2811
+ /**
2812
+ * Whether Backspace can undo this input rule immediately after it fires.
2813
+ * @default true
2814
+ */
2815
+ undoable?: boolean;
2816
+ }
2817
+ /**
2818
+ * Creates an input rule that replaces matched text with a string.
2819
+ *
2820
+ * @example
2821
+ * // `--` converts to em dash
2822
+ * textInputRule({ find: /--$/, replace: '\u2014' });
2823
+ *
2824
+ * @example
2825
+ * // Non-undoable replacement
2826
+ * textInputRule({ find: /->$/, replace: '\u2192', undoable: false });
2827
+ */
2828
+ declare function textInputRule(options: TextInputRuleOptions): InputRule;
2829
+
2830
+ /**
2831
+ * Node Input Rule Helper
2832
+ *
2833
+ * Creates input rules that replace matched text with a node.
2834
+ * Used for nodes like HorizontalRule, Image, Emoji, etc.
2835
+ */
2836
+
2837
+ interface NodeInputRuleOptions {
2838
+ /**
2839
+ * The regex pattern to match.
2840
+ */
2841
+ find: RegExp;
2842
+ /**
2843
+ * The node type to insert.
2844
+ */
2845
+ type: NodeType;
2846
+ /**
2847
+ * Whether Backspace can undo this input rule immediately after it fires.
2848
+ * @default true
2849
+ */
2850
+ undoable?: boolean;
2851
+ /**
2852
+ * Node attributes, or a function that computes them from the match.
2853
+ */
2854
+ getAttributes?: Attrs | null | ((match: RegExpMatchArray, state: EditorState) => Attrs | null);
2855
+ }
2856
+ /**
2857
+ * Creates an input rule that replaces matched text with a node.
2858
+ *
2859
+ * @example
2860
+ * // `:smile:` inserts an emoji node
2861
+ * nodeInputRule({
2862
+ * find: /:([a-zA-Z0-9_+-]+):$/,
2863
+ * type: schema.nodes.emoji,
2864
+ * getAttributes: (match) => ({ name: match[1] }),
2865
+ * });
2866
+ */
2867
+ declare function nodeInputRule(options: NodeInputRuleOptions): InputRule;
2868
+
2869
+ /**
2870
+ * URL Validation Helper
2871
+ *
2872
+ * Provides a utility for validating URLs.
2873
+ */
2874
+ /**
2875
+ * Options for URL validation
2876
+ */
2877
+ interface IsValidUrlOptions {
2878
+ /**
2879
+ * List of allowed URL protocols
2880
+ * @default ['http:', 'https:']
2881
+ */
2882
+ protocols?: string[];
2883
+ }
2884
+ /**
2885
+ * Checks if a string is a valid URL
2886
+ *
2887
+ * @param url - The string to validate
2888
+ * @param options - Validation options
2889
+ * @returns True if the string is a valid URL with an allowed protocol
2890
+ *
2891
+ * @example
2892
+ * ```ts
2893
+ * isValidUrl('https://example.com'); // true
2894
+ * isValidUrl('javascript:alert(1)'); // false (protocol not allowed)
2895
+ * isValidUrl('not a url'); // false
2896
+ * ```
2897
+ */
2898
+ declare function isValidUrl(url: string, options?: IsValidUrlOptions): boolean;
2899
+
2900
+ interface GenerateHTMLOptions {
2901
+ /**
2902
+ * Custom document implementation. If not provided, uses native document
2903
+ * in browser or linkedom in Node.js.
2904
+ */
2905
+ document?: Document;
2906
+ }
2907
+ /**
2908
+ * Generate HTML string from JSON content.
2909
+ *
2910
+ * @param content - The JSON content to convert
2911
+ * @param extensions - Extensions that define the schema
2912
+ * @param options - Optional configuration
2913
+ * @returns HTML string
2914
+ *
2915
+ * @example
2916
+ * ```ts
2917
+ * const html = generateHTML(
2918
+ * { type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hello' }] }] },
2919
+ * [Document, Paragraph, Text]
2920
+ * );
2921
+ * // Returns: '<p>Hello</p>'
2922
+ * ```
2923
+ */
2924
+ declare function generateHTML(content: JSONContent, extensions: AnyExtension[], options?: GenerateHTMLOptions): string;
2925
+ interface GenerateJSONOptions {
2926
+ /**
2927
+ * Custom document implementation. If not provided, uses native document
2928
+ * in browser or linkedom in Node.js.
2929
+ */
2930
+ document?: Document;
3191
2931
  }
3192
2932
  /**
3193
2933
  * Generate JSON content from HTML string.
@@ -3212,94 +2952,397 @@ interface GenerateTextOptions {
3212
2952
  * Separator between block elements.
3213
2953
  * @default '\n\n'
3214
2954
  */
3215
- blockSeparator?: string;
3216
- }
3217
- /**
3218
- * Generate plain text from JSON content.
3219
- *
3220
- * @param content - The JSON content to extract text from
3221
- * @param extensions - Extensions that define the schema
3222
- * @param options - Optional configuration
3223
- * @returns Plain text string
3224
- *
3225
- * @example
3226
- * ```ts
3227
- * const text = generateText(
3228
- * { type: 'doc', content: [
3229
- * { type: 'paragraph', content: [{ type: 'text', text: 'Hello' }] },
3230
- * { type: 'paragraph', content: [{ type: 'text', text: 'World' }] }
3231
- * ]},
3232
- * [Document, Paragraph, Text]
3233
- * );
3234
- * // Returns: 'Hello\n\nWorld'
3235
- * ```
3236
- */
3237
- declare function generateText(content: JSONContent, extensions: AnyExtension[], options?: GenerateTextOptions): string;
3238
-
3239
- /**
3240
- * Get the range of a mark at a resolved position.
3241
- *
3242
- * Walks backward and forward from the position to find contiguous
3243
- * text nodes that share the same mark type, returning the full range.
3244
- */
3245
-
3246
- interface MarkRange {
3247
- from: number;
3248
- to: number;
3249
- }
3250
- /**
3251
- * Returns the contiguous range of a mark around the given resolved position.
3252
- * Returns undefined if the mark is not present at the position.
3253
- */
3254
- declare function getMarkRange($pos: ResolvedPos, type: MarkType): MarkRange | undefined;
3255
-
3256
- /**
3257
- * Find the closest parent node matching a predicate
3258
- *
3259
- * Returns a curried function: findParentNode(predicate)(selection)
3260
- *
3261
- * @example
3262
- * const details = findParentNode(node => node.type.name === 'details')(selection);
3263
- * if (details) {
3264
- * console.log(details.pos, details.node);
3265
- * }
3266
- */
3267
-
3268
- interface FindParentNodeResult {
3269
- pos: number;
3270
- start: number;
3271
- depth: number;
3272
- node: Node$1;
3273
- }
3274
- declare const findParentNode: (predicate: (node: Node$1) => boolean) => (selection: Selection$1) => FindParentNodeResult | undefined;
3275
-
3276
- /**
3277
- * Find all children of a node matching a predicate
3278
- *
3279
- * @example
3280
- * const summaries = findChildren(detailsNode, node => node.type.name === 'detailsSummary');
3281
- */
3282
-
3283
- interface FindChildResult {
3284
- node: Node$1;
3285
- pos: number;
2955
+ blockSeparator?: string;
2956
+ }
2957
+ /**
2958
+ * Generate plain text from JSON content.
2959
+ *
2960
+ * @param content - The JSON content to extract text from
2961
+ * @param extensions - Extensions that define the schema
2962
+ * @param options - Optional configuration
2963
+ * @returns Plain text string
2964
+ *
2965
+ * @example
2966
+ * ```ts
2967
+ * const text = generateText(
2968
+ * { type: 'doc', content: [
2969
+ * { type: 'paragraph', content: [{ type: 'text', text: 'Hello' }] },
2970
+ * { type: 'paragraph', content: [{ type: 'text', text: 'World' }] }
2971
+ * ]},
2972
+ * [Document, Paragraph, Text]
2973
+ * );
2974
+ * // Returns: 'Hello\n\nWorld'
2975
+ * ```
2976
+ */
2977
+ declare function generateText(content: JSONContent, extensions: AnyExtension[], options?: GenerateTextOptions): string;
2978
+
2979
+ /**
2980
+ * Get the range of a mark at a resolved position.
2981
+ *
2982
+ * Walks backward and forward from the position to find contiguous
2983
+ * text nodes that share the same mark type, returning the full range.
2984
+ */
2985
+
2986
+ interface MarkRange {
2987
+ from: number;
2988
+ to: number;
2989
+ }
2990
+ /**
2991
+ * Returns the contiguous range of a mark around the given resolved position.
2992
+ * Returns undefined if the mark is not present at the position.
2993
+ */
2994
+ declare function getMarkRange($pos: ResolvedPos, type: MarkType): MarkRange | undefined;
2995
+
2996
+ /**
2997
+ * Find the closest parent node matching a predicate
2998
+ *
2999
+ * Returns a curried function: findParentNode(predicate)(selection)
3000
+ *
3001
+ * @example
3002
+ * const details = findParentNode(node => node.type.name === 'details')(selection);
3003
+ * if (details) {
3004
+ * console.log(details.pos, details.node);
3005
+ * }
3006
+ */
3007
+
3008
+ interface FindParentNodeResult {
3009
+ pos: number;
3010
+ start: number;
3011
+ depth: number;
3012
+ node: Node$1;
3013
+ }
3014
+ declare const findParentNode: (predicate: (node: Node$1) => boolean) => (selection: Selection$1) => FindParentNodeResult | undefined;
3015
+
3016
+ /**
3017
+ * Find all children of a node matching a predicate
3018
+ *
3019
+ * @example
3020
+ * const summaries = findChildren(detailsNode, node => node.type.name === 'detailsSummary');
3021
+ */
3022
+
3023
+ interface FindChildResult {
3024
+ node: Node$1;
3025
+ pos: number;
3026
+ }
3027
+ declare const findChildren: (node: Node$1, predicate: (node: Node$1) => boolean) => FindChildResult[];
3028
+
3029
+ /**
3030
+ * Get the default block type at a given content match position
3031
+ *
3032
+ * Finds the first textblock type that can be created without required attributes.
3033
+ * Useful for creating new empty blocks (e.g., paragraph after details).
3034
+ *
3035
+ * @example
3036
+ * const type = defaultBlockAt(parent.contentMatchAt(index));
3037
+ * if (type) {
3038
+ * const node = type.createAndFill();
3039
+ * }
3040
+ */
3041
+
3042
+ declare const defaultBlockAt: (match: ContentMatch) => NodeType | null;
3043
+
3044
+ /**
3045
+ * Extension - Base class for all extensions
3046
+ *
3047
+ * Extensions provide functionality without contributing to the schema.
3048
+ * For schema contributions, use Node (for block/inline nodes) or Mark (for inline formatting).
3049
+ *
3050
+ * Three-tier model:
3051
+ * - Extension (type: 'extension') → Pure functionality (History, Placeholder, etc.)
3052
+ * - Node (type: 'node') → Schema nodes (Paragraph, Heading, etc.)
3053
+ * - Mark (type: 'mark') → Schema marks (Bold, Italic, etc.)
3054
+ *
3055
+ * @example
3056
+ * const History = Extension.create({
3057
+ * name: 'history',
3058
+ * addOptions() {
3059
+ * return { depth: 100 };
3060
+ * },
3061
+ * addKeyboardShortcuts() {
3062
+ * return {
3063
+ * 'Mod-z': () => this.editor.commands.undo(),
3064
+ * 'Mod-Shift-z': () => this.editor.commands.redo(),
3065
+ * };
3066
+ * },
3067
+ * });
3068
+ */
3069
+
3070
+ /**
3071
+ * Editor interface for Extension
3072
+ * Forward declaration to avoid circular dependency
3073
+ */
3074
+ interface ExtensionEditorInterface {
3075
+ readonly state: EditorState;
3076
+ readonly view: EditorView;
3077
+ readonly schema: unknown;
3078
+ readonly commands: SingleCommands;
3079
+ }
3080
+ /**
3081
+ * Base class for all extensions
3082
+ *
3083
+ * @typeParam Options - Extension options type
3084
+ * @typeParam Storage - Extension storage type
3085
+ */
3086
+ declare class Extension<Options = unknown, Storage = unknown> {
3087
+ /**
3088
+ * Extension type identifier
3089
+ * Used to distinguish between Extension, Node, and Mark
3090
+ * Subclasses override this to 'node' or 'mark'
3091
+ */
3092
+ readonly type: 'extension' | 'node' | 'mark';
3093
+ /**
3094
+ * Unique extension name
3095
+ */
3096
+ readonly name: string;
3097
+ /**
3098
+ * Extension options (immutable after creation)
3099
+ */
3100
+ readonly options: Options;
3101
+ /**
3102
+ * Extension storage (mutable state)
3103
+ * Accessible via editor.storage[extensionName]
3104
+ */
3105
+ storage: Storage;
3106
+ /**
3107
+ * The original configuration object
3108
+ */
3109
+ readonly config: ExtensionConfig<Options, Storage>;
3110
+ /**
3111
+ * Editor instance (set by ExtensionManager after creation)
3112
+ * null until ExtensionManager binds it
3113
+ */
3114
+ editor: ExtensionEditorInterface | null;
3115
+ /**
3116
+ * Reference to the parent config method when using extend().
3117
+ * Set temporarily during config method execution so overridden
3118
+ * methods can call `this.parent?.()` to invoke the original.
3119
+ */
3120
+ parent?: ((...args: unknown[]) => unknown) | undefined;
3121
+ /**
3122
+ * Protected constructor - use Extension.create() instead
3123
+ */
3124
+ protected constructor(config: ExtensionConfig<Options, Storage>);
3125
+ /**
3126
+ * Creates a new extension instance
3127
+ *
3128
+ * @param config - Extension configuration
3129
+ * @returns New extension instance
3130
+ *
3131
+ * @example
3132
+ * const MyExtension = Extension.create({
3133
+ * name: 'myExtension',
3134
+ * addOptions() {
3135
+ * return { enabled: true };
3136
+ * },
3137
+ * });
3138
+ */
3139
+ static create<O = unknown, S = unknown>(config: ExtensionConfig<O, S>): Extension<O, S>;
3140
+ /**
3141
+ * Creates a new extension with merged options
3142
+ * Original extension is not modified
3143
+ *
3144
+ * **Note:** Options are merged shallowly using object spread (`...`).
3145
+ * Nested objects are replaced entirely, not deeply merged.
3146
+ *
3147
+ * @param options - Options to merge with existing options
3148
+ * @returns New extension instance with merged options
3149
+ *
3150
+ * @example
3151
+ * const configured = MyExtension.configure({ enabled: false });
3152
+ *
3153
+ * @example
3154
+ * // Shallow merge behavior with nested objects:
3155
+ * // Given: options = { nested: { a: 1, b: 2 } }
3156
+ * // configure({ nested: { b: 3 } })
3157
+ * // Result: { nested: { b: 3 } } — 'a' is lost!
3158
+ * // To preserve nested values, spread manually:
3159
+ * // configure({ nested: { ...original.options.nested, b: 3 } })
3160
+ */
3161
+ configure(options: Partial<Options>): Extension<Options, Storage>;
3162
+ /**
3163
+ * Creates a new extension with extended configuration
3164
+ * Original extension is not modified
3165
+ *
3166
+ * **Note:** Config is merged shallowly using object spread (`...`).
3167
+ * Config properties (like `addCommands`, `addKeyboardShortcuts`) are
3168
+ * replaced entirely, not combined with the base extension's config.
3169
+ *
3170
+ * @param extendedConfig - Configuration to extend/override
3171
+ * @returns New extension instance with extended config
3172
+ *
3173
+ * @example
3174
+ * const Extended = MyExtension.extend({
3175
+ * name: 'extendedExtension',
3176
+ * addCommands() {
3177
+ * return { customCommand: () => ({ tr }) => true };
3178
+ * },
3179
+ * });
3180
+ *
3181
+ * @example
3182
+ * // To preserve base extension's commands while adding new ones:
3183
+ * const Extended = BaseExtension.extend({
3184
+ * addCommands() {
3185
+ * const baseCommands = BaseExtension.config.addCommands?.call(this) ?? {};
3186
+ * return {
3187
+ * ...baseCommands,
3188
+ * newCommand: () => ({ tr }) => true,
3189
+ * };
3190
+ * },
3191
+ * });
3192
+ */
3193
+ extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig: Partial<ExtensionConfigBase<ExtendedOptions, ExtendedStorage>> & ThisType<ExtensionContext<ExtendedOptions, ExtendedStorage>>): Extension<ExtendedOptions, ExtendedStorage>;
3194
+ }
3195
+
3196
+ /**
3197
+ * Node - Base class for node extensions
3198
+ *
3199
+ * Nodes define document structure elements that contribute to the schema.
3200
+ * Examples: Paragraph, Heading, List, Image, etc.
3201
+ *
3202
+ * Three-tier model:
3203
+ * - Extension (type: 'extension') → Pure functionality (History, Placeholder, etc.)
3204
+ * - Node (type: 'node') → Schema nodes (Paragraph, Heading, etc.)
3205
+ * - Mark (type: 'mark') → Schema marks (Bold, Italic, etc.)
3206
+ *
3207
+ * @example
3208
+ * const Paragraph = Node.create({
3209
+ * name: 'paragraph',
3210
+ * group: 'block',
3211
+ * content: 'inline*',
3212
+ * parseHTML() {
3213
+ * return [{ tag: 'p' }];
3214
+ * },
3215
+ * renderHTML({ HTMLAttributes }) {
3216
+ * return ['p', HTMLAttributes, 0];
3217
+ * },
3218
+ * });
3219
+ */
3220
+
3221
+ /**
3222
+ * Extended editor interface for Node
3223
+ * Includes schema access for NodeType getter
3224
+ */
3225
+ interface NodeEditorInterface extends ExtensionEditorInterface {
3226
+ readonly schema: {
3227
+ nodes: Record<string, NodeType>;
3228
+ };
3229
+ }
3230
+ /**
3231
+ * Base class for node extensions
3232
+ *
3233
+ * @typeParam Options - Node options type
3234
+ * @typeParam Storage - Node storage type
3235
+ */
3236
+ declare class Node<Options = unknown, Storage = unknown> extends Extension<Options, Storage> {
3237
+ /**
3238
+ * Node type identifier
3239
+ * Distinguishes nodes from extensions and marks
3240
+ */
3241
+ readonly type: "node";
3242
+ /**
3243
+ * The original configuration object
3244
+ * Typed as NodeConfig for node-specific properties
3245
+ */
3246
+ readonly config: NodeConfig<Options, Storage>;
3247
+ /**
3248
+ * Editor instance with schema access
3249
+ * null until set by ExtensionManager
3250
+ */
3251
+ editor: NodeEditorInterface | null;
3252
+ /**
3253
+ * Protected constructor - use Node.create() instead
3254
+ */
3255
+ protected constructor(config: NodeConfig<Options, Storage>);
3256
+ /**
3257
+ * Get the ProseMirror NodeType from schema
3258
+ *
3259
+ * This is a lazy getter because schema doesn't exist at node creation time.
3260
+ * Schema is built FROM nodes by ExtensionManager.
3261
+ *
3262
+ * Returns null if editor is not yet initialized.
3263
+ * Always check editor is set before using nodeType.
3264
+ */
3265
+ get nodeType(): NodeType | null;
3266
+ /**
3267
+ * Get NodeType or throw if not initialized.
3268
+ * Use in contexts where editor is guaranteed to be set (like addCommands).
3269
+ */
3270
+ get nodeTypeOrThrow(): NodeType;
3271
+ /**
3272
+ * Creates a new node instance
3273
+ *
3274
+ * @param config - Node configuration
3275
+ * @returns New node instance
3276
+ *
3277
+ * @example
3278
+ * const Paragraph = Node.create({
3279
+ * name: 'paragraph',
3280
+ * group: 'block',
3281
+ * content: 'inline*',
3282
+ * });
3283
+ */
3284
+ static create<O = unknown, S = unknown>(config: NodeConfig<O, S>): Node<O, S>;
3285
+ /**
3286
+ * Creates a new node with merged options
3287
+ * Original node is not modified
3288
+ *
3289
+ * **Note:** Options are merged shallowly using object spread (`...`).
3290
+ * Nested objects are replaced entirely, not deeply merged.
3291
+ *
3292
+ * @param options - Options to merge with existing options
3293
+ * @returns New node instance with merged options
3294
+ *
3295
+ * @example
3296
+ * const CustomParagraph = Paragraph.configure({ HTMLAttributes: { class: 'custom' } });
3297
+ *
3298
+ * @example
3299
+ * // Shallow merge behavior with nested objects:
3300
+ * // Given: options = { HTMLAttributes: { class: 'a', id: 'b' } }
3301
+ * // configure({ HTMLAttributes: { class: 'c' } })
3302
+ * // Result: { HTMLAttributes: { class: 'c' } } — 'id' is lost!
3303
+ * // To preserve nested values, spread manually:
3304
+ * // configure({ HTMLAttributes: { ...original.options.HTMLAttributes, class: 'c' } })
3305
+ */
3306
+ configure(options: Partial<Options>): Node<Options, Storage>;
3307
+ /**
3308
+ * Creates a new node with extended configuration
3309
+ * Original node is not modified
3310
+ *
3311
+ * **Note:** Config is merged shallowly using object spread (`...`).
3312
+ * Config properties (like `addAttributes`, `parseHTML`) are
3313
+ * replaced entirely, not combined with the base node's config.
3314
+ *
3315
+ * @param extendedConfig - Configuration to extend/override
3316
+ * @returns New node instance with extended config
3317
+ *
3318
+ * @example
3319
+ * const CustomParagraph = Paragraph.extend({
3320
+ * name: 'customParagraph',
3321
+ * addAttributes() {
3322
+ * return { ...this.parent?.(), align: { default: 'left' } };
3323
+ * },
3324
+ * });
3325
+ *
3326
+ * @example
3327
+ * // To preserve base node's parse rules while adding new ones:
3328
+ * const Extended = BaseNode.extend({
3329
+ * parseHTML() {
3330
+ * const baseRules = BaseNode.config.parseHTML?.call(this) ?? [];
3331
+ * return [...baseRules, { tag: 'custom-tag' }];
3332
+ * },
3333
+ * });
3334
+ */
3335
+ extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig: Partial<NodeConfig<ExtendedOptions, ExtendedStorage>> & ThisType<NodeContext<ExtendedOptions, ExtendedStorage>>): Node<ExtendedOptions, ExtendedStorage>;
3336
+ /**
3337
+ * Creates a ProseMirror NodeSpec from this node's configuration
3338
+ *
3339
+ * Called by ExtensionManager when building the schema.
3340
+ * Converts our config format to ProseMirror's NodeSpec format.
3341
+ *
3342
+ * @returns ProseMirror NodeSpec
3343
+ */
3344
+ createNodeSpec(): NodeSpec;
3286
3345
  }
3287
- declare const findChildren: (node: Node$1, predicate: (node: Node$1) => boolean) => FindChildResult[];
3288
-
3289
- /**
3290
- * Get the default block type at a given content match position
3291
- *
3292
- * Finds the first textblock type that can be created without required attributes.
3293
- * Useful for creating new empty blocks (e.g., paragraph after details).
3294
- *
3295
- * @example
3296
- * const type = defaultBlockAt(parent.contentMatchAt(index));
3297
- * if (type) {
3298
- * const node = type.createAndFill();
3299
- * }
3300
- */
3301
-
3302
- declare const defaultBlockAt: (match: ContentMatch) => NodeType | null;
3303
3346
 
3304
3347
  /**
3305
3348
  * ToolbarController — Headless, framework-agnostic toolbar state machine
@@ -3471,6 +3514,168 @@ declare class ToolbarController {
3471
3514
 
3472
3515
  declare const defaultIcons: IconSet;
3473
3516
 
3517
+ /**
3518
+ * Mark - Base class for mark extensions
3519
+ *
3520
+ * Marks define inline formatting that can be applied to text.
3521
+ * Examples: Bold, Italic, Link, Code, etc.
3522
+ *
3523
+ * Three-tier model:
3524
+ * - Extension (type: 'extension') → Pure functionality (History, Placeholder, etc.)
3525
+ * - Node (type: 'node') → Schema nodes (Paragraph, Heading, etc.)
3526
+ * - Mark (type: 'mark') → Schema marks (Bold, Italic, etc.)
3527
+ *
3528
+ * @example
3529
+ * const Bold = Mark.create({
3530
+ * name: 'bold',
3531
+ * parseHTML() {
3532
+ * return [
3533
+ * { tag: 'strong' },
3534
+ * { tag: 'b' },
3535
+ * { style: 'font-weight=bold' },
3536
+ * ];
3537
+ * },
3538
+ * renderHTML({ HTMLAttributes }) {
3539
+ * return ['strong', HTMLAttributes, 0];
3540
+ * },
3541
+ * });
3542
+ */
3543
+
3544
+ /**
3545
+ * Extended editor interface for Mark
3546
+ * Includes schema access for MarkType getter
3547
+ */
3548
+ interface MarkEditorInterface extends ExtensionEditorInterface {
3549
+ readonly schema: {
3550
+ marks: Record<string, MarkType>;
3551
+ };
3552
+ }
3553
+ /**
3554
+ * Base class for mark extensions
3555
+ *
3556
+ * @typeParam Options - Mark options type
3557
+ * @typeParam Storage - Mark storage type
3558
+ */
3559
+ declare class Mark<Options = unknown, Storage = unknown> extends Extension<Options, Storage> {
3560
+ /**
3561
+ * Mark type identifier
3562
+ * Distinguishes marks from extensions and nodes
3563
+ */
3564
+ readonly type: "mark";
3565
+ /**
3566
+ * The original configuration object
3567
+ * Typed as MarkConfig for mark-specific properties
3568
+ */
3569
+ readonly config: MarkConfig<Options, Storage>;
3570
+ /**
3571
+ * Editor instance with schema access
3572
+ * null until set by ExtensionManager
3573
+ */
3574
+ editor: MarkEditorInterface | null;
3575
+ /**
3576
+ * Protected constructor - use Mark.create() instead
3577
+ */
3578
+ protected constructor(config: MarkConfig<Options, Storage>);
3579
+ /**
3580
+ * Whether this mark represents visual formatting.
3581
+ * Returns false for semantic marks (links, comments) that should
3582
+ * survive `unsetAllMarks`. Defaults to true.
3583
+ */
3584
+ get isFormatting(): boolean;
3585
+ /**
3586
+ * Get the ProseMirror MarkType from schema
3587
+ *
3588
+ * This is a lazy getter because schema doesn't exist at mark creation time.
3589
+ * Schema is built FROM marks by ExtensionManager.
3590
+ *
3591
+ * Returns null if editor is not yet initialized.
3592
+ * Always check editor is set before using markType.
3593
+ */
3594
+ get markType(): MarkType | null;
3595
+ /**
3596
+ * Get MarkType or throw if not initialized.
3597
+ * Use in contexts where editor is guaranteed to be set (like addCommands).
3598
+ */
3599
+ get markTypeOrThrow(): MarkType;
3600
+ /**
3601
+ * Creates a new mark instance
3602
+ *
3603
+ * @param config - Mark configuration
3604
+ * @returns New mark instance
3605
+ *
3606
+ * @example
3607
+ * const Bold = Mark.create({
3608
+ * name: 'bold',
3609
+ * parseHTML() {
3610
+ * return [{ tag: 'strong' }, { tag: 'b' }];
3611
+ * },
3612
+ * });
3613
+ */
3614
+ static create<O = unknown, S = unknown>(config: MarkConfig<O, S>): Mark<O, S>;
3615
+ /**
3616
+ * Creates a new mark with merged options
3617
+ * Original mark is not modified
3618
+ *
3619
+ * **Note:** Options are merged shallowly using object spread (`...`).
3620
+ * Nested objects are replaced entirely, not deeply merged.
3621
+ *
3622
+ * @param options - Options to merge with existing options
3623
+ * @returns New mark instance with merged options
3624
+ *
3625
+ * @example
3626
+ * const CustomBold = Bold.configure({ HTMLAttributes: { class: 'custom-bold' } });
3627
+ *
3628
+ * @example
3629
+ * // Shallow merge behavior with nested objects:
3630
+ * // Given: options = { HTMLAttributes: { class: 'a', id: 'b' } }
3631
+ * // configure({ HTMLAttributes: { class: 'c' } })
3632
+ * // Result: { HTMLAttributes: { class: 'c' } } — 'id' is lost!
3633
+ * // To preserve nested values, spread manually:
3634
+ * // configure({ HTMLAttributes: { ...original.options.HTMLAttributes, class: 'c' } })
3635
+ */
3636
+ configure(options: Partial<Options> & {
3637
+ isFormatting?: boolean;
3638
+ }): Mark<Options, Storage>;
3639
+ /**
3640
+ * Creates a new mark with extended configuration
3641
+ * Original mark is not modified
3642
+ *
3643
+ * **Note:** Config is merged shallowly using object spread (`...`).
3644
+ * Config properties (like `addAttributes`, `parseHTML`) are
3645
+ * replaced entirely, not combined with the base mark's config.
3646
+ *
3647
+ * @param extendedConfig - Configuration to extend/override
3648
+ * @returns New mark instance with extended config
3649
+ *
3650
+ * @example
3651
+ * const CustomBold = Bold.extend({
3652
+ * name: 'customBold',
3653
+ * addAttributes() {
3654
+ * return { ...this.parent?.(), weight: { default: 'bold' } };
3655
+ * },
3656
+ * });
3657
+ *
3658
+ * @example
3659
+ * // To preserve base mark's parse rules while adding new ones:
3660
+ * const Extended = BaseMark.extend({
3661
+ * parseHTML() {
3662
+ * const baseRules = BaseMark.config.parseHTML?.call(this) ?? [];
3663
+ * return [...baseRules, { tag: 'custom-tag' }];
3664
+ * },
3665
+ * });
3666
+ */
3667
+ extend<ExtendedOptions = Options, ExtendedStorage = Storage>(extendedConfig: Partial<MarkConfig<ExtendedOptions, ExtendedStorage>> & ThisType<MarkContext<ExtendedOptions, ExtendedStorage>>): Mark<ExtendedOptions, ExtendedStorage>;
3668
+ /**
3669
+ * Creates a ProseMirror MarkSpec from this mark's configuration
3670
+ *
3671
+ * Called by ExtensionManager when building the schema.
3672
+ * Converts our config format to ProseMirror's MarkSpec format.
3673
+ *
3674
+ * @returns ProseMirror MarkSpec
3675
+ */
3676
+ createMarkSpec(): MarkSpec;
3677
+ }
3678
+
3474
3679
  /**
3475
3680
  * ChainBuilder - Chainable command builder
3476
3681
  *
@@ -5022,7 +5227,7 @@ declare const Selection: Extension<SelectionOptions, SelectionStorage>;
5022
5227
  *
5023
5228
  * Collapses the editor's range selection to a cursor when the editor loses
5024
5229
  * focus. This prevents a "ghost selection" from lingering after the user
5025
- * clicks outside the editor (approach A same as Google Docs / Notion).
5230
+ * clicks outside the editor (approach A - same as Google Docs / Notion).
5026
5231
  *
5027
5232
  * Toolbar and bubble-menu buttons call `event.preventDefault()` on
5028
5233
  * `mousedown`, so they never trigger blur — the selection stays intact
@@ -5133,7 +5338,7 @@ declare const InvisibleChars: Extension<InvisibleCharsOptions, InvisibleCharsSto
5133
5338
  * extensions: [
5134
5339
  * // ... other extensions
5135
5340
  * TextStyle,
5136
- * TextColor, // uses the default 80-color palette
5341
+ * TextColor, // uses the default 25-color palette
5137
5342
  * ],
5138
5343
  * });
5139
5344
  *
@@ -5628,4 +5833,4 @@ declare const StarterKit: Extension<StarterKitOptions, unknown>;
5628
5833
  */
5629
5834
  declare const VERSION = "0.1.0";
5630
5835
 
5631
- 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 NodeParseRule, type NodeRenderHTMLProps, 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, TextStyle, type TextStyleOptions, 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, 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, placeholderPluginKey, positionFloating, positionFloatingOnce, resetAttributes, selectAll, selectNodeBackward, selectionDecorationPluginKey, setBlockType, setContent, setMark, toggleBlockType, toggleList, toggleMark, toggleWrap, uniqueIDPluginKey, unsetAllMarks, unsetMark, updateAttributes, wrapIn };
5836
+ 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 };