@domternal/core 0.14.0 → 1.0.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.cjs CHANGED
@@ -3,10 +3,10 @@
3
3
  var state = require('@domternal/pm/state');
4
4
  var view = require('@domternal/pm/view');
5
5
  var model = require('@domternal/pm/model');
6
+ var transform = require('@domternal/pm/transform');
6
7
  var keymap = require('@domternal/pm/keymap');
7
8
  var commands = require('@domternal/pm/commands');
8
9
  var inputrules = require('@domternal/pm/inputrules');
9
- var transform = require('@domternal/pm/transform');
10
10
  var schemaList = require('@domternal/pm/schema-list');
11
11
  var dom = require('@floating-ui/dom');
12
12
  var linkifyjs = require('linkifyjs');
@@ -225,6 +225,90 @@ var ExtensionConfigurationError = class extends Error {
225
225
  }
226
226
  };
227
227
 
228
+ // src/utils/prosemirrorSingleton.ts
229
+ var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("domternal.prosemirror.copies");
230
+ var REPORTED_KEY = /* @__PURE__ */ Symbol.for("domternal.prosemirror.copies.reported");
231
+ function registry() {
232
+ const host = globalThis;
233
+ const existing = host[REGISTRY_KEY];
234
+ if (existing instanceof Map) return existing;
235
+ const fresh = /* @__PURE__ */ new Map();
236
+ host[REGISTRY_KEY] = fresh;
237
+ return fresh;
238
+ }
239
+ function reported() {
240
+ const host = globalThis;
241
+ const existing = host[REPORTED_KEY];
242
+ if (existing instanceof Set) return existing;
243
+ const fresh = /* @__PURE__ */ new Set();
244
+ host[REPORTED_KEY] = fresh;
245
+ return fresh;
246
+ }
247
+ var FIXES = [
248
+ 'pnpm: add the package to "pnpm.overrides" in package.json, then run `pnpm dedupe`',
249
+ 'npm: add the package to "overrides" in package.json, then reinstall',
250
+ 'yarn: add the package to "resolutions" in package.json, then reinstall',
251
+ "Vite: depend on '<module>' in the app, then resolve.dedupe: ['<module>']",
252
+ "webpack: depend on '<module>' in the app, then resolve.alias it to that one copy"
253
+ ];
254
+ var DOCS_URL = "https://domternal.dev/v1/guides/single-prosemirror-copy/";
255
+ function describeConflict(module, first, second) {
256
+ return [
257
+ `Two different copies of "${module}" are loaded on this page.`,
258
+ `"${first}" registered one, "${second}" arrived with another.`,
259
+ "ProseMirror compares classes by identity, so objects made by one copy are",
260
+ "rejected by the other and the editor fails as soon as the two meet.",
261
+ "",
262
+ "Force a single copy:",
263
+ ...FIXES.map((line) => ` ${line.replaceAll("<module>", module)}`),
264
+ "",
265
+ DOCS_URL
266
+ ].join("\n");
267
+ }
268
+ function describeForeignExtension(name) {
269
+ return [
270
+ `The extension "${name}" was built by a different copy of "@domternal/core"`,
271
+ "than the one building this editor.",
272
+ "Extensions are bound to the core that created them: their base class, schema",
273
+ "and plugin keys all belong to that copy, so an editor cannot use one built",
274
+ "elsewhere. Two copies usually mean a linked or nested install.",
275
+ "",
276
+ "Force a single copy:",
277
+ ...FIXES.map((line) => ` ${line.replaceAll("<module>", "@domternal/core")}`),
278
+ "",
279
+ DOCS_URL
280
+ ].join("\n");
281
+ }
282
+ function registerProseMirrorCopy(module, copy, consumer) {
283
+ const entries = registry();
284
+ const existing = entries.get(module);
285
+ if (!existing) {
286
+ entries.set(module, { copy, consumer });
287
+ return null;
288
+ }
289
+ if (existing.copy === copy) return null;
290
+ return {
291
+ module,
292
+ firstConsumer: existing.consumer,
293
+ secondConsumer: consumer,
294
+ message: describeConflict(module, existing.consumer, consumer)
295
+ };
296
+ }
297
+ function assertSingleProseMirrorCopy(module, copy, consumer) {
298
+ const conflict = registerProseMirrorCopy(module, copy, consumer);
299
+ if (conflict) throw new ExtensionConfigurationError(conflict.message);
300
+ }
301
+ function warnOnDuplicateProseMirrorCopy(module, copy, consumer) {
302
+ const conflict = registerProseMirrorCopy(module, copy, consumer);
303
+ if (!conflict) return null;
304
+ const seen = reported();
305
+ if (!seen.has(module)) {
306
+ seen.add(module);
307
+ console.warn(conflict.message);
308
+ }
309
+ return conflict;
310
+ }
311
+
228
312
  // src/helpers/callOrReturn.ts
229
313
  function callOrReturn(value, context, ...args) {
230
314
  if (typeof value === "function") {
@@ -233,6 +317,183 @@ function callOrReturn(value, context, ...args) {
233
317
  return value;
234
318
  }
235
319
 
320
+ // src/Extension.ts
321
+ function mergeConfigWithParentBinding(parentConfig, extendedConfig) {
322
+ const parent = parentConfig;
323
+ const merged = { ...parent };
324
+ for (const [key, value] of Object.entries(extendedConfig)) {
325
+ if (typeof value === "function" && typeof parent[key] === "function") {
326
+ const parentFn = parent[key];
327
+ const childFn = value;
328
+ merged[key] = function(...args) {
329
+ const previousParent = this.parent;
330
+ this.parent = (...pArgs) => parentFn.call(this, ...pArgs);
331
+ const result = childFn.call(this, ...args);
332
+ this.parent = previousParent;
333
+ return result;
334
+ };
335
+ } else {
336
+ merged[key] = value;
337
+ }
338
+ }
339
+ return merged;
340
+ }
341
+ var EXTENSION_BRAND = /* @__PURE__ */ Symbol.for("domternal.core.extension");
342
+ var Extension = class _Extension {
343
+ /**
344
+ * Brand read by `ExtensionManager` to tell an extension built by another
345
+ * copy of `@domternal/core` from a plain object. See `EXTENSION_BRAND`.
346
+ */
347
+ [EXTENSION_BRAND] = true;
348
+ /**
349
+ * Extension type identifier
350
+ * Used to distinguish between Extension, Node, and Mark
351
+ * Subclasses override this to 'node' or 'mark'
352
+ */
353
+ type = "extension";
354
+ /**
355
+ * Unique extension name
356
+ */
357
+ name;
358
+ /**
359
+ * Extension options (immutable after creation)
360
+ */
361
+ options;
362
+ /**
363
+ * Extension storage (mutable state)
364
+ * Accessible via editor.storage[extensionName]
365
+ */
366
+ storage;
367
+ /**
368
+ * The original configuration object
369
+ */
370
+ config;
371
+ /**
372
+ * Editor instance (set by ExtensionManager after creation)
373
+ * null until ExtensionManager binds it
374
+ */
375
+ editor = null;
376
+ /**
377
+ * Reference to the parent config method when using extend().
378
+ * Set temporarily during config method execution so overridden
379
+ * methods can call `this.parent?.()` to invoke the original.
380
+ */
381
+ parent;
382
+ /**
383
+ * Protected constructor - use Extension.create() instead
384
+ */
385
+ constructor(config) {
386
+ if (!/^[a-z][a-zA-Z0-9]*$/.test(config.name)) {
387
+ throw new Error(
388
+ `Extension name '${config.name}' is invalid. Names must be camelCase starting with a lowercase letter (e.g., 'myExtension').`
389
+ );
390
+ }
391
+ this.config = config;
392
+ this.name = config.name;
393
+ const defaultOptions = callOrReturn(config.addOptions, this);
394
+ this.options = defaultOptions ?? {};
395
+ const defaultStorage = callOrReturn(config.addStorage, this);
396
+ this.storage = defaultStorage ?? {};
397
+ }
398
+ /**
399
+ * Creates a new extension instance
400
+ *
401
+ * @param config - Extension configuration
402
+ * @returns New extension instance
403
+ *
404
+ * @example
405
+ * const MyExtension = Extension.create({
406
+ * name: 'myExtension',
407
+ * addOptions() {
408
+ * return { enabled: true };
409
+ * },
410
+ * });
411
+ */
412
+ static create(config) {
413
+ return new _Extension(config);
414
+ }
415
+ /**
416
+ * Creates a new extension with merged options
417
+ * Original extension is not modified
418
+ *
419
+ * **Note:** Options are merged shallowly using object spread (`...`).
420
+ * Nested objects are replaced entirely, not deeply merged.
421
+ *
422
+ * @param options - Options to merge with existing options
423
+ * @returns New extension instance with merged options
424
+ *
425
+ * @example
426
+ * const configured = MyExtension.configure({ enabled: false });
427
+ *
428
+ * @example
429
+ * // Shallow merge behavior with nested objects:
430
+ * // Given: options = { nested: { a: 1, b: 2 } }
431
+ * // configure({ nested: { b: 3 } })
432
+ * // Result: { nested: { b: 3 } } - 'a' is lost!
433
+ * // To preserve nested values, spread manually:
434
+ * // configure({ nested: { ...original.options.nested, b: 3 } })
435
+ */
436
+ configure(options) {
437
+ const newConfig = {
438
+ ...this.config,
439
+ // Override addOptions to return merged options
440
+ addOptions: () => ({
441
+ ...this.options,
442
+ ...options
443
+ })
444
+ };
445
+ return new _Extension(newConfig);
446
+ }
447
+ /**
448
+ * Returns a fresh, unbound copy built from the same `config`: `editor` reset to
449
+ * null and `options`/`storage` re-derived, while `configure()`/`extend()`
450
+ * results are preserved (they live in `config`). Polymorphic: a `Node`/`Mark`
451
+ * clones to its own subclass.
452
+ *
453
+ * `ExtensionManager` clones every extension so each editor owns its instances
454
+ * and binding one editor can't mutate extensions shared with another.
455
+ */
456
+ clone() {
457
+ const Ctor = this.constructor;
458
+ return new Ctor(this.config);
459
+ }
460
+ /**
461
+ * Creates a new extension with extended configuration
462
+ * Original extension is not modified
463
+ *
464
+ * **Note:** Config is merged shallowly using object spread (`...`).
465
+ * Config properties (like `addCommands`, `addKeyboardShortcuts`) are
466
+ * replaced entirely, not combined with the base extension's config.
467
+ *
468
+ * @param extendedConfig - Configuration to extend/override
469
+ * @returns New extension instance with extended config
470
+ *
471
+ * @example
472
+ * const Extended = MyExtension.extend({
473
+ * name: 'extendedExtension',
474
+ * addCommands() {
475
+ * return { customCommand: () => ({ tr }) => true };
476
+ * },
477
+ * });
478
+ *
479
+ * @example
480
+ * // To preserve base extension's commands while adding new ones:
481
+ * const Extended = BaseExtension.extend({
482
+ * addCommands() {
483
+ * const baseCommands = BaseExtension.config.addCommands?.call(this) ?? {};
484
+ * return {
485
+ * ...baseCommands,
486
+ * newCommand: () => ({ tr }) => true,
487
+ * };
488
+ * },
489
+ * });
490
+ */
491
+ extend(extendedConfig) {
492
+ const newConfig = mergeConfigWithParentBinding(this.config, extendedConfig);
493
+ return new _Extension(newConfig);
494
+ }
495
+ };
496
+
236
497
  // src/ExtensionManager.ts
237
498
  function mergeHTMLAttrs(target, source) {
238
499
  const result = { ...target };
@@ -247,6 +508,13 @@ function mergeHTMLAttrs(target, source) {
247
508
  }
248
509
  return result;
249
510
  }
511
+ function assertOwnExtension(ext) {
512
+ if (ext instanceof Extension) return;
513
+ const foreign = ext?.[EXTENSION_BRAND];
514
+ if (foreign !== true) return;
515
+ const name = typeof ext.name === "string" ? ext.name : "unknown";
516
+ throw new ExtensionConfigurationError(describeForeignExtension(name));
517
+ }
250
518
  var ExtensionManager = class {
251
519
  /**
252
520
  * Processed extensions (flattened, sorted by priority)
@@ -306,8 +574,9 @@ var ExtensionManager = class {
306
574
  "ExtensionManager requires either extensions or schema. Provide at least Document, Text, and Paragraph extensions."
307
575
  );
308
576
  }
309
- const flattened = this.flattenExtensions(options.extensions);
310
- const deduped = this.deduplicateExtensions(flattened);
577
+ const autoIncluded = /* @__PURE__ */ new Set();
578
+ const flattened = this.flattenExtensions(options.extensions, autoIncluded);
579
+ const deduped = this.deduplicateExtensions(flattened, autoIncluded);
311
580
  const cloned = this.cloneExtensions(deduped);
312
581
  this._extensions = this.resolveExtensions(cloned);
313
582
  this.detectConflicts();
@@ -389,33 +658,57 @@ var ExtensionManager = class {
389
658
  /**
390
659
  * Recursively flattens extensions by expanding addExtensions()
391
660
  * This allows extension bundles like StarterKit to work
661
+ *
662
+ * `autoIncluded` collects everything that arrived through an
663
+ * `addExtensions()` rather than from the caller's own list, which is what
664
+ * lets deduplication tell a default apart from a choice.
392
665
  */
393
- flattenExtensions(extensions) {
666
+ flattenExtensions(extensions, autoIncluded, fromBundle = false) {
394
667
  const result = [];
395
668
  for (const ext of extensions) {
669
+ assertOwnExtension(ext);
670
+ if (fromBundle) autoIncluded.add(ext);
396
671
  result.push(ext);
397
672
  const nested = callOrReturn(
398
673
  ext.config.addExtensions,
399
674
  ext
400
675
  );
401
676
  if (nested && nested.length > 0) {
402
- result.push(...this.flattenExtensions(nested));
677
+ result.push(...this.flattenExtensions(nested, autoIncluded, true));
403
678
  }
404
679
  }
405
680
  return result;
406
681
  }
407
682
  /**
408
- * Removes duplicate extensions by name, keeping the last occurrence.
409
- * This allows parent extensions to auto-include children via addExtensions()
410
- * while letting users override with explicitly configured versions.
683
+ * Removes duplicate extensions by name.
684
+ *
685
+ * A version the caller listed themselves always wins over one a bundle
686
+ * included on their behalf, and position does not enter into it. Keeping
687
+ * the last occurrence alone said the same thing only while every bundle was
688
+ * listed first, which is the habit for StarterKit and no rule at all: an
689
+ * extension that includes a default and is written LOWER in the list, as
690
+ * `Export` and its `Print` are, silently replaced the configured copy
691
+ * above it and the caller's options went missing with it.
692
+ *
693
+ * Between two of the same kind the later one still wins, so two bundles
694
+ * offering the same default resolve as they always have.
411
695
  */
412
- deduplicateExtensions(extensions) {
413
- const seen = /* @__PURE__ */ new Map();
696
+ deduplicateExtensions(extensions, autoIncluded) {
697
+ const winners = /* @__PURE__ */ new Map();
414
698
  for (let i = 0; i < extensions.length; i++) {
415
699
  const ext = extensions[i];
416
- if (ext) seen.set(ext.name, i);
700
+ if (!ext) continue;
701
+ const held = winners.get(ext.name);
702
+ if (held === void 0) {
703
+ winners.set(ext.name, i);
704
+ continue;
705
+ }
706
+ const heldIsAuto = autoIncluded.has(extensions[held]);
707
+ const nextIsAuto = autoIncluded.has(ext);
708
+ if (nextIsAuto && !heldIsAuto) continue;
709
+ winners.set(ext.name, i);
417
710
  }
418
- return extensions.filter((ext, i) => seen.get(ext.name) === i);
711
+ return extensions.filter((ext, i) => winners.get(ext.name) === i);
419
712
  }
420
713
  /**
421
714
  * Clone every extension so this editor owns its instances. Extensions hold
@@ -1140,7 +1433,8 @@ function markInputRule(options) {
1140
1433
  return null;
1141
1434
  }
1142
1435
  const { tr } = state;
1143
- tr.replaceWith(start, end, state.schema.text(textContent));
1436
+ const marks = tr.storedMarks ?? tr.doc.resolve(start).marksAcross(tr.doc.resolve(end));
1437
+ tr.replaceWith(start, end, state.schema.text(textContent, marks));
1144
1438
  tr.addMark(start, start + textContent.length, type.create(attributes ?? void 0));
1145
1439
  tr.removeStoredMark(type);
1146
1440
  return tr;
@@ -1232,7 +1526,10 @@ function textInputRule(options) {
1232
1526
  const { find: find2, replace, undoable } = options;
1233
1527
  return new inputrules.InputRule(
1234
1528
  find2,
1235
- (state, _match, start, end) => state.tr.replaceWith(start, end, state.schema.text(replace)),
1529
+ // insertText inherits the replaced range's marks (stored marks first,
1530
+ // then marksAcross), so a replacement inside marked text keeps the
1531
+ // marks instead of punching an unmarked hole into them.
1532
+ (state, _match, start, end) => state.tr.insertText(replace, start, end),
1236
1533
  undoable !== void 0 ? { undoable } : {}
1237
1534
  );
1238
1535
  }
@@ -1470,177 +1767,6 @@ var insertContent = (content) => ({ state, tr, dispatch }) => {
1470
1767
  return true;
1471
1768
  };
1472
1769
 
1473
- // src/Extension.ts
1474
- function mergeConfigWithParentBinding(parentConfig, extendedConfig) {
1475
- const parent = parentConfig;
1476
- const merged = { ...parent };
1477
- for (const [key, value] of Object.entries(extendedConfig)) {
1478
- if (typeof value === "function" && typeof parent[key] === "function") {
1479
- const parentFn = parent[key];
1480
- const childFn = value;
1481
- merged[key] = function(...args) {
1482
- const previousParent = this.parent;
1483
- this.parent = (...pArgs) => parentFn.call(this, ...pArgs);
1484
- const result = childFn.call(this, ...args);
1485
- this.parent = previousParent;
1486
- return result;
1487
- };
1488
- } else {
1489
- merged[key] = value;
1490
- }
1491
- }
1492
- return merged;
1493
- }
1494
- var Extension = class _Extension {
1495
- /**
1496
- * Extension type identifier
1497
- * Used to distinguish between Extension, Node, and Mark
1498
- * Subclasses override this to 'node' or 'mark'
1499
- */
1500
- type = "extension";
1501
- /**
1502
- * Unique extension name
1503
- */
1504
- name;
1505
- /**
1506
- * Extension options (immutable after creation)
1507
- */
1508
- options;
1509
- /**
1510
- * Extension storage (mutable state)
1511
- * Accessible via editor.storage[extensionName]
1512
- */
1513
- storage;
1514
- /**
1515
- * The original configuration object
1516
- */
1517
- config;
1518
- /**
1519
- * Editor instance (set by ExtensionManager after creation)
1520
- * null until ExtensionManager binds it
1521
- */
1522
- editor = null;
1523
- /**
1524
- * Reference to the parent config method when using extend().
1525
- * Set temporarily during config method execution so overridden
1526
- * methods can call `this.parent?.()` to invoke the original.
1527
- */
1528
- parent;
1529
- /**
1530
- * Protected constructor - use Extension.create() instead
1531
- */
1532
- constructor(config) {
1533
- if (!/^[a-z][a-zA-Z0-9]*$/.test(config.name)) {
1534
- throw new Error(
1535
- `Extension name '${config.name}' is invalid. Names must be camelCase starting with a lowercase letter (e.g., 'myExtension').`
1536
- );
1537
- }
1538
- this.config = config;
1539
- this.name = config.name;
1540
- const defaultOptions = callOrReturn(config.addOptions, this);
1541
- this.options = defaultOptions ?? {};
1542
- const defaultStorage = callOrReturn(config.addStorage, this);
1543
- this.storage = defaultStorage ?? {};
1544
- }
1545
- /**
1546
- * Creates a new extension instance
1547
- *
1548
- * @param config - Extension configuration
1549
- * @returns New extension instance
1550
- *
1551
- * @example
1552
- * const MyExtension = Extension.create({
1553
- * name: 'myExtension',
1554
- * addOptions() {
1555
- * return { enabled: true };
1556
- * },
1557
- * });
1558
- */
1559
- static create(config) {
1560
- return new _Extension(config);
1561
- }
1562
- /**
1563
- * Creates a new extension with merged options
1564
- * Original extension is not modified
1565
- *
1566
- * **Note:** Options are merged shallowly using object spread (`...`).
1567
- * Nested objects are replaced entirely, not deeply merged.
1568
- *
1569
- * @param options - Options to merge with existing options
1570
- * @returns New extension instance with merged options
1571
- *
1572
- * @example
1573
- * const configured = MyExtension.configure({ enabled: false });
1574
- *
1575
- * @example
1576
- * // Shallow merge behavior with nested objects:
1577
- * // Given: options = { nested: { a: 1, b: 2 } }
1578
- * // configure({ nested: { b: 3 } })
1579
- * // Result: { nested: { b: 3 } } - 'a' is lost!
1580
- * // To preserve nested values, spread manually:
1581
- * // configure({ nested: { ...original.options.nested, b: 3 } })
1582
- */
1583
- configure(options) {
1584
- const newConfig = {
1585
- ...this.config,
1586
- // Override addOptions to return merged options
1587
- addOptions: () => ({
1588
- ...this.options,
1589
- ...options
1590
- })
1591
- };
1592
- return new _Extension(newConfig);
1593
- }
1594
- /**
1595
- * Returns a fresh, unbound copy built from the same `config`: `editor` reset to
1596
- * null and `options`/`storage` re-derived, while `configure()`/`extend()`
1597
- * results are preserved (they live in `config`). Polymorphic: a `Node`/`Mark`
1598
- * clones to its own subclass.
1599
- *
1600
- * `ExtensionManager` clones every extension so each editor owns its instances
1601
- * and binding one editor can't mutate extensions shared with another.
1602
- */
1603
- clone() {
1604
- const Ctor = this.constructor;
1605
- return new Ctor(this.config);
1606
- }
1607
- /**
1608
- * Creates a new extension with extended configuration
1609
- * Original extension is not modified
1610
- *
1611
- * **Note:** Config is merged shallowly using object spread (`...`).
1612
- * Config properties (like `addCommands`, `addKeyboardShortcuts`) are
1613
- * replaced entirely, not combined with the base extension's config.
1614
- *
1615
- * @param extendedConfig - Configuration to extend/override
1616
- * @returns New extension instance with extended config
1617
- *
1618
- * @example
1619
- * const Extended = MyExtension.extend({
1620
- * name: 'extendedExtension',
1621
- * addCommands() {
1622
- * return { customCommand: () => ({ tr }) => true };
1623
- * },
1624
- * });
1625
- *
1626
- * @example
1627
- * // To preserve base extension's commands while adding new ones:
1628
- * const Extended = BaseExtension.extend({
1629
- * addCommands() {
1630
- * const baseCommands = BaseExtension.config.addCommands?.call(this) ?? {};
1631
- * return {
1632
- * ...baseCommands,
1633
- * newCommand: () => ({ tr }) => true,
1634
- * };
1635
- * },
1636
- * });
1637
- */
1638
- extend(extendedConfig) {
1639
- const newConfig = mergeConfigWithParentBinding(this.config, extendedConfig);
1640
- return new _Extension(newConfig);
1641
- }
1642
- };
1643
-
1644
1770
  // src/helpers/specBuilder.ts
1645
1771
  function buildProseMirrorAttrs(attributeSpecs) {
1646
1772
  const attrs = {};
@@ -1811,6 +1937,8 @@ var Mark = class _Mark extends Extension {
1811
1937
  if (this.config.group !== void 0) spec.group = this.config.group;
1812
1938
  if (this.config.spanning !== void 0)
1813
1939
  spec.spanning = this.config.spanning;
1940
+ if (this.config.keepOnDuplicate !== void 0)
1941
+ spec["keepOnDuplicate"] = this.config.keepOnDuplicate;
1814
1942
  const attributeSpecs = callOrReturn(this.config.addAttributes, this);
1815
1943
  if (attributeSpecs) {
1816
1944
  spec.attrs = buildProseMirrorAttrs(attributeSpecs);
@@ -1848,9 +1976,9 @@ var Mark = class _Mark extends Extension {
1848
1976
  const renderFn = this.config.renderHTML;
1849
1977
  const attrSpecs = attributeSpecs;
1850
1978
  const markInstance = this;
1851
- spec.toDOM = (mark, _inline) => {
1852
- const htmlAttrs = attrSpecs ? buildHTMLAttributes(mark.attrs, attrSpecs) : {};
1853
- return renderFn.call(markInstance, { mark, HTMLAttributes: htmlAttrs });
1979
+ spec.toDOM = (mark2, _inline) => {
1980
+ const htmlAttrs = attrSpecs ? buildHTMLAttributes(mark2.attrs, attrSpecs) : {};
1981
+ return renderFn.call(markInstance, { mark: mark2, HTMLAttributes: htmlAttrs });
1854
1982
  };
1855
1983
  }
1856
1984
  return spec;
@@ -1943,8 +2071,8 @@ var setMark = (markName, attributes) => ({ state, tr, dispatch }) => {
1943
2071
  const from = firstRange.$from.pos;
1944
2072
  const existingMark = tr.storedMarks?.find((m) => m.type === markType) ?? state.storedMarks?.find((m) => m.type === markType) ?? tr.doc.resolve(from).marks().find((m) => m.type === markType) ?? null;
1945
2073
  const mergedAttrs = existingMark ? { ...existingMark.attrs, ...attributes } : attributes;
1946
- const mark = markType.create(mergedAttrs);
1947
- tr.addStoredMark(mark);
2074
+ const mark2 = markType.create(mergedAttrs);
2075
+ tr.addStoredMark(mark2);
1948
2076
  dispatch(tr);
1949
2077
  return true;
1950
2078
  }
@@ -2571,12 +2699,12 @@ var updateAttributes = (typeOrName, attributes) => ({ state, tr, dispatch }) =>
2571
2699
  const markType = state.schema.marks[typeOrName];
2572
2700
  tr.doc.nodesBetween(from, to, (node, pos) => {
2573
2701
  if (!node.isInline) return;
2574
- const mark = markType.isInSet(node.marks);
2575
- if (mark) {
2702
+ const mark2 = markType.isInSet(node.marks);
2703
+ if (mark2) {
2576
2704
  markChanges.push({
2577
2705
  pos,
2578
2706
  nodeSize: node.nodeSize,
2579
- attrs: { ...mark.attrs, ...attributes }
2707
+ attrs: { ...mark2.attrs, ...attributes }
2580
2708
  });
2581
2709
  }
2582
2710
  });
@@ -2622,12 +2750,12 @@ var resetAttributes = (typeOrName, attributeName) => ({ state, tr, dispatch }) =
2622
2750
  const defaultValue = markType.spec.attrs?.[attributeName]?.default;
2623
2751
  tr.doc.nodesBetween(from, to, (node, pos) => {
2624
2752
  if (!node.isInline) return;
2625
- const mark = markType.isInSet(node.marks);
2626
- if (mark) {
2753
+ const mark2 = markType.isInSet(node.marks);
2754
+ if (mark2) {
2627
2755
  markChanges.push({
2628
2756
  pos,
2629
2757
  nodeSize: node.nodeSize,
2630
- attrs: { ...mark.attrs, [attributeName]: defaultValue }
2758
+ attrs: { ...mark2.attrs, [attributeName]: defaultValue }
2631
2759
  });
2632
2760
  }
2633
2761
  });
@@ -2676,8 +2804,7 @@ var builtInCommands = {
2676
2804
  function buildCommandProps(options) {
2677
2805
  const { editor, tr, dispatch, chain, can, commands } = options;
2678
2806
  return {
2679
- // Cast required: CommandPropsEditor is a minimal interface, but CommandProps
2680
- // expects full Editor. Callers ensure the actual editor instance is passed.
2807
+ // CommandPropsEditor is intentionally the minimal shape CommandProps consumes here.
2681
2808
  editor,
2682
2809
  state: editor.view.state,
2683
2810
  tr,
@@ -3105,8 +3232,7 @@ var CommandManager = class {
3105
3232
  buildCommandProps(tr, dispatch) {
3106
3233
  const { editor } = this;
3107
3234
  return {
3108
- // Cast needed: CommandManagerEditor is a minimal interface for dependency injection,
3109
- // but CommandProps expects the full Editor type. Callers pass the actual Editor instance.
3235
+ // CommandManagerEditor is intentionally the minimal shape CommandProps consumes here.
3110
3236
  editor,
3111
3237
  state: editor.state,
3112
3238
  tr,
@@ -3258,6 +3384,17 @@ function resolveOverrides(overrides) {
3258
3384
  if (!overrides) return DEFAULTS;
3259
3385
  return { ...DEFAULTS, ...overrides };
3260
3386
  }
3387
+ var BULLET_MARKERS = ["disc", "circle", "square"];
3388
+ var ORDERED_MARKERS = ["decimal", "lower-alpha", "lower-roman"];
3389
+ function listMarkerDepth(el, container) {
3390
+ let depth = 0;
3391
+ for (let parent = el.parentElement; parent !== null && parent !== container; parent = parent.parentElement) {
3392
+ const tag = parent.tagName;
3393
+ if (tag === "TD" || tag === "TH") break;
3394
+ if (tag === "UL" || tag === "OL") depth += 1;
3395
+ }
3396
+ return depth;
3397
+ }
3261
3398
  function applyInlineStyles(container, overrides) {
3262
3399
  const v = resolveOverrides(overrides);
3263
3400
  if (overrides?.codeHighlighter) {
@@ -3339,16 +3476,27 @@ function applyInlineStyles(container, overrides) {
3339
3476
  case "H6":
3340
3477
  styles = "font-size: 0.9em; font-weight: 700; line-height: 1.25; margin: 1.5em 0 0.5em;";
3341
3478
  break;
3479
+ // `type` is not a schema attribute on either list node, so no document
3480
+ // this editor produced carries one.
3342
3481
  case "UL":
3343
3482
  if (el.getAttribute("data-type") === "taskList") {
3344
3483
  styles = "list-style: none; padding-left: 0; margin: 0.75em 0;";
3345
- } else {
3484
+ } else if (el.hasAttribute("type")) {
3346
3485
  styles = "margin: 0.75em 0; padding-left: 1.5em;";
3486
+ } else {
3487
+ const bullet = BULLET_MARKERS[listMarkerDepth(el, container) % 3] ?? "disc";
3488
+ styles = `margin: 0.75em 0; padding-left: 1.5em; list-style-type: ${bullet};`;
3347
3489
  }
3348
3490
  break;
3349
- case "OL":
3350
- styles = "margin: 0.75em 0; padding-left: 1.5em;";
3491
+ case "OL": {
3492
+ if (el.hasAttribute("type")) {
3493
+ styles = "margin: 0.75em 0; padding-left: 1.5em;";
3494
+ break;
3495
+ }
3496
+ const number = ORDERED_MARKERS[listMarkerDepth(el, container) % 3] ?? "decimal";
3497
+ styles = `margin: 0.75em 0; padding-left: 1.5em; list-style-type: ${number};`;
3351
3498
  break;
3499
+ }
3352
3500
  case "LI":
3353
3501
  if (el.getAttribute("data-type") === "taskItem") {
3354
3502
  styles = "display: flex; align-items: flex-start; gap: 0.5em; margin: 0.25em 0;";
@@ -3510,6 +3658,12 @@ var Editor = class _Editor extends EventEmitter {
3510
3658
  * True while EditorView's constructor runs; see buildViewDispatch.
3511
3659
  */
3512
3660
  _isViewConstructing = false;
3661
+ /**
3662
+ * The `.dm-editor` host this editor painted `dm-notion-mode` onto because
3663
+ * of `preset: 'notion'`. Tracked so destroy() removes only a class the
3664
+ * editor itself added, never one the consumer wrote.
3665
+ */
3666
+ _presetClassHost = null;
3513
3667
  /**
3514
3668
  * Creates a new Editor instance
3515
3669
  *
@@ -3529,6 +3683,15 @@ var Editor = class _Editor extends EventEmitter {
3529
3683
  "Editor requires either schema or extensions. Provide a ProseMirror schema directly, or use extensions like [Document, Paragraph, Text]."
3530
3684
  );
3531
3685
  }
3686
+ warnOnDuplicateProseMirrorCopy("prosemirror-model", model.Fragment, "@domternal/core");
3687
+ warnOnDuplicateProseMirrorCopy("prosemirror-state", state.Plugin, "@domternal/core");
3688
+ warnOnDuplicateProseMirrorCopy("prosemirror-view", view.EditorView, "@domternal/core");
3689
+ warnOnDuplicateProseMirrorCopy("prosemirror-transform", transform.Transform, "@domternal/core");
3690
+ warnOnDuplicateProseMirrorCopy(
3691
+ "@domternal/core",
3692
+ ExtensionConfigurationError,
3693
+ "@domternal/core"
3694
+ );
3532
3695
  this.options = {
3533
3696
  editable: true,
3534
3697
  ...options
@@ -3554,6 +3717,43 @@ var Editor = class _Editor extends EventEmitter {
3554
3717
  get isEditable() {
3555
3718
  return this.view ? this.view.editable : this.options.editable ?? true;
3556
3719
  }
3720
+ /**
3721
+ * The resolved editing-experience preset.
3722
+ *
3723
+ * The `preset` option wins when provided (so an explicit 'classic' can
3724
+ * opt out of everything). Otherwise a `dm-notion-mode` class on or above
3725
+ * the view counts as 'notion': consumers that predate the option declare
3726
+ * Notion mode with the theme class alone, and behavior must follow what
3727
+ * the user actually sees. Resolved on every read, not cached, so a class
3728
+ * toggled at runtime is picked up.
3729
+ */
3730
+ get preset() {
3731
+ if (this.options.preset) {
3732
+ return this.options.preset;
3733
+ }
3734
+ if (this.view?.dom.closest(".dm-notion-mode")) {
3735
+ return "notion";
3736
+ }
3737
+ return "classic";
3738
+ }
3739
+ /**
3740
+ * Paints `dm-notion-mode` on the `.dm-editor` host when the editor was
3741
+ * created with `preset: 'notion'`. Runs during creation, and framework
3742
+ * wrappers call it again after adopting the view's DOM: they construct
3743
+ * the editor in a detached element, so the creation-time run cannot see
3744
+ * the host yet. Idempotent; a no-op for any other preset. Only a class
3745
+ * added here is removed again on destroy.
3746
+ */
3747
+ adoptPresetClass() {
3748
+ if (this.options.preset !== "notion" || this._presetClassHost) {
3749
+ return;
3750
+ }
3751
+ const host = this.view.dom.closest(".dm-editor");
3752
+ if (host && !host.classList.contains("dm-notion-mode")) {
3753
+ host.classList.add("dm-notion-mode");
3754
+ this._presetClassHost = host;
3755
+ }
3756
+ }
3557
3757
  /**
3558
3758
  * Checks if the editor content is empty
3559
3759
  */
@@ -3639,11 +3839,11 @@ var Editor = class _Editor extends EventEmitter {
3639
3839
  if (markType) {
3640
3840
  if (selection.empty) {
3641
3841
  const storedMarks = state.storedMarks ?? $from.marks();
3642
- const hasMark = storedMarks.some((mark) => mark.type === markType);
3842
+ const hasMark = storedMarks.some((mark2) => mark2.type === markType);
3643
3843
  if (!hasMark) return false;
3644
3844
  if (attrs) {
3645
- const mark = storedMarks.find((m) => m.type === markType);
3646
- return mark ? this.matchAttributes(mark.attrs, attrs) : false;
3845
+ const mark2 = storedMarks.find((m) => m.type === markType);
3846
+ return mark2 ? this.matchAttributes(mark2.attrs, attrs) : false;
3647
3847
  }
3648
3848
  return true;
3649
3849
  }
@@ -3718,8 +3918,8 @@ var Editor = class _Editor extends EventEmitter {
3718
3918
  const markType = schema.marks[name];
3719
3919
  if (markType) {
3720
3920
  const marks = state.storedMarks ?? $from.marks();
3721
- const mark = marks.find((m) => m.type === markType);
3722
- return mark ? { ...mark.attrs } : {};
3921
+ const mark2 = marks.find((m) => m.type === markType);
3922
+ return mark2 ? { ...mark2.attrs } : {};
3723
3923
  }
3724
3924
  const nodeType = schema.nodes[name];
3725
3925
  if (nodeType) {
@@ -3786,11 +3986,7 @@ var Editor = class _Editor extends EventEmitter {
3786
3986
  */
3787
3987
  getText(options = {}) {
3788
3988
  const { blockSeparator = "\n\n" } = options;
3789
- return this.state.doc.textBetween(
3790
- 0,
3791
- this.state.doc.content.size,
3792
- blockSeparator
3793
- );
3989
+ return this.state.doc.textBetween(0, this.state.doc.content.size, blockSeparator);
3794
3990
  }
3795
3991
  /**
3796
3992
  * Executes a command with proper CommandProps
@@ -3900,6 +4096,10 @@ var Editor = class _Editor extends EventEmitter {
3900
4096
  }
3901
4097
  this.emit("destroy");
3902
4098
  this.options.onDestroy?.();
4099
+ if (this._presetClassHost) {
4100
+ this._presetClassHost.classList.remove("dm-notion-mode");
4101
+ this._presetClassHost = null;
4102
+ }
3903
4103
  this.view.destroy();
3904
4104
  this._extensionManager.destroy();
3905
4105
  this.removeAllListeners();
@@ -3945,10 +4145,7 @@ var Editor = class _Editor extends EventEmitter {
3945
4145
  this._extensionManager.validateSchema();
3946
4146
  let doc;
3947
4147
  try {
3948
- doc = createDocument(
3949
- this.options.content ?? null,
3950
- this._extensionManager.schema
3951
- );
4148
+ doc = createDocument(this.options.content ?? null, this._extensionManager.schema);
3952
4149
  } catch (error) {
3953
4150
  const contentError = error instanceof Error ? error : new Error(String(error));
3954
4151
  this.emit("contentError", {
@@ -3984,7 +4181,10 @@ var Editor = class _Editor extends EventEmitter {
3984
4181
  }),
3985
4182
  ...Object.keys(nodeViews).length > 0 ? { nodeViews } : {},
3986
4183
  // Clipboard transform - apply user-provided transform (e.g. inlineStyles) on copy/cut
3987
- ...this.options.clipboardHTMLTransform ? this.buildClipboardSerializer(this.options.clipboardHTMLTransform, this._extensionManager.schema) : {},
4184
+ ...this.options.clipboardHTMLTransform ? this.buildClipboardSerializer(
4185
+ this.options.clipboardHTMLTransform,
4186
+ this._extensionManager.schema
4187
+ ) : {},
3988
4188
  // Handle focus/blur events
3989
4189
  handleDOMEvents: {
3990
4190
  focus: (_view, event) => {
@@ -4002,6 +4202,7 @@ var Editor = class _Editor extends EventEmitter {
4002
4202
  }
4003
4203
  });
4004
4204
  this._isViewConstructing = false;
4205
+ this.adoptPresetClass();
4005
4206
  this.emit("mount", { editor: this, view: this.view });
4006
4207
  this.options.onMount?.({ editor: this, view: this.view });
4007
4208
  this.commandManager = new CommandManager(this);
@@ -4238,22 +4439,20 @@ function refocusEditorAfterCommand(view) {
4238
4439
  }
4239
4440
 
4240
4441
  // src/utils/defaultBubbleContexts.ts
4241
- var NOTION_MODE_CLASS = "dm-notion-mode";
4242
4442
  var NOTION_TEXT_CONTEXT = Object.freeze([
4243
- // `ai` leads (Notion's "Ask AI"); skipped with its leading separator when
4244
- // the pro extension is absent, exactly like `mathInline`.
4245
4443
  "ai",
4444
+ "comment",
4445
+ "|",
4446
+ "heading",
4447
+ "|",
4448
+ "link",
4246
4449
  "|",
4247
4450
  "bold",
4248
4451
  "italic",
4249
4452
  "underline",
4250
4453
  "strike",
4251
4454
  "code",
4252
- "mathInline",
4253
- "|",
4254
- "link",
4255
- "|",
4256
- "textAlign"
4455
+ "mathInline"
4257
4456
  ]);
4258
4457
  var STANDARD_TEXT_CONTEXT = Object.freeze([
4259
4458
  "bold",
@@ -4266,11 +4465,210 @@ var STANDARD_TEXT_CONTEXT = Object.freeze([
4266
4465
  "link"
4267
4466
  ]);
4268
4467
  function defaultBubbleContexts(editor) {
4269
- const inNotionMode = editor.view.dom.closest("." + NOTION_MODE_CLASS) !== null;
4270
- const text = inNotionMode ? NOTION_TEXT_CONTEXT : STANDARD_TEXT_CONTEXT;
4468
+ const text = editor.preset === "notion" ? NOTION_TEXT_CONTEXT : STANDARD_TEXT_CONTEXT;
4271
4469
  return { text: [...text] };
4272
4470
  }
4273
4471
 
4472
+ // src/utils/collapseSeparators.ts
4473
+ function collapseSeparators(items) {
4474
+ const out = [];
4475
+ for (const item of items) {
4476
+ if (item.type !== "separator") {
4477
+ out.push(item);
4478
+ continue;
4479
+ }
4480
+ if (out.length === 0) continue;
4481
+ if (out[out.length - 1]?.type === "separator") continue;
4482
+ out.push(item);
4483
+ }
4484
+ while (out.length > 0 && out[out.length - 1]?.type === "separator") out.pop();
4485
+ return out.length === items.length ? items : out;
4486
+ }
4487
+
4488
+ // src/utils/bubbleMenuResolver.ts
4489
+ function buildBubbleItemMaps(editor) {
4490
+ const itemMap = /* @__PURE__ */ new Map();
4491
+ const dropdownMap = /* @__PURE__ */ new Map();
4492
+ for (const item of editor.toolbarItems) {
4493
+ if (item.type === "button") {
4494
+ itemMap.set(item.name, item);
4495
+ } else if (item.type === "dropdown") {
4496
+ dropdownMap.set(item.name, item);
4497
+ for (const sub of item.items) {
4498
+ itemMap.set(sub.name, sub);
4499
+ }
4500
+ }
4501
+ }
4502
+ return {
4503
+ itemMap,
4504
+ dropdownMap,
4505
+ bubbleDefaults: buildBubbleDefaults(editor)
4506
+ };
4507
+ }
4508
+ function buildBubbleDefaults(editor) {
4509
+ const byCtx = /* @__PURE__ */ new Map();
4510
+ const addItem = (btn) => {
4511
+ const ctx = btn["bubbleMenu"];
4512
+ if (!ctx) return;
4513
+ let arr = byCtx.get(ctx);
4514
+ if (!arr) {
4515
+ arr = [];
4516
+ byCtx.set(ctx, arr);
4517
+ }
4518
+ arr.push(btn);
4519
+ };
4520
+ for (const item of editor.toolbarItems) {
4521
+ if (item.type === "button") addItem(item);
4522
+ else if (item.type === "dropdown") {
4523
+ for (const sub of item.items) addItem(sub);
4524
+ }
4525
+ }
4526
+ const result = /* @__PURE__ */ new Map();
4527
+ for (const [ctx, ctxItems] of byCtx) {
4528
+ ctxItems.sort((a, b) => (b.priority ?? 100) - (a.priority ?? 100));
4529
+ const list = [];
4530
+ let lastGroup;
4531
+ let sepIdx = 0;
4532
+ for (const item of ctxItems) {
4533
+ if (lastGroup !== void 0 && item.group !== lastGroup) {
4534
+ list.push({ type: "separator", name: `bsep-${String(sepIdx++)}` });
4535
+ }
4536
+ list.push(item);
4537
+ lastGroup = item.group;
4538
+ }
4539
+ result.set(ctx, list);
4540
+ }
4541
+ return result;
4542
+ }
4543
+ function resolveBubbleNames(names, itemMap, dropdownMap) {
4544
+ const result = [];
4545
+ let sepIdx = 0;
4546
+ for (const name of names) {
4547
+ if (name === "|") {
4548
+ result.push({ type: "separator", name: `sep-${String(sepIdx++)}` });
4549
+ continue;
4550
+ }
4551
+ const dropdown = dropdownMap.get(name);
4552
+ if (dropdown) {
4553
+ result.push(dropdown);
4554
+ continue;
4555
+ }
4556
+ const item = itemMap.get(name);
4557
+ if (item) result.push(item);
4558
+ }
4559
+ return result;
4560
+ }
4561
+ function getBubbleFormatItems(itemMap) {
4562
+ return Array.from(itemMap.values()).filter((item) => item.group === "format").sort((a, b) => (b.priority ?? 100) - (a.priority ?? 100));
4563
+ }
4564
+ function detectBubbleContext(selection, ctxs) {
4565
+ if ("$anchorCell" in selection) return null;
4566
+ if (selection.node) return selection.node.type.name;
4567
+ if (selection.empty) return null;
4568
+ const fromCell = findCellNode(selection.$from);
4569
+ if (fromCell) {
4570
+ const toCell = findCellNode(selection.$to);
4571
+ if (toCell && fromCell !== toCell) return null;
4572
+ return "table";
4573
+ }
4574
+ const fromName = selection.$from.parent.type.name;
4575
+ if (fromName in ctxs) return fromName;
4576
+ if ("text" in ctxs && selection.$from.parent.type.spec.marks !== "") return "text";
4577
+ const toName = selection.$to.parent.type.name;
4578
+ if (toName in ctxs) return toName;
4579
+ if ("text" in ctxs && selection.$to.parent.type.spec.marks !== "") return "text";
4580
+ return null;
4581
+ }
4582
+ function filterBubbleItemsBySchema(editor, contextName, schemaItems) {
4583
+ if (contextName === "text" || contextName === "table") return schemaItems;
4584
+ const schema = editor.state.schema;
4585
+ if (!schema) return schemaItems;
4586
+ const nodeType = schema.nodes[contextName];
4587
+ if (!nodeType) return schemaItems;
4588
+ return schemaItems.filter((item) => {
4589
+ const markName = typeof item.isActive === "string" ? item.isActive : null;
4590
+ if (!markName) return true;
4591
+ const markType = schema.marks[markName];
4592
+ if (!markType) return true;
4593
+ return nodeType.allowsMarkType(markType);
4594
+ });
4595
+ }
4596
+ function isInsideTableCell($pos) {
4597
+ for (let d = $pos.depth; d > 0; d--) {
4598
+ const name = $pos.node(d).type.name;
4599
+ if (name === "tableCell" || name === "tableHeader") return true;
4600
+ }
4601
+ return false;
4602
+ }
4603
+ function findCellNode(pos) {
4604
+ for (let d = pos.depth; d > 0; d--) {
4605
+ const node = pos.node(d);
4606
+ if (node.type.name === "tableCell" || node.type.name === "tableHeader") return node;
4607
+ }
4608
+ return null;
4609
+ }
4610
+ function resolveBubbleMenuItems(options) {
4611
+ return collapseSeparators(pickBubbleMenuItems(options));
4612
+ }
4613
+ function pickBubbleMenuItems({
4614
+ editor,
4615
+ maps,
4616
+ contexts,
4617
+ fallbackItems
4618
+ }) {
4619
+ const selection = editor.state.selection;
4620
+ if (contexts) {
4621
+ const ctx = detectBubbleContext(selection, contexts);
4622
+ if (!ctx) return [];
4623
+ if (ctx in contexts) {
4624
+ const val = contexts[ctx];
4625
+ if (val === null || Array.isArray(val) && val.length === 0) return [];
4626
+ if (val === true) {
4627
+ return filterBubbleItemsBySchema(editor, ctx, getBubbleFormatItems(maps.itemMap));
4628
+ }
4629
+ if (Array.isArray(val)) {
4630
+ const resolved = resolveBubbleNames(val, maps.itemMap, maps.dropdownMap);
4631
+ const buttons = resolved.filter(
4632
+ (item) => item.type !== "separator"
4633
+ );
4634
+ const allowed = new Set(
4635
+ filterBubbleItemsBySchema(editor, ctx, buttons).map((item) => item.name)
4636
+ );
4637
+ return resolved.filter(
4638
+ (item) => item.type === "separator" || allowed.has(item.name)
4639
+ );
4640
+ }
4641
+ }
4642
+ return maps.bubbleDefaults.get(ctx) ?? [];
4643
+ }
4644
+ if (selection.node && maps.bubbleDefaults.has(selection.node.type.name)) {
4645
+ return maps.bubbleDefaults.get(selection.node.type.name) ?? [];
4646
+ }
4647
+ return fallbackItems;
4648
+ }
4649
+ function createBubbleShouldShow(maps, contexts) {
4650
+ if (contexts) {
4651
+ return ({ state }) => {
4652
+ const selection = state.selection;
4653
+ const ctx = detectBubbleContext(selection, contexts);
4654
+ if (!ctx) return false;
4655
+ if (ctx in contexts) {
4656
+ const val = contexts[ctx];
4657
+ if (val === null) return false;
4658
+ return val === true || Array.isArray(val) && val.length > 0;
4659
+ }
4660
+ return maps.bubbleDefaults.has(ctx);
4661
+ };
4662
+ }
4663
+ return ({ state }) => {
4664
+ const selection = state.selection;
4665
+ if (selection.empty) return false;
4666
+ if (selection.node) return maps.bubbleDefaults.has(selection.node.type.name);
4667
+ if (isInsideTableCell(selection.$from)) return false;
4668
+ return selection.$from.parent.type.spec.marks !== "" || selection.$to.parent.type.spec.marks !== "";
4669
+ };
4670
+ }
4671
+
4274
4672
  // src/utils/insertAsListItemChild.ts
4275
4673
  var LIST_ITEM_TYPES3 = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
4276
4674
  var LIST_WRAPPER_TYPES2 = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
@@ -5026,7 +5424,8 @@ function groupFloatingMenuItems(items) {
5026
5424
  if (!list) {
5027
5425
  list = [];
5028
5426
  map.set(name, list);
5029
- order.push(name);
5427
+ if (name === "") order.unshift(name);
5428
+ else order.push(name);
5030
5429
  }
5031
5430
  list.push(item);
5032
5431
  }
@@ -5238,12 +5637,13 @@ var FloatingMenuController = class _FloatingMenuController {
5238
5637
  */
5239
5638
  updateDisabledStates() {
5240
5639
  let changed = false;
5241
- let canProxy = null;
5242
- try {
5243
- canProxy = this.editor.can();
5244
- } catch {
5245
- canProxy = null;
5246
- }
5640
+ const canProxy = (() => {
5641
+ try {
5642
+ return this.editor.can();
5643
+ } catch {
5644
+ return null;
5645
+ }
5646
+ })();
5247
5647
  for (const item of this._flatItems) {
5248
5648
  const was = this._disabledMap.get(item.name) ?? false;
5249
5649
  let now = false;
@@ -5560,7 +5960,8 @@ var defaultIcons = {
5560
5960
  plus: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z"/></svg>',
5561
5961
  dotsSixVertical: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M108,60A16,16,0,1,1,92,44,16,16,0,0,1,108,60Zm56,16A16,16,0,1,0,148,60,16,16,0,0,0,164,76ZM92,112a16,16,0,1,0,16,16A16,16,0,0,0,92,112Zm72,0a16,16,0,1,0,16,16A16,16,0,0,0,164,112ZM92,180a16,16,0,1,0,16,16A16,16,0,0,0,92,180Zm72,0a16,16,0,1,0,16,16A16,16,0,0,0,164,180Z"/></svg>',
5562
5962
  dotsThree: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M140,128a12,12,0,1,1-12-12A12,12,0,0,1,140,128ZM64,116a12,12,0,1,0,12,12A12,12,0,0,0,64,116Zm128,0a12,12,0,1,0,12,12A12,12,0,0,0,192,116Z"/></svg>',
5563
- copy: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z"/></svg>'
5963
+ copy: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z"/></svg>',
5964
+ printer: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M214.67,72H200V40a8,8,0,0,0-8-8H64a8,8,0,0,0-8,8V72H41.33C27.36,72,16,82.77,16,96v80a8,8,0,0,0,8,8H56v32a8,8,0,0,0,8,8H192a8,8,0,0,0,8-8V184h32a8,8,0,0,0,8-8V96C240,82.77,228.64,72,214.67,72ZM72,48H184V72H72ZM184,208H72V160H184Zm40-40H200V152a8,8,0,0,0-8-8H64a8,8,0,0,0-8,8v16H32V96c0-4.34,4.28-8,9.33-8H214.67c5.05,0,9.33,3.66,9.33,8Zm-24-52a12,12,0,1,1-12-12A12,12,0,0,1,200,116Z"/></svg>'
5564
5965
  };
5565
5966
 
5566
5967
  // src/nodes/Document.ts
@@ -7092,6 +7493,7 @@ var TaskList = Node2.create({
7092
7493
  // src/marks/Bold.ts
7093
7494
  var Bold = Mark.create({
7094
7495
  name: "bold",
7496
+ group: "formatting",
7095
7497
  addOptions() {
7096
7498
  return {
7097
7499
  HTMLAttributes: {}
@@ -7169,6 +7571,7 @@ var Bold = Mark.create({
7169
7571
  // src/marks/Italic.ts
7170
7572
  var Italic = Mark.create({
7171
7573
  name: "italic",
7574
+ group: "formatting",
7172
7575
  addOptions() {
7173
7576
  return {
7174
7577
  HTMLAttributes: {}
@@ -7248,6 +7651,7 @@ var Italic = Mark.create({
7248
7651
  // src/marks/Underline.ts
7249
7652
  var Underline = Mark.create({
7250
7653
  name: "underline",
7654
+ group: "formatting",
7251
7655
  addOptions() {
7252
7656
  return {
7253
7657
  HTMLAttributes: {}
@@ -7300,6 +7704,7 @@ var Underline = Mark.create({
7300
7704
  // src/marks/Strike.ts
7301
7705
  var Strike = Mark.create({
7302
7706
  name: "strike",
7707
+ group: "formatting",
7303
7708
  addOptions() {
7304
7709
  return {
7305
7710
  HTMLAttributes: {}
@@ -7371,9 +7776,14 @@ var Code = Mark.create({
7371
7776
  HTMLAttributes: {}
7372
7777
  };
7373
7778
  },
7374
- // Code mark is exclusive - it cannot be combined with other marks
7375
- // ProseMirror uses '_' to mean "exclude all marks"
7376
- excludes: "_",
7779
+ // Code cannot be combined with other FORMATTING marks (bold, italic,
7780
+ // color...), but semantic marks outside the group (link, a comment
7781
+ // thread anchor) survive: code excludes the 'formatting' group instead
7782
+ // of '_' (everything). Third-party formatting marks opt into the same
7783
+ // exclusion by declaring group: 'formatting'. Code is in the group
7784
+ // itself, which also keeps it self-exclusive.
7785
+ group: "formatting",
7786
+ excludes: "formatting",
7377
7787
  // Code should not span across multiple nodes
7378
7788
  spanning: false,
7379
7789
  parseHTML() {
@@ -7434,7 +7844,7 @@ function linkClickPlugin(options) {
7434
7844
  if (!view.editable) {
7435
7845
  return false;
7436
7846
  }
7437
- let link = null;
7847
+ let link;
7438
7848
  if (event.target instanceof HTMLAnchorElement) {
7439
7849
  link = event.target;
7440
7850
  } else {
@@ -7861,6 +8271,7 @@ var Link = Mark.create({
7861
8271
  // src/marks/Subscript.ts
7862
8272
  var Subscript = Mark.create({
7863
8273
  name: "subscript",
8274
+ group: "formatting",
7864
8275
  // Mutual exclusion handled in toggle commands (not schema)
7865
8276
  // so can() dry-run works correctly for toolbar disabled state
7866
8277
  excludes: "",
@@ -7922,6 +8333,7 @@ var Subscript = Mark.create({
7922
8333
  // src/marks/Superscript.ts
7923
8334
  var Superscript = Mark.create({
7924
8335
  name: "superscript",
8336
+ group: "formatting",
7925
8337
  // Mutual exclusion handled in toggle commands (not schema)
7926
8338
  // so can() dry-run works correctly for toolbar disabled state
7927
8339
  excludes: "",
@@ -7983,6 +8395,7 @@ var Superscript = Mark.create({
7983
8395
  // src/marks/TextStyle.ts
7984
8396
  var TextStyle = Mark.create({
7985
8397
  name: "textStyle",
8398
+ group: "formatting",
7986
8399
  // Lower priority so it renders after other marks
7987
8400
  priority: 101,
7988
8401
  addOptions() {
@@ -8039,7 +8452,7 @@ var TextStyle = Mark.create({
8039
8452
  let hasEmptyTextStyle = false;
8040
8453
  tr.doc.nodesBetween(from, to, (node) => {
8041
8454
  const textStyleMark = node.marks.find(
8042
- (mark) => mark.type.name === this.name
8455
+ (mark2) => mark2.type.name === this.name
8043
8456
  );
8044
8457
  if (textStyleMark) {
8045
8458
  const hasNonNullAttr = Object.values(textStyleMark.attrs).some(
@@ -8053,9 +8466,9 @@ var TextStyle = Mark.create({
8053
8466
  if (!hasEmptyTextStyle) return false;
8054
8467
  if (dispatch) {
8055
8468
  tr.doc.nodesBetween(from, to, (node, pos) => {
8056
- const mark = node.marks.find((m) => m.type === markType);
8057
- if (mark) {
8058
- const hasActiveAttr = Object.values(mark.attrs).some(
8469
+ const mark2 = node.marks.find((m) => m.type === markType);
8470
+ if (mark2) {
8471
+ const hasActiveAttr = Object.values(mark2.attrs).some(
8059
8472
  (v) => v !== null && v !== void 0
8060
8473
  );
8061
8474
  if (!hasActiveAttr) {
@@ -8560,11 +8973,7 @@ var Typography = Extension.create({
8560
8973
  rules.push(
8561
8974
  new inputrules.InputRule(/"([^"]+)"$/, (state, match, start, end) => {
8562
8975
  const text = match[1] ?? "";
8563
- return state.tr.replaceWith(
8564
- start,
8565
- end,
8566
- state.schema.text(openDoubleQuote + text + closeDoubleQuote)
8567
- );
8976
+ return state.tr.insertText(openDoubleQuote + text + closeDoubleQuote, start, end);
8568
8977
  })
8569
8978
  );
8570
8979
  rules.push(
@@ -8572,12 +8981,10 @@ var Typography = Extension.create({
8572
8981
  const text = match[1] ?? "";
8573
8982
  const prefix = match[0].charAt(0);
8574
8983
  const hasPrefix = prefix !== "'";
8575
- return state.tr.replaceWith(
8984
+ return state.tr.insertText(
8985
+ hasPrefix ? prefix + openSingleQuote + text + closeSingleQuote : openSingleQuote + text + closeSingleQuote,
8576
8986
  start,
8577
- end,
8578
- state.schema.text(
8579
- hasPrefix ? prefix + openSingleQuote + text + closeSingleQuote : openSingleQuote + text + closeSingleQuote
8580
- )
8987
+ end
8581
8988
  );
8582
8989
  })
8583
8990
  );
@@ -8842,6 +9249,9 @@ function generateUUID() {
8842
9249
  });
8843
9250
  }
8844
9251
  var uniqueIDPluginKey = new state.PluginKey("uniqueID");
9252
+ function isWithin(from, to, ranges) {
9253
+ return ranges.some((range) => from >= range.from && to <= range.to);
9254
+ }
8845
9255
  var UniqueID = Extension.create({
8846
9256
  name: "uniqueID",
8847
9257
  addOptions() {
@@ -8891,12 +9301,27 @@ var UniqueID = Extension.create({
8891
9301
  addProseMirrorPlugins() {
8892
9302
  const { types, attributeName, generateID, filterDuplicates } = this.options;
8893
9303
  const editor = this.editor;
9304
+ const replacedRanges = (view) => {
9305
+ if (view?.dragging) return [];
9306
+ const ranges = view?.state?.selection?.ranges;
9307
+ if (ranges === void 0) return [];
9308
+ const replaced = [];
9309
+ for (const range of ranges) {
9310
+ if (range.$to.pos > range.$from.pos) {
9311
+ replaced.push({ from: range.$from.pos, to: range.$to.pos });
9312
+ }
9313
+ }
9314
+ return replaced;
9315
+ };
8894
9316
  const transformPastedSlice = (slice, view) => {
8895
9317
  if (view?.dragging?.move === true) return slice;
8896
9318
  const existingIDs = /* @__PURE__ */ new Set();
8897
- editor?.state.doc.descendants((node) => {
9319
+ const replaced = replacedRanges(view);
9320
+ editor?.state.doc.descendants((node, pos) => {
9321
+ if (isWithin(pos, pos + node.nodeSize, replaced)) return false;
8898
9322
  const id = node.attrs[attributeName];
8899
9323
  if (id) existingIDs.add(id);
9324
+ return true;
8900
9325
  });
8901
9326
  const transformNode = (node) => {
8902
9327
  if (!types.includes(node.type.name)) {
@@ -9746,13 +10171,13 @@ var Highlight = Extension.create({
9746
10171
  let hasHighlight = false;
9747
10172
  if (empty) {
9748
10173
  const marks = state.storedMarks ?? state.doc.resolve(from).marks();
9749
- const mark = markType.isInSet(marks);
9750
- hasHighlight = !!mark?.attrs["backgroundColor"] || !!mark?.attrs["backgroundColorToken"];
10174
+ const mark2 = markType.isInSet(marks);
10175
+ hasHighlight = !!mark2?.attrs["backgroundColor"] || !!mark2?.attrs["backgroundColorToken"];
9751
10176
  } else {
9752
10177
  state.doc.nodesBetween(from, to, (node) => {
9753
10178
  if (hasHighlight) return false;
9754
- const mark = markType.isInSet(node.marks);
9755
- if (mark?.attrs["backgroundColor"] || mark?.attrs["backgroundColorToken"]) {
10179
+ const mark2 = markType.isInSet(node.marks);
10180
+ if (mark2?.attrs["backgroundColor"] || mark2?.attrs["backgroundColorToken"]) {
9756
10181
  hasHighlight = true;
9757
10182
  return false;
9758
10183
  }
@@ -9799,7 +10224,8 @@ var Highlight = Extension.create({
9799
10224
  const content = match[1];
9800
10225
  if (!content) return null;
9801
10226
  const { tr } = state;
9802
- tr.replaceWith(start, end, state.schema.text(content));
10227
+ const marks = tr.storedMarks ?? tr.doc.resolve(start).marksAcross(tr.doc.resolve(end));
10228
+ tr.replaceWith(start, end, state.schema.text(content, marks));
9803
10229
  tr.addMark(
9804
10230
  start,
9805
10231
  start + content.length,
@@ -10095,6 +10521,153 @@ var ClearFormatting = Extension.create({
10095
10521
  ];
10096
10522
  }
10097
10523
  });
10524
+
10525
+ // src/extensions/Print.ts
10526
+ var ROOT_CLASS = "dm-print-root";
10527
+ var ANCESTOR_CLASS = "dm-print-ancestor";
10528
+ var PRINTING_CLASS = "dm-printing";
10529
+ var marked = /* @__PURE__ */ new Set();
10530
+ var printing = false;
10531
+ var Print = Extension.create({
10532
+ name: "print",
10533
+ addOptions() {
10534
+ return {
10535
+ toolbar: true,
10536
+ root: null,
10537
+ isolateNativePrint: false
10538
+ };
10539
+ },
10540
+ addStorage() {
10541
+ return { cleanup: null };
10542
+ },
10543
+ addCommands() {
10544
+ return {
10545
+ printDocument: () => ({ dispatch }) => {
10546
+ if (!dispatch) return true;
10547
+ const editor = this.editor;
10548
+ if (!editor || typeof window === "undefined") return false;
10549
+ const root = resolveRoot(editor, this.options.root);
10550
+ if (!root) return false;
10551
+ mark(root);
10552
+ try {
10553
+ emit(editor, "beforePrint", { root });
10554
+ window.print();
10555
+ } finally {
10556
+ unmark();
10557
+ emit(editor, "afterPrint", void 0);
10558
+ }
10559
+ return true;
10560
+ }
10561
+ };
10562
+ },
10563
+ addToolbarItems() {
10564
+ if (!this.options.toolbar) return [];
10565
+ return [
10566
+ {
10567
+ type: "button",
10568
+ name: "print",
10569
+ command: "printDocument",
10570
+ icon: "printer",
10571
+ label: "Print",
10572
+ shortcut: "Mod-P",
10573
+ group: "document",
10574
+ priority: 100,
10575
+ // Reading a document out to paper is not editing it, so the button
10576
+ // stays live in a read-only editor.
10577
+ allowReadOnly: true
10578
+ }
10579
+ ];
10580
+ },
10581
+ addKeyboardShortcuts() {
10582
+ return {
10583
+ // Only bound while the caret is in the editor, which is exactly when
10584
+ // the reader means "print this document" rather than "print this
10585
+ // page". Everywhere else the browser's own Ctrl/Cmd+P is untouched.
10586
+ "Mod-p": () => this.editor?.commands.printDocument() ?? false
10587
+ };
10588
+ },
10589
+ onCreate() {
10590
+ if (!this.options.isolateNativePrint) return;
10591
+ if (typeof window === "undefined") return;
10592
+ const editor = this.editor;
10593
+ if (!editor) return;
10594
+ const resolve = this.options.root;
10595
+ const before = () => {
10596
+ if (printing) return;
10597
+ const root = resolveRoot(editor, resolve);
10598
+ if (!root) return;
10599
+ mark(root);
10600
+ emit(editor, "beforePrint", { root });
10601
+ };
10602
+ const after = () => {
10603
+ if (!printing) return;
10604
+ unmark();
10605
+ emit(editor, "afterPrint", void 0);
10606
+ };
10607
+ window.addEventListener("beforeprint", before);
10608
+ window.addEventListener("afterprint", after);
10609
+ let detachMedia = null;
10610
+ const onMediaChange = (event) => {
10611
+ if (event.matches) before();
10612
+ else after();
10613
+ };
10614
+ if (typeof window.matchMedia === "function") {
10615
+ const media = window.matchMedia("print");
10616
+ if (typeof media.addEventListener === "function") {
10617
+ media.addEventListener("change", onMediaChange);
10618
+ detachMedia = () => {
10619
+ media.removeEventListener("change", onMediaChange);
10620
+ };
10621
+ }
10622
+ }
10623
+ this.storage.cleanup = () => {
10624
+ window.removeEventListener("beforeprint", before);
10625
+ window.removeEventListener("afterprint", after);
10626
+ detachMedia?.();
10627
+ };
10628
+ },
10629
+ onDestroy() {
10630
+ this.storage.cleanup?.();
10631
+ this.storage.cleanup = null;
10632
+ unmark();
10633
+ }
10634
+ });
10635
+ function resolveRoot(editor, resolve) {
10636
+ if (resolve) return resolve(editor);
10637
+ const dom = editor.view.dom;
10638
+ return dom.closest(".dm-editor") ?? dom;
10639
+ }
10640
+ function mark(root) {
10641
+ root.classList.add(ROOT_CLASS);
10642
+ marked.add(root);
10643
+ let node = parentOf(root);
10644
+ while (node) {
10645
+ node.classList.add(ANCESTOR_CLASS);
10646
+ marked.add(node);
10647
+ node = parentOf(node);
10648
+ }
10649
+ document.body.classList.add(PRINTING_CLASS);
10650
+ printing = true;
10651
+ }
10652
+ function parentOf(node) {
10653
+ if (node.parentElement) return node.parentElement;
10654
+ if (typeof ShadowRoot === "undefined") return null;
10655
+ const root = node.getRootNode();
10656
+ return root instanceof ShadowRoot ? root.host : null;
10657
+ }
10658
+ function unmark() {
10659
+ printing = false;
10660
+ if (typeof document === "undefined") return;
10661
+ document.body.classList.remove(PRINTING_CLASS);
10662
+ for (const el of marked) {
10663
+ el.classList.remove(ROOT_CLASS, ANCESTOR_CLASS);
10664
+ }
10665
+ marked.clear();
10666
+ }
10667
+ function emit(editor, name, payload) {
10668
+ const bus = editor;
10669
+ bus.emit?.(name, payload);
10670
+ }
10098
10671
  var linkPopoverPluginKey = new state.PluginKey("linkPopover");
10099
10672
  function linkPopoverPlugin({ editor, markType, protocols }) {
10100
10673
  const el = document.createElement("div");
@@ -10432,9 +11005,10 @@ function createBubbleMenuPlugin(options) {
10432
11005
  const onDocumentMousedown = (e) => {
10433
11006
  const target = e.target;
10434
11007
  if (!target) return;
11008
+ if (!target.isConnected) return;
10435
11009
  if (element.contains(target)) return;
10436
11010
  if (editor.view.dom.contains(target)) return;
10437
- if (target instanceof HTMLElement && target.closest("[data-dm-editor-ui]")) return;
11011
+ if (target instanceof Element && target.closest("[data-dm-editor-ui]")) return;
10438
11012
  hideMenu();
10439
11013
  suppressed = true;
10440
11014
  };
@@ -10668,7 +11242,7 @@ var StarterKit = Extension.create({
10668
11242
  });
10669
11243
 
10670
11244
  // src/index.ts
10671
- var VERSION = "0.14.0";
11245
+ var VERSION = "1.0.0";
10672
11246
 
10673
11247
  Object.defineProperty(exports, "PluginKey", {
10674
11248
  enumerable: true,
@@ -10726,6 +11300,7 @@ exports.NotionColorPicker = NotionColorPicker;
10726
11300
  exports.OrderedList = OrderedList;
10727
11301
  exports.Paragraph = Paragraph;
10728
11302
  exports.Placeholder = Placeholder;
11303
+ exports.Print = Print;
10729
11304
  exports.Selection = Selection5;
10730
11305
  exports.SelectionDecoration = SelectionDecoration;
10731
11306
  exports.StarterKit = StarterKit;
@@ -10746,18 +11321,22 @@ exports.UniqueID = UniqueID;
10746
11321
  exports.VERSION = VERSION;
10747
11322
  exports.announce = announce;
10748
11323
  exports.applyInlineStyles = applyInlineStyles;
11324
+ exports.assertSingleProseMirrorCopy = assertSingleProseMirrorCopy;
10749
11325
  exports.autolinkPlugin = autolinkPlugin;
10750
11326
  exports.autolinkPluginKey = autolinkPluginKey;
10751
11327
  exports.blur = blur;
10752
11328
  exports.bubbleMenuPluginKey = bubbleMenuPluginKey;
11329
+ exports.buildBubbleItemMaps = buildBubbleItemMaps;
10753
11330
  exports.buildCommandProps = buildCommandProps;
10754
11331
  exports.builtInCommands = builtInCommands;
10755
11332
  exports.callOrReturn = callOrReturn;
10756
11333
  exports.characterCountPluginKey = characterCountPluginKey;
10757
11334
  exports.clearContent = clearContent;
11335
+ exports.collapseSeparators = collapseSeparators;
10758
11336
  exports.copyThemeClass = copyThemeClass;
10759
11337
  exports.createAccumulatingDispatch = createAccumulatingDispatch;
10760
11338
  exports.createBubbleMenuPlugin = createBubbleMenuPlugin;
11339
+ exports.createBubbleShouldShow = createBubbleShouldShow;
10761
11340
  exports.createCanChecker = createCanChecker;
10762
11341
  exports.createChainBuilder = createChainBuilder;
10763
11342
  exports.createDocument = createDocument;
@@ -10767,6 +11346,8 @@ exports.defaultBubbleContexts = defaultBubbleContexts;
10767
11346
  exports.defaultFloatingMenuShouldShow = defaultFloatingMenuShouldShow;
10768
11347
  exports.defaultIcons = defaultIcons;
10769
11348
  exports.deleteSelection = deleteSelection;
11349
+ exports.detectBubbleContext = detectBubbleContext;
11350
+ exports.filterBubbleItemsBySchema = filterBubbleItemsBySchema;
10770
11351
  exports.findChildren = findChildren;
10771
11352
  exports.findListItemAncestorDepth = findListItemAncestorDepth;
10772
11353
  exports.findParentNode = findParentNode;
@@ -10776,6 +11357,7 @@ exports.focusPluginKey = focusPluginKey;
10776
11357
  exports.generateHTML = generateHTML;
10777
11358
  exports.generateJSON = generateJSON;
10778
11359
  exports.generateText = generateText;
11360
+ exports.getBubbleFormatItems = getBubbleFormatItems;
10779
11361
  exports.getListItemCursorContext = getListItemCursorContext;
10780
11362
  exports.getMarkRange = getMarkRange;
10781
11363
  exports.groupFloatingMenuItems = groupFloatingMenuItems;
@@ -10790,6 +11372,7 @@ exports.invisibleCharsPluginKey = invisibleCharsPluginKey;
10790
11372
  exports.isDocumentEmpty = isDocumentEmpty;
10791
11373
  exports.isInListItemLabel = isInListItemLabel;
10792
11374
  exports.isInsideListItem = isInsideListItem;
11375
+ exports.isInsideTableCell = isInsideTableCell;
10793
11376
  exports.isNodeEmpty = isNodeEmpty;
10794
11377
  exports.isValidUrl = isValidUrl;
10795
11378
  exports.lift = lift;
@@ -10809,7 +11392,10 @@ exports.placeholderPluginKey = placeholderPluginKey;
10809
11392
  exports.positionFloating = positionFloating;
10810
11393
  exports.positionFloatingOnce = positionFloatingOnce;
10811
11394
  exports.refocusEditorAfterCommand = refocusEditorAfterCommand;
11395
+ exports.registerProseMirrorCopy = registerProseMirrorCopy;
10812
11396
  exports.resetAttributes = resetAttributes;
11397
+ exports.resolveBubbleMenuItems = resolveBubbleMenuItems;
11398
+ exports.resolveBubbleNames = resolveBubbleNames;
10813
11399
  exports.selectAll = selectAll;
10814
11400
  exports.selectNodeBackward = selectNodeBackward;
10815
11401
  exports.selectionDecorationPluginKey = selectionDecorationPluginKey;
@@ -10829,6 +11415,7 @@ exports.uniqueIDPluginKey = uniqueIDPluginKey;
10829
11415
  exports.unsetAllMarks = unsetAllMarks;
10830
11416
  exports.unsetMark = unsetMark;
10831
11417
  exports.updateAttributes = updateAttributes;
11418
+ exports.warnOnDuplicateProseMirrorCopy = warnOnDuplicateProseMirrorCopy;
10832
11419
  exports.wrapIn = wrapIn;
10833
11420
  exports.wrappingInputRule = wrappingInputRule;
10834
11421
  exports.writeToClipboard = writeToClipboard;