@domternal/core 0.6.2 → 0.7.1

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
@@ -271,6 +271,10 @@ var ExtensionManager = class {
271
271
  * Cached toolbar items (collected lazily)
272
272
  */
273
273
  _toolbarItems = null;
274
+ /**
275
+ * Cached floating-menu items (collected lazily)
276
+ */
277
+ _floatingMenuItems = null;
274
278
  /**
275
279
  * Cached node views (collected lazily)
276
280
  */
@@ -344,6 +348,14 @@ var ExtensionManager = class {
344
348
  this._toolbarItems ??= this.collectToolbarItems();
345
349
  return this._toolbarItems;
346
350
  }
351
+ /**
352
+ * Gets floating-menu items from all extensions
353
+ * Cached after first call
354
+ */
355
+ get floatingMenuItems() {
356
+ this._floatingMenuItems ??= this.collectFloatingMenuItems();
357
+ return this._floatingMenuItems;
358
+ }
347
359
  /**
348
360
  * Gets node views from all Node extensions that define addNodeView
349
361
  */
@@ -360,6 +372,7 @@ var ExtensionManager = class {
360
372
  this._plugins = null;
361
373
  this._commands = null;
362
374
  this._toolbarItems = null;
375
+ this._floatingMenuItems = null;
363
376
  this._nodeViews = null;
364
377
  }
365
378
  // === Extension Processing ===
@@ -406,7 +419,7 @@ var ExtensionManager = class {
406
419
  });
407
420
  }
408
421
  /**
409
- * Detects duplicate extension names (AD-7: Schema Conflict Detection)
422
+ * Detects duplicate extension names.
410
423
  * @throws Error if duplicate names found
411
424
  */
412
425
  detectConflicts() {
@@ -707,6 +720,25 @@ var ExtensionManager = class {
707
720
  }
708
721
  return items;
709
722
  }
723
+ /**
724
+ * Collects floating-menu items from all extensions via `addFloatingMenuItems()`.
725
+ */
726
+ collectFloatingMenuItems() {
727
+ const items = [];
728
+ for (const ext of this._extensions) {
729
+ const addItems = ext.config.addFloatingMenuItems;
730
+ if (addItems) {
731
+ const extItems = this.safeCall(
732
+ () => callOrReturn(addItems, ext),
733
+ `${ext.name}.addFloatingMenuItems`
734
+ );
735
+ if (extItems && extItems.length > 0) {
736
+ items.push(...extItems);
737
+ }
738
+ }
739
+ }
740
+ return items;
741
+ }
710
742
  /**
711
743
  * Collects node views from all Node extensions.
712
744
  * Returns a map of node name to NodeViewConstructor for EditorView.
@@ -1477,7 +1509,7 @@ var Extension = class _Extension {
1477
1509
  * // Shallow merge behavior with nested objects:
1478
1510
  * // Given: options = { nested: { a: 1, b: 2 } }
1479
1511
  * // configure({ nested: { b: 3 } })
1480
- * // Result: { nested: { b: 3 } } 'a' is lost!
1512
+ * // Result: { nested: { b: 3 } } - 'a' is lost!
1481
1513
  * // To preserve nested values, spread manually:
1482
1514
  * // configure({ nested: { ...original.options.nested, b: 3 } })
1483
1515
  */
@@ -1631,25 +1663,12 @@ var Mark = class _Mark extends Extension {
1631
1663
  return new _Mark(config);
1632
1664
  }
1633
1665
  /**
1634
- * Creates a new mark with merged options
1635
- * Original mark is not modified
1636
- *
1637
- * **Note:** Options are merged shallowly using object spread (`...`).
1638
- * Nested objects are replaced entirely, not deeply merged.
1639
- *
1640
- * @param options - Options to merge with existing options
1641
- * @returns New mark instance with merged options
1666
+ * Creates a new mark with merged options. Original mark is not modified.
1667
+ * Options merge shallowly (object spread); see {@link Extension.configure}
1668
+ * for the nested-object gotcha and a workaround.
1642
1669
  *
1643
1670
  * @example
1644
1671
  * const CustomBold = Bold.configure({ HTMLAttributes: { class: 'custom-bold' } });
1645
- *
1646
- * @example
1647
- * // Shallow merge behavior with nested objects:
1648
- * // Given: options = { HTMLAttributes: { class: 'a', id: 'b' } }
1649
- * // configure({ HTMLAttributes: { class: 'c' } })
1650
- * // Result: { HTMLAttributes: { class: 'c' } } — 'id' is lost!
1651
- * // To preserve nested values, spread manually:
1652
- * // configure({ HTMLAttributes: { ...original.options.HTMLAttributes, class: 'c' } })
1653
1672
  */
1654
1673
  configure(options) {
1655
1674
  const { isFormatting, ...restOptions } = options;
@@ -1924,6 +1943,40 @@ var unsetAllMarks = () => ({ state, tr, dispatch, editor }) => {
1924
1943
  dispatch(tr);
1925
1944
  return true;
1926
1945
  };
1946
+
1947
+ // src/utils/listItemAncestor.ts
1948
+ var LIST_ITEM_TYPE_NAMES = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
1949
+ function findListItemAncestorDepth($pos) {
1950
+ for (let d = $pos.depth; d >= 1; d--) {
1951
+ if (LIST_ITEM_TYPE_NAMES.has($pos.node(d).type.name)) return d;
1952
+ }
1953
+ return -1;
1954
+ }
1955
+ function isInsideListItem($pos) {
1956
+ return findListItemAncestorDepth($pos) !== -1;
1957
+ }
1958
+ function isInListItemLabel($pos) {
1959
+ const d = findListItemAncestorDepth($pos);
1960
+ if (d === -1) return false;
1961
+ return $pos.index(d) === 0;
1962
+ }
1963
+
1964
+ // src/utils/liftCurrentListItem.ts
1965
+ function liftCurrentListItem(state, tr) {
1966
+ if (!tr.selection.empty) return false;
1967
+ if (tr.steps.length !== 0) return false;
1968
+ const { $from } = tr.selection;
1969
+ const listItemDepth = findListItemAncestorDepth($from);
1970
+ if (listItemDepth === -1) return false;
1971
+ if ($from.index(listItemDepth) !== 0) return false;
1972
+ const listItemType = $from.node(listItemDepth).type;
1973
+ return schemaList.liftListItem(listItemType)(state, (liftTr) => {
1974
+ for (const step of liftTr.steps) tr.step(step);
1975
+ if (liftTr.selectionSet) tr.setSelection(liftTr.selection);
1976
+ });
1977
+ }
1978
+
1979
+ // src/commands/nodeCommands.ts
1927
1980
  var setBlockType = (nodeName, attributes) => ({ state, tr, dispatch }) => {
1928
1981
  const nodeType = state.schema.nodes[nodeName];
1929
1982
  if (!nodeType) {
@@ -1947,7 +2000,26 @@ var setBlockType = (nodeName, attributes) => ({ state, tr, dispatch }) => {
1947
2000
  });
1948
2001
  return found;
1949
2002
  });
1950
- if (!canApply) return false;
2003
+ if (!canApply) {
2004
+ const liftOk = liftCurrentListItem(state, tr);
2005
+ if (!liftOk) return false;
2006
+ const $reFrom = tr.doc.resolve(tr.selection.from);
2007
+ if ($reFrom.depth < 1) return false;
2008
+ const blockParent = $reFrom.node($reFrom.depth - 1);
2009
+ const blockIndex = $reFrom.index($reFrom.depth - 1);
2010
+ if (!blockParent.canReplaceWith(blockIndex, blockIndex + 1, nodeType)) {
2011
+ return false;
2012
+ }
2013
+ if (!dispatch) return true;
2014
+ tr.setBlockType(
2015
+ tr.selection.from,
2016
+ tr.selection.to,
2017
+ nodeType,
2018
+ (node) => ({ ...node.attrs, ...attributes ?? {} })
2019
+ );
2020
+ dispatch(tr.scrollIntoView());
2021
+ return true;
2022
+ }
1951
2023
  if (!dispatch) return true;
1952
2024
  for (const range of tr.selection.ranges) {
1953
2025
  const from = range.$from.pos;
@@ -1992,9 +2064,22 @@ var wrapIn = (nodeName, attributes) => ({ state, tr, dispatch }) => {
1992
2064
  const range = $from.blockRange($to);
1993
2065
  if (!range) return false;
1994
2066
  const wrapping = transform.findWrapping(range, nodeType, attributes);
1995
- if (!wrapping) return false;
2067
+ if (wrapping) {
2068
+ if (!dispatch) return true;
2069
+ tr.wrap(range, wrapping).scrollIntoView();
2070
+ dispatch(tr);
2071
+ return true;
2072
+ }
2073
+ const liftOk = liftCurrentListItem(state, tr);
2074
+ if (!liftOk) return false;
2075
+ const $reFrom = tr.doc.resolve(tr.selection.from);
2076
+ const $reTo = tr.doc.resolve(tr.selection.to);
2077
+ const reRange = $reFrom.blockRange($reTo);
2078
+ if (!reRange) return false;
2079
+ const reWrapping = transform.findWrapping(reRange, nodeType, attributes);
2080
+ if (!reWrapping) return false;
1996
2081
  if (!dispatch) return true;
1997
- tr.wrap(range, wrapping).scrollIntoView();
2082
+ tr.wrap(reRange, reWrapping).scrollIntoView();
1998
2083
  dispatch(tr);
1999
2084
  return true;
2000
2085
  };
@@ -2089,6 +2174,30 @@ var lift = () => ({ tr, dispatch }) => {
2089
2174
  dispatch(tr);
2090
2175
  return true;
2091
2176
  };
2177
+
2178
+ // src/utils/listItemCursorContext.ts
2179
+ var LIST_ITEM_TYPES = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
2180
+ function getListItemCursorContext($from) {
2181
+ if ($from.parent.type.name !== "paragraph") return null;
2182
+ const itemDepth = $from.depth - 1;
2183
+ if (itemDepth < 1) return null;
2184
+ const itemNode = $from.node(itemDepth);
2185
+ if (!LIST_ITEM_TYPES.has(itemNode.type.name)) return null;
2186
+ const wrapperDepth = itemDepth - 1;
2187
+ const childIndex = $from.index(itemDepth);
2188
+ return {
2189
+ itemDepth,
2190
+ itemPos: $from.before(itemDepth),
2191
+ wrapperPos: $from.before(wrapperDepth),
2192
+ isInChildrenZone: childIndex > 0,
2193
+ paragraphIsEmpty: $from.parent.content.size === 0,
2194
+ isLastChild: childIndex === itemNode.childCount - 1,
2195
+ childIndex,
2196
+ itemIndexInWrapper: $from.index(wrapperDepth)
2197
+ };
2198
+ }
2199
+
2200
+ // src/commands/listCommands.ts
2092
2201
  function joinListBackwards(tr, listType) {
2093
2202
  const { $from } = tr.selection;
2094
2203
  for (let d = $from.depth; d >= 0; d--) {
@@ -2235,6 +2344,19 @@ var toggleList = (listNodeName, listItemNodeName, attributes) => ({ state: state
2235
2344
  return true;
2236
2345
  }
2237
2346
  const { from, to } = tr.selection;
2347
+ if (from === to) {
2348
+ const ctx = getListItemCursorContext(tr.selection.$from);
2349
+ if (ctx?.isInChildrenZone) {
2350
+ if (!dispatch) return true;
2351
+ const blockRange = tr.selection.$from.blockRange();
2352
+ if (blockRange && schemaList.wrapRangeInList(tr, blockRange, listType, attributes)) {
2353
+ joinListBackwards(tr, listType);
2354
+ joinListForwards(tr, listType);
2355
+ dispatch(tr.scrollIntoView());
2356
+ return true;
2357
+ }
2358
+ }
2359
+ }
2238
2360
  const contentBlocks = collectListContext(tr.doc, from, to);
2239
2361
  const allInTargetList = contentBlocks.length > 0 && contentBlocks.every((b) => b.inTargetList);
2240
2362
  const allInSomeList = contentBlocks.length > 0 && contentBlocks.every((b) => b.inSomeList);
@@ -2893,8 +3015,8 @@ var CommandManager = class {
2893
3015
  };
2894
3016
  }
2895
3017
  /**
2896
- * Single commands that execute immediately
2897
- * Uses Proxy to dynamically generate command methods (ID-1)
3018
+ * Single commands that execute immediately.
3019
+ * Uses a Proxy to dynamically generate command methods.
2898
3020
  *
2899
3021
  * @example
2900
3022
  * editor.commands.focus('end');
@@ -3373,12 +3495,18 @@ var Editor = class extends EventEmitter {
3373
3495
  return this._extensionManager.storage;
3374
3496
  }
3375
3497
  /**
3376
- * Gets toolbar items registered by all extensions.
3377
- * Used by framework toolbar components to auto-generate UI.
3498
+ * Toolbar items registered by all extensions.
3378
3499
  */
3379
3500
  get toolbarItems() {
3380
3501
  return this._extensionManager.toolbarItems;
3381
3502
  }
3503
+ /**
3504
+ * Floating-menu items registered by all extensions, rendered as the
3505
+ * block-insert menu shown on empty paragraphs.
3506
+ */
3507
+ get floatingMenuItems() {
3508
+ return this._extensionManager.floatingMenuItems;
3509
+ }
3382
3510
  // === Active State Methods ===
3383
3511
  /**
3384
3512
  * Checks if a node or mark is currently active
@@ -3629,9 +3757,8 @@ var Editor = class extends EventEmitter {
3629
3757
  }
3630
3758
  // === Dynamic Plugin Management ===
3631
3759
  /**
3632
- * Registers a ProseMirror plugin dynamically at runtime.
3633
- * Used by framework wrappers (e.g. Angular BubbleMenu component) to add
3634
- * plugins after the editor is created.
3760
+ * Registers a ProseMirror plugin dynamically at runtime, after the editor
3761
+ * has been created. Safe to call repeatedly with the same plugin key.
3635
3762
  */
3636
3763
  registerPlugin(plugin) {
3637
3764
  if (plugin.spec.key?.get(this.view.state)) return;
@@ -3746,7 +3873,7 @@ var Editor = class extends EventEmitter {
3746
3873
  ...this.options.editable ?? true ? {} : { "aria-readonly": "true" }
3747
3874
  }),
3748
3875
  ...Object.keys(nodeViews).length > 0 ? { nodeViews } : {},
3749
- // Clipboard transform apply user-provided transform (e.g. inlineStyles) on copy/cut
3876
+ // Clipboard transform - apply user-provided transform (e.g. inlineStyles) on copy/cut
3750
3877
  ...this.options.clipboardHTMLTransform ? this.buildClipboardSerializer(this.options.clipboardHTMLTransform, this._extensionManager.schema) : {},
3751
3878
  // Handle focus/blur events
3752
3879
  handleDOMEvents: {
@@ -3879,8 +4006,239 @@ function positionFloatingOnce(reference, floating, options) {
3879
4006
  });
3880
4007
  }
3881
4008
 
4009
+ // src/utils/clipboard.ts
4010
+ async function writeToClipboard(text) {
4011
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
4012
+ try {
4013
+ await navigator.clipboard.writeText(text);
4014
+ return true;
4015
+ } catch {
4016
+ }
4017
+ }
4018
+ if (typeof document === "undefined") return false;
4019
+ const textarea = document.createElement("textarea");
4020
+ textarea.value = text;
4021
+ textarea.style.position = "fixed";
4022
+ textarea.style.top = "-1000px";
4023
+ textarea.style.left = "-1000px";
4024
+ textarea.style.opacity = "0";
4025
+ textarea.setAttribute("readonly", "");
4026
+ document.body.appendChild(textarea);
4027
+ try {
4028
+ textarea.select();
4029
+ textarea.setSelectionRange(0, text.length);
4030
+ return document.execCommand("copy");
4031
+ } catch {
4032
+ return false;
4033
+ } finally {
4034
+ textarea.remove();
4035
+ }
4036
+ }
4037
+
4038
+ // src/utils/copyThemeClass.ts
4039
+ function copyThemeClass(view, target) {
4040
+ const stale = [];
4041
+ target.classList.forEach((cls) => {
4042
+ if (cls.startsWith("dm-theme-")) stale.push(cls);
4043
+ });
4044
+ for (const cls of stale) target.classList.remove(cls);
4045
+ const source = view.dom.closest('[class*="dm-theme-"]') ?? view.dom.closest(".dm-editor");
4046
+ if (!source) return;
4047
+ source.classList.forEach((cls) => {
4048
+ if (cls.startsWith("dm-theme-")) target.classList.add(cls);
4049
+ });
4050
+ }
4051
+
4052
+ // src/utils/defaultBubbleContexts.ts
4053
+ var NOTION_MODE_CLASS = "dm-notion-mode";
4054
+ var NOTION_TEXT_CONTEXT = Object.freeze([
4055
+ "bold",
4056
+ "italic",
4057
+ "underline",
4058
+ "strike",
4059
+ "code",
4060
+ "|",
4061
+ "link",
4062
+ "|",
4063
+ "textAlign"
4064
+ ]);
4065
+ var STANDARD_TEXT_CONTEXT = Object.freeze([
4066
+ "bold",
4067
+ "italic",
4068
+ "underline",
4069
+ "strike",
4070
+ "code",
4071
+ "|",
4072
+ "link"
4073
+ ]);
4074
+ function defaultBubbleContexts(editor) {
4075
+ const inNotionMode = editor.view.dom.closest("." + NOTION_MODE_CLASS) !== null;
4076
+ const text = inNotionMode ? NOTION_TEXT_CONTEXT : STANDARD_TEXT_CONTEXT;
4077
+ return { text: [...text] };
4078
+ }
4079
+
4080
+ // src/utils/insertAsListItemChild.ts
4081
+ var LIST_ITEM_TYPES2 = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
4082
+ var LIST_WRAPPER_TYPES = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
4083
+ function insertAsListItemChild(args) {
4084
+ const { tr, wrapperPos, targetItemPos, blockNode, sourceRange } = args;
4085
+ if (wrapperPos < 0 || wrapperPos >= tr.doc.content.size) return { ok: false };
4086
+ const wrapper = tr.doc.nodeAt(wrapperPos);
4087
+ if (!wrapper || !LIST_WRAPPER_TYPES.has(wrapper.type.name)) return { ok: false };
4088
+ if (wrapper.childCount === 0) return { ok: false };
4089
+ let targetItem;
4090
+ let targetItemStart;
4091
+ if (targetItemPos !== void 0) {
4092
+ if (targetItemPos < 0 || targetItemPos >= tr.doc.content.size) return { ok: false };
4093
+ const candidate = tr.doc.nodeAt(targetItemPos);
4094
+ if (!candidate || !LIST_ITEM_TYPES2.has(candidate.type.name)) return { ok: false };
4095
+ if (targetItemPos < wrapperPos + 1 || targetItemPos >= wrapperPos + wrapper.nodeSize) {
4096
+ return { ok: false };
4097
+ }
4098
+ targetItem = candidate;
4099
+ targetItemStart = targetItemPos;
4100
+ } else {
4101
+ const last = wrapper.lastChild;
4102
+ if (!last || !LIST_ITEM_TYPES2.has(last.type.name)) return { ok: false };
4103
+ let pos = wrapperPos + 1;
4104
+ for (let i = 0; i < wrapper.childCount - 1; i++) {
4105
+ pos += wrapper.child(i).nodeSize;
4106
+ }
4107
+ targetItem = last;
4108
+ targetItemStart = pos;
4109
+ }
4110
+ if (!targetItem.canReplaceWith(targetItem.childCount, targetItem.childCount, blockNode.type)) {
4111
+ return { ok: false };
4112
+ }
4113
+ const targetItemContentEnd = targetItemStart + targetItem.nodeSize - 1;
4114
+ if (sourceRange) {
4115
+ const { from, to } = sourceRange;
4116
+ if (targetItemContentEnd >= from && targetItemContentEnd <= to) {
4117
+ return { ok: false };
4118
+ }
4119
+ tr.delete(from, to);
4120
+ const adjustedInsertPos = targetItemContentEnd > from ? targetItemContentEnd - (to - from) : targetItemContentEnd;
4121
+ tr.insert(adjustedInsertPos, blockNode);
4122
+ return { ok: true, insertedAt: adjustedInsertPos };
4123
+ }
4124
+ tr.insert(targetItemContentEnd, blockNode);
4125
+ return { ok: true, insertedAt: targetItemContentEnd };
4126
+ }
4127
+ function liftEmptyChildrenZoneParagraph(state$1, dispatch, ctx) {
4128
+ if (!ctx.isInChildrenZone || !ctx.paragraphIsEmpty) return false;
4129
+ const { $from } = state$1.selection;
4130
+ if ($from.parent.type.name !== "paragraph") return false;
4131
+ if ($from.parent.content.size !== 0) return false;
4132
+ const paragraphType = state$1.schema.nodes["paragraph"];
4133
+ if (!paragraphType) return false;
4134
+ const range = $from.blockRange();
4135
+ if (range) {
4136
+ const target = transform.liftTarget(range);
4137
+ if (target !== null) {
4138
+ if (!dispatch) return true;
4139
+ const tr2 = state$1.tr.lift(range, target);
4140
+ const oldParaStart = $from.before($from.depth);
4141
+ const newParaStart = tr2.mapping.map(oldParaStart);
4142
+ tr2.setSelection(state.TextSelection.create(tr2.doc, newParaStart + 1));
4143
+ dispatch(tr2.scrollIntoView());
4144
+ return true;
4145
+ }
4146
+ }
4147
+ const paraStart = $from.before($from.depth);
4148
+ const paraEnd = $from.after($from.depth);
4149
+ const itemNode = state$1.doc.nodeAt(ctx.itemPos);
4150
+ if (!itemNode) return false;
4151
+ const itemEnd = ctx.itemPos + itemNode.nodeSize;
4152
+ const wrapperNode = state$1.doc.nodeAt(ctx.wrapperPos);
4153
+ if (!wrapperNode) return false;
4154
+ const wrapperEnd = ctx.wrapperPos + wrapperNode.nodeSize;
4155
+ const isLastItem = ctx.itemIndexInWrapper === wrapperNode.childCount - 1;
4156
+ const wrapperParentDepth = ctx.itemDepth - 2;
4157
+ if (wrapperParentDepth < 0) return false;
4158
+ const wrapperParent = $from.node(wrapperParentDepth);
4159
+ const wrapperIndexInParent = $from.index(wrapperParentDepth);
4160
+ if (!wrapperParent.canReplaceWith(
4161
+ wrapperIndexInParent + 1,
4162
+ wrapperIndexInParent + 1,
4163
+ paragraphType
4164
+ )) {
4165
+ return false;
4166
+ }
4167
+ if (!dispatch) return true;
4168
+ const newPara = paragraphType.create();
4169
+ const tr = state$1.tr;
4170
+ if (isLastItem) {
4171
+ tr.delete(paraStart, paraEnd);
4172
+ const insertPos = tr.mapping.map(wrapperEnd);
4173
+ tr.insert(insertPos, newPara);
4174
+ tr.setSelection(state.TextSelection.create(tr.doc, insertPos + 1));
4175
+ } else {
4176
+ tr.delete(paraStart, paraEnd);
4177
+ const splitAt = tr.mapping.map(itemEnd);
4178
+ tr.split(splitAt, 1);
4179
+ const insertPos = splitAt + 1;
4180
+ tr.insert(insertPos, newPara);
4181
+ tr.setSelection(state.TextSelection.create(tr.doc, insertPos + 1));
4182
+ }
4183
+ dispatch(tr.scrollIntoView());
4184
+ return true;
4185
+ }
4186
+ function insertChildrenZoneSibling(state$1, dispatch, ctx) {
4187
+ if (!ctx.isInChildrenZone || !ctx.paragraphIsEmpty) return false;
4188
+ const paragraphType = state$1.schema.nodes["paragraph"];
4189
+ if (!paragraphType) return false;
4190
+ const { $from } = state$1.selection;
4191
+ if ($from.parent.type.name !== "paragraph") return false;
4192
+ if ($from.parent.content.size !== 0) return false;
4193
+ if (!dispatch) return true;
4194
+ const insertPos = $from.after($from.depth);
4195
+ const tr = state$1.tr.insert(insertPos, paragraphType.create());
4196
+ tr.setSelection(state.TextSelection.create(tr.doc, insertPos + 1));
4197
+ dispatch(tr.scrollIntoView());
4198
+ return true;
4199
+ }
4200
+ function splitListForInsert(state, tr) {
4201
+ if (!tr.selection.empty) return null;
4202
+ if (tr.steps.length !== 0) return null;
4203
+ const { $from } = tr.selection;
4204
+ const listItemDepth = findListItemAncestorDepth($from);
4205
+ if (listItemDepth === -1) return null;
4206
+ if ($from.index(listItemDepth) !== 0) return null;
4207
+ const listDepth = listItemDepth - 1;
4208
+ const listItemNode = $from.node(listItemDepth);
4209
+ const labelParagraph = listItemNode.firstChild;
4210
+ const labelIsEmpty = listItemNode.childCount === 1 && (labelParagraph?.content.size ?? 0) === 0;
4211
+ if (labelIsEmpty) {
4212
+ const listItemType = listItemNode.type;
4213
+ let safetyLimit = 10;
4214
+ while (safetyLimit-- > 0) {
4215
+ const stepState = state.apply(tr);
4216
+ const liftOk = schemaList.liftListItem(listItemType)(stepState, (liftTr) => {
4217
+ for (const step of liftTr.steps) tr.step(step);
4218
+ if (liftTr.selectionSet) tr.setSelection(liftTr.selection);
4219
+ });
4220
+ if (!liftOk) break;
4221
+ if (!isInsideListItem(tr.selection.$from)) break;
4222
+ }
4223
+ const $afterLift = tr.selection.$from;
4224
+ if (isInsideListItem($afterLift)) return null;
4225
+ return { from: $afterLift.before(), to: $afterLift.after() };
4226
+ }
4227
+ const listNode = $from.node(listDepth);
4228
+ const indexInList = $from.index(listDepth);
4229
+ const isLast = indexInList === listNode.childCount - 1;
4230
+ if (isLast) {
4231
+ const pos2 = $from.after(listDepth);
4232
+ return { from: pos2, to: pos2 };
4233
+ }
4234
+ const splitPos = $from.after(listItemDepth);
4235
+ tr.split(splitPos, 1);
4236
+ const pos = splitPos + 1;
4237
+ return { from: pos, to: pos };
4238
+ }
4239
+
3882
4240
  // src/Node.ts
3883
- var Node = class _Node extends Extension {
4241
+ var Node2 = class _Node extends Extension {
3884
4242
  /**
3885
4243
  * Node type identifier
3886
4244
  * Distinguishes nodes from extensions and marks
@@ -3942,25 +4300,12 @@ var Node = class _Node extends Extension {
3942
4300
  return new _Node(config);
3943
4301
  }
3944
4302
  /**
3945
- * Creates a new node with merged options
3946
- * Original node is not modified
3947
- *
3948
- * **Note:** Options are merged shallowly using object spread (`...`).
3949
- * Nested objects are replaced entirely, not deeply merged.
3950
- *
3951
- * @param options - Options to merge with existing options
3952
- * @returns New node instance with merged options
4303
+ * Creates a new node with merged options. Original node is not modified.
4304
+ * Options merge shallowly (object spread); see {@link Extension.configure}
4305
+ * for the nested-object gotcha and a workaround.
3953
4306
  *
3954
4307
  * @example
3955
4308
  * const CustomParagraph = Paragraph.configure({ HTMLAttributes: { class: 'custom' } });
3956
- *
3957
- * @example
3958
- * // Shallow merge behavior with nested objects:
3959
- * // Given: options = { HTMLAttributes: { class: 'a', id: 'b' } }
3960
- * // configure({ HTMLAttributes: { class: 'c' } })
3961
- * // Result: { HTMLAttributes: { class: 'c' } } — 'id' is lost!
3962
- * // To preserve nested values, spread manually:
3963
- * // configure({ HTMLAttributes: { ...original.options.HTMLAttributes, class: 'c' } })
3964
4309
  */
3965
4310
  configure(options) {
3966
4311
  const newConfig = {
@@ -4134,7 +4479,7 @@ var ToolbarController = class _ToolbarController {
4134
4479
  _activeMap = /* @__PURE__ */ new Map();
4135
4480
  /** Disabled state for each button (keyed by item.name) */
4136
4481
  _disabledMap = /* @__PURE__ */ new Map();
4137
- /** Expanded state for emitEvent buttons true when their panel is open */
4482
+ /** Expanded state for emitEvent buttons - true when their panel is open */
4138
4483
  _expandedMap = /* @__PURE__ */ new Map();
4139
4484
  /** Currently open dropdown name (null = none) */
4140
4485
  _openDropdown = null;
@@ -4469,6 +4814,262 @@ var ToolbarController = class _ToolbarController {
4469
4814
  }
4470
4815
  };
4471
4816
 
4817
+ // src/utils/groupFloatingMenuItems.ts
4818
+ function groupFloatingMenuItems(items) {
4819
+ const map = /* @__PURE__ */ new Map();
4820
+ const order = [];
4821
+ for (const item of items) {
4822
+ const name = item.group ?? "";
4823
+ let list = map.get(name);
4824
+ if (!list) {
4825
+ list = [];
4826
+ map.set(name, list);
4827
+ order.push(name);
4828
+ }
4829
+ list.push(item);
4830
+ }
4831
+ const groups = [];
4832
+ for (const name of order) {
4833
+ const list = (map.get(name) ?? []).slice();
4834
+ list.sort((a, b) => (b.priority ?? 100) - (a.priority ?? 100));
4835
+ groups.push({ name, items: list });
4836
+ }
4837
+ return groups;
4838
+ }
4839
+
4840
+ // src/FloatingMenuController.ts
4841
+ var FLOATING_MENU_NO_FOCUS = -1;
4842
+ var FloatingMenuController = class _FloatingMenuController {
4843
+ /**
4844
+ * Resolves an `items` option (array | function) against the editor's
4845
+ * default floating-menu items. Exposed as static so the plugin can
4846
+ * resolve once at init without constructing a controller.
4847
+ */
4848
+ static resolveItems(editor, override) {
4849
+ const defaults = editor.floatingMenuItems;
4850
+ if (!override) return defaults;
4851
+ if (Array.isArray(override)) return override;
4852
+ try {
4853
+ return override(defaults, editor);
4854
+ } catch {
4855
+ return defaults;
4856
+ }
4857
+ }
4858
+ /**
4859
+ * Executes a floating-menu item's command on the editor.
4860
+ * String commands are dispatched via `editor.commands[name](...args)`;
4861
+ * function commands are called directly.
4862
+ */
4863
+ static executeItem(editor, item) {
4864
+ if (typeof item.command === "function") {
4865
+ item.command(editor);
4866
+ return;
4867
+ }
4868
+ const commands = editor.commands;
4869
+ const cmd = commands[item.command];
4870
+ if (!cmd) return;
4871
+ if (item.commandArgs?.length) {
4872
+ cmd(...item.commandArgs);
4873
+ } else {
4874
+ cmd();
4875
+ }
4876
+ }
4877
+ editor;
4878
+ onChange;
4879
+ override;
4880
+ transactionHandler = null;
4881
+ _groups = [];
4882
+ _flatItems = [];
4883
+ _disabledMap = /* @__PURE__ */ new Map();
4884
+ _focusedIndex = FLOATING_MENU_NO_FOCUS;
4885
+ constructor(editor, onChange, override) {
4886
+ this.editor = editor;
4887
+ this.onChange = onChange;
4888
+ this.override = override;
4889
+ this.rebuild();
4890
+ }
4891
+ // === Getters ===
4892
+ get groups() {
4893
+ return this._groups;
4894
+ }
4895
+ get flatItems() {
4896
+ return this._flatItems;
4897
+ }
4898
+ get disabledMap() {
4899
+ return this._disabledMap;
4900
+ }
4901
+ get focusedIndex() {
4902
+ return this._focusedIndex;
4903
+ }
4904
+ /** True when keyboard focus is inside the menu (at least one item focused). */
4905
+ get isEntered() {
4906
+ return this._focusedIndex >= 0;
4907
+ }
4908
+ get itemCount() {
4909
+ return this._flatItems.length;
4910
+ }
4911
+ // === State Methods ===
4912
+ isDisabled(item) {
4913
+ return this._disabledMap.get(item.name) ?? false;
4914
+ }
4915
+ /**
4916
+ * Executes an item's command, then closes the menu keyboard focus.
4917
+ * Callers should refocus the editor after calling this.
4918
+ */
4919
+ execute(item) {
4920
+ if (this.isDisabled(item)) return;
4921
+ _FloatingMenuController.executeItem(this.editor, item);
4922
+ this._focusedIndex = FLOATING_MENU_NO_FOCUS;
4923
+ this.onChange();
4924
+ }
4925
+ /**
4926
+ * Rebuilds items from the editor. Call when the editor's extensions
4927
+ * change (rare) or on explicit refresh. Notification is delegated to
4928
+ * `updateDisabledStates` which fires `onChange` only when a disabled
4929
+ * state flipped - wrappers that need to react to pure group-structure
4930
+ * changes do so by bumping their own render signal after constructing
4931
+ * / re-using the controller (see framework wrapper usage).
4932
+ */
4933
+ rebuild() {
4934
+ const items = _FloatingMenuController.resolveItems(this.editor, this.override);
4935
+ this._groups = this.groupItems(items);
4936
+ this._flatItems = this._groups.flatMap((g) => g.items);
4937
+ if (this._focusedIndex >= this._flatItems.length) {
4938
+ this._focusedIndex = FLOATING_MENU_NO_FOCUS;
4939
+ }
4940
+ this.updateDisabledStates();
4941
+ }
4942
+ // === Keyboard Navigation (roving tabindex) ===
4943
+ /** Enter keyboard focus on the menu (first item). Called by the plugin's keymap. */
4944
+ enterMenu() {
4945
+ if (this._flatItems.length === 0) {
4946
+ this._focusedIndex = FLOATING_MENU_NO_FOCUS;
4947
+ return this._focusedIndex;
4948
+ }
4949
+ this._focusedIndex = 0;
4950
+ this.onChange();
4951
+ return this._focusedIndex;
4952
+ }
4953
+ /** Leave keyboard focus (Escape). Refocus of the editor is the caller's job. */
4954
+ leaveMenu() {
4955
+ if (this._focusedIndex === FLOATING_MENU_NO_FOCUS) return;
4956
+ this._focusedIndex = FLOATING_MENU_NO_FOCUS;
4957
+ this.onChange();
4958
+ }
4959
+ /** ArrowDown - wrap to first at end. */
4960
+ next() {
4961
+ if (this._flatItems.length === 0) return FLOATING_MENU_NO_FOCUS;
4962
+ const cur = this._focusedIndex < 0 ? -1 : this._focusedIndex;
4963
+ this._focusedIndex = (cur + 1) % this._flatItems.length;
4964
+ this.onChange();
4965
+ return this._focusedIndex;
4966
+ }
4967
+ /** ArrowUp - wrap to last at start. */
4968
+ prev() {
4969
+ if (this._flatItems.length === 0) return FLOATING_MENU_NO_FOCUS;
4970
+ const len = this._flatItems.length;
4971
+ const cur = this._focusedIndex < 0 ? len : this._focusedIndex;
4972
+ this._focusedIndex = (cur - 1 + len) % len;
4973
+ this.onChange();
4974
+ return this._focusedIndex;
4975
+ }
4976
+ first() {
4977
+ if (this._flatItems.length === 0) return FLOATING_MENU_NO_FOCUS;
4978
+ this._focusedIndex = 0;
4979
+ this.onChange();
4980
+ return this._focusedIndex;
4981
+ }
4982
+ last() {
4983
+ if (this._flatItems.length === 0) return FLOATING_MENU_NO_FOCUS;
4984
+ this._focusedIndex = this._flatItems.length - 1;
4985
+ this.onChange();
4986
+ return this._focusedIndex;
4987
+ }
4988
+ /** Set focused index directly (e.g. on pointer hover). */
4989
+ setFocusedIndex(index) {
4990
+ if (index < 0 || index >= this._flatItems.length) return;
4991
+ if (this._focusedIndex === index) return;
4992
+ this._focusedIndex = index;
4993
+ this.onChange();
4994
+ }
4995
+ /** Get focused item (or null). */
4996
+ focusedItem() {
4997
+ return this._flatItems[this._focusedIndex] ?? null;
4998
+ }
4999
+ /** Get flat index of item by name (for wrappers binding roving tabindex). */
5000
+ getFlatIndex(name) {
5001
+ return this._flatItems.findIndex((i) => i.name === name);
5002
+ }
5003
+ // === Lifecycle ===
5004
+ /** Subscribes to editor transactions for disabled-state tracking. */
5005
+ subscribe() {
5006
+ this.transactionHandler = () => {
5007
+ this.updateDisabledStates();
5008
+ };
5009
+ this.editor.on("transaction", this.transactionHandler);
5010
+ }
5011
+ /** Unsubscribes and clears internal state. */
5012
+ destroy() {
5013
+ if (this.transactionHandler) {
5014
+ this.editor.off("transaction", this.transactionHandler);
5015
+ this.transactionHandler = null;
5016
+ }
5017
+ this._groups = [];
5018
+ this._flatItems = [];
5019
+ this._disabledMap.clear();
5020
+ this._focusedIndex = FLOATING_MENU_NO_FOCUS;
5021
+ }
5022
+ // === Internal ===
5023
+ /**
5024
+ * Groups items by `group` preserving insertion order, then sorts by
5025
+ * priority (higher first) within each group. Delegates to the shared
5026
+ * utility so `SlashCommand`'s renderer and this controller always
5027
+ * produce identical ordering.
5028
+ */
5029
+ groupItems(items) {
5030
+ return groupFloatingMenuItems(items);
5031
+ }
5032
+ /**
5033
+ * Updates disabled state for each item. Uses custom predicate when
5034
+ * provided; otherwise tries a dry-run against `editor.can()[command]`.
5035
+ * Only notifies on change to avoid noisy re-renders.
5036
+ */
5037
+ updateDisabledStates() {
5038
+ let changed = false;
5039
+ let canProxy = null;
5040
+ try {
5041
+ canProxy = this.editor.can();
5042
+ } catch {
5043
+ canProxy = null;
5044
+ }
5045
+ for (const item of this._flatItems) {
5046
+ const was = this._disabledMap.get(item.name) ?? false;
5047
+ let now = false;
5048
+ if (item.isDisabled) {
5049
+ try {
5050
+ now = item.isDisabled(this.editor);
5051
+ } catch {
5052
+ now = false;
5053
+ }
5054
+ } else if (typeof item.command === "string" && canProxy) {
5055
+ try {
5056
+ const canCmd = canProxy[item.command];
5057
+ if (canCmd) {
5058
+ now = item.commandArgs?.length ? !canCmd(...item.commandArgs) : !canCmd();
5059
+ }
5060
+ } catch {
5061
+ now = false;
5062
+ }
5063
+ }
5064
+ if (was !== now) {
5065
+ this._disabledMap.set(item.name, now);
5066
+ changed = true;
5067
+ }
5068
+ }
5069
+ if (changed) this.onChange();
5070
+ }
5071
+ };
5072
+
4472
5073
  // src/icons/phosphor.ts
4473
5074
  var defaultIcons = {
4474
5075
  // --- Format: Inline ---
@@ -4528,24 +5129,29 @@ var defaultIcons = {
4528
5129
  arrowClockwise: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M240,56v48a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16H211.4L184.81,71.64l-.25-.24a80,80,0,1,0-1.67,114.78,8,8,0,0,1,11,11.63A95.44,95.44,0,0,1,128,224h-1.32A96,96,0,1,1,195.75,60L224,85.8V56a8,8,0,1,1,16,0Z"/></svg>',
4529
5130
  // --- Table ---
4530
5131
  table: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,48H32a8,8,0,0,0-8,8V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A8,8,0,0,0,224,48ZM40,112H80v32H40Zm56,0H216v32H96ZM216,64V96H40V64ZM40,160H80v32H40Zm176,32H96V160H216v32Z"/></svg>',
4531
- gridNine: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,48H40A16,16,0,0,0,24,64V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V64A16,16,0,0,0,216,48ZM104,144V112h48v32Zm48,16v32H104V160ZM40,112H88v32H40Zm64-16V64h48V96Zm64,16h48v32H168Zm48-16H168V64h48ZM88,64V96H40V64ZM40,160H88v32H40Zm176,32H168V160h48v32Z"/></svg>'
5132
+ gridNine: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M216,48H40A16,16,0,0,0,24,64V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V64A16,16,0,0,0,216,48ZM104,144V112h48v32Zm48,16v32H104V160ZM40,112H88v32H40Zm64-16V64h48V96Zm64,16h48v32H168Zm48-16H168V64h48ZM88,64V96H40V64ZM40,160H88v32H40Zm176,32H168V160h48v32Z"/></svg>',
5133
+ // --- Block Handle / Context Menu ---
5134
+ 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>',
5135
+ 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>',
5136
+ 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>',
5137
+ 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>'
4532
5138
  };
4533
5139
 
4534
5140
  // src/nodes/Document.ts
4535
- var Document = Node.create({
5141
+ var Document = Node2.create({
4536
5142
  name: "doc",
4537
5143
  topNode: true,
4538
5144
  content: "block+"
4539
5145
  });
4540
5146
 
4541
5147
  // src/nodes/Text.ts
4542
- var Text = Node.create({
5148
+ var Text = Node2.create({
4543
5149
  name: "text",
4544
5150
  group: "inline"
4545
5151
  });
4546
5152
 
4547
5153
  // src/nodes/Paragraph.ts
4548
- var Paragraph = Node.create({
5154
+ var Paragraph = Node2.create({
4549
5155
  name: "paragraph",
4550
5156
  group: "block",
4551
5157
  content: "inline*",
@@ -4578,7 +5184,7 @@ var Paragraph = Node.create({
4578
5184
  };
4579
5185
  }
4580
5186
  });
4581
- var Heading = Node.create({
5187
+ var Heading = Node2.create({
4582
5188
  name: "heading",
4583
5189
  group: "block",
4584
5190
  content: "inline*",
@@ -4641,6 +5247,33 @@ var Heading = Node.create({
4641
5247
  return editor?.commands["toggleHeading"]?.({ level }) ?? false;
4642
5248
  };
4643
5249
  });
5250
+ shortcuts["Enter"] = () => {
5251
+ if (!editor) return false;
5252
+ const { state: state$1, view } = editor;
5253
+ const { selection } = state$1;
5254
+ if (!selection.empty) return false;
5255
+ const { $from } = selection;
5256
+ if ($from.parent.type.name !== "heading") return false;
5257
+ const paragraphType = state$1.schema.nodes["paragraph"];
5258
+ if (!paragraphType) return false;
5259
+ if ($from.parent.content.size === 0) {
5260
+ view.dispatch(
5261
+ state$1.tr.setNodeMarkup($from.before($from.depth), paragraphType).scrollIntoView()
5262
+ );
5263
+ return true;
5264
+ }
5265
+ if ($from.parentOffset !== $from.parent.content.size) return false;
5266
+ const after = $from.after($from.depth);
5267
+ const $after = state$1.doc.resolve(after);
5268
+ const indexAfter = $after.index();
5269
+ if (!$after.parent.canReplaceWith(indexAfter, indexAfter, paragraphType)) {
5270
+ return false;
5271
+ }
5272
+ const tr = state$1.tr.insert(after, paragraphType.create());
5273
+ tr.setSelection(state.TextSelection.create(tr.doc, after + 1));
5274
+ view.dispatch(tr.scrollIntoView());
5275
+ return true;
5276
+ };
4644
5277
  return shortcuts;
4645
5278
  },
4646
5279
  addToolbarItems() {
@@ -4682,6 +5315,30 @@ var Heading = Node.create({
4682
5315
  }
4683
5316
  ];
4684
5317
  },
5318
+ addFloatingMenuItems() {
5319
+ const iconMap = {
5320
+ 1: "textHOne",
5321
+ 2: "textHTwo",
5322
+ 3: "textHThree"
5323
+ };
5324
+ const descriptionMap = {
5325
+ 1: "Big section heading",
5326
+ 2: "Medium section heading",
5327
+ 3: "Small section heading"
5328
+ };
5329
+ return this.options.levels.filter((level) => level <= 3).map((level) => ({
5330
+ name: `heading-${String(level)}`,
5331
+ label: `Heading ${String(level)}`,
5332
+ description: descriptionMap[level] ?? "Section heading",
5333
+ icon: iconMap[level] ?? "textH",
5334
+ group: "Basic",
5335
+ priority: 210 - level * 10,
5336
+ keywords: ["heading", `h${String(level)}`, "title"],
5337
+ shortcut: "#".repeat(level) + " ",
5338
+ command: "toggleHeading",
5339
+ commandArgs: [{ level }]
5340
+ }));
5341
+ },
4685
5342
  addProseMirrorPlugins() {
4686
5343
  const { options, editor } = this;
4687
5344
  const codeToLevel = {};
@@ -4755,7 +5412,7 @@ var Heading = Node.create({
4755
5412
  });
4756
5413
 
4757
5414
  // src/nodes/Blockquote.ts
4758
- var Blockquote = Node.create({
5415
+ var Blockquote = Node2.create({
4759
5416
  name: "blockquote",
4760
5417
  group: "block",
4761
5418
  content: "block+",
@@ -4808,9 +5465,24 @@ var Blockquote = Node.create({
4808
5465
  }
4809
5466
  ];
4810
5467
  },
4811
- addInputRules() {
4812
- const { nodeType } = this;
4813
- if (!nodeType) {
5468
+ addFloatingMenuItems() {
5469
+ return [
5470
+ {
5471
+ name: "blockquote",
5472
+ label: "Quote",
5473
+ description: "Capture a quote",
5474
+ icon: "quotes",
5475
+ group: "Basic",
5476
+ priority: 170,
5477
+ keywords: ["quote", "blockquote", "citation"],
5478
+ shortcut: "> ",
5479
+ command: "toggleBlockquote"
5480
+ }
5481
+ ];
5482
+ },
5483
+ addInputRules() {
5484
+ const { nodeType } = this;
5485
+ if (!nodeType) {
4814
5486
  return [];
4815
5487
  }
4816
5488
  return [
@@ -4818,7 +5490,7 @@ var Blockquote = Node.create({
4818
5490
  ];
4819
5491
  }
4820
5492
  });
4821
- var CodeBlock = Node.create({
5493
+ var CodeBlock = Node2.create({
4822
5494
  name: "codeBlock",
4823
5495
  group: "block",
4824
5496
  content: "text*",
@@ -4938,6 +5610,21 @@ var CodeBlock = Node.create({
4938
5610
  }
4939
5611
  ];
4940
5612
  },
5613
+ addFloatingMenuItems() {
5614
+ return [
5615
+ {
5616
+ name: "code-block",
5617
+ label: "Code block",
5618
+ description: "Capture a code snippet",
5619
+ icon: "codeBlock",
5620
+ group: "Basic",
5621
+ priority: 160,
5622
+ keywords: ["code", "snippet", "pre"],
5623
+ shortcut: "``` ",
5624
+ command: "toggleCodeBlock"
5625
+ }
5626
+ ];
5627
+ },
4941
5628
  addInputRules() {
4942
5629
  const { nodeType } = this;
4943
5630
  if (!nodeType) {
@@ -4955,6 +5642,7 @@ var CodeBlock = Node.create({
4955
5642
  ];
4956
5643
  }
4957
5644
  });
5645
+ var LIST_GROUP_TYPES = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
4958
5646
  function getListItemContext(editor, listItemName) {
4959
5647
  const { state, view } = editor;
4960
5648
  const listItemType = state.schema.nodes[listItemName];
@@ -5001,10 +5689,39 @@ var ListKeymap = Extension.create({
5001
5689
  Backspace: () => {
5002
5690
  const editor = this.editor;
5003
5691
  if (!editor) return false;
5004
- const { state, view } = editor;
5005
- const { $from, empty } = state.selection;
5692
+ const { state: state$1, view } = editor;
5693
+ const { $from, empty } = state$1.selection;
5006
5694
  if (!empty || $from.parentOffset !== 0) return false;
5007
- const listItemType = state.schema.nodes[this.options.listItem];
5695
+ if ($from.parent.type.name === "paragraph" && $from.parent.content.size === 0 && $from.depth >= 1) {
5696
+ const containerDepth = $from.depth - 1;
5697
+ const idx = $from.index(containerDepth);
5698
+ if (idx > 0) {
5699
+ const container = $from.node(containerDepth);
5700
+ const prev = container.child(idx - 1);
5701
+ if (LIST_GROUP_TYPES.has(prev.type.name)) {
5702
+ const paraStart = $from.before($from.depth);
5703
+ const paraEnd = $from.after($from.depth);
5704
+ const listEnd = paraStart;
5705
+ const listStart = listEnd - prev.nodeSize;
5706
+ let lastTextblockEnd = -1;
5707
+ state$1.doc.nodesBetween(listStart, listEnd, (n, p) => {
5708
+ if (n.isTextblock) lastTextblockEnd = p + 1 + n.content.size;
5709
+ return true;
5710
+ });
5711
+ if (lastTextblockEnd !== -1) {
5712
+ const tr = state$1.tr.delete(paraStart, paraEnd);
5713
+ const next = idx + 1 < container.childCount ? container.child(idx + 1) : null;
5714
+ if (next?.type === prev.type && transform.canJoin(tr.doc, paraStart)) {
5715
+ tr.join(paraStart);
5716
+ }
5717
+ tr.setSelection(state.TextSelection.create(tr.doc, lastTextblockEnd));
5718
+ view.dispatch(tr.scrollIntoView());
5719
+ return true;
5720
+ }
5721
+ }
5722
+ }
5723
+ }
5724
+ const listItemType = state$1.schema.nodes[this.options.listItem];
5008
5725
  if (!listItemType) return false;
5009
5726
  let listItemDepth = -1;
5010
5727
  for (let d = $from.depth; d > 0; d--) {
@@ -5019,7 +5736,7 @@ var ListKeymap = Extension.create({
5019
5736
  if (firstChild?.isTextblock) {
5020
5737
  const posInListItem = $from.pos - $from.start(listItemDepth);
5021
5738
  if (posInListItem <= 1) {
5022
- return schemaList.liftListItem(listItemType)(state, view.dispatch);
5739
+ return schemaList.liftListItem(listItemType)(state$1, view.dispatch);
5023
5740
  }
5024
5741
  }
5025
5742
  return false;
@@ -5029,9 +5746,11 @@ var ListKeymap = Extension.create({
5029
5746
  });
5030
5747
 
5031
5748
  // src/nodes/ListItem.ts
5032
- var ListItem = Node.create({
5749
+ var ListItem = Node2.create({
5033
5750
  name: "listItem",
5034
- content: "block+",
5751
+ // Notion-strict: paragraph must be the first child (the "label" line aligned
5752
+ // with the bullet); additional blocks render below as nested children.
5753
+ content: "paragraph block*",
5035
5754
  defining: true,
5036
5755
  addOptions() {
5037
5756
  return {
@@ -5054,6 +5773,15 @@ var ListItem = Node.create({
5054
5773
  const { state: state$1, view } = this.editor;
5055
5774
  const { $from } = state$1.selection;
5056
5775
  if ($from.depth < 1 || $from.node(-1).type !== this.nodeType) return false;
5776
+ if ($from.parent.type.name !== "paragraph") return false;
5777
+ const ctx = getListItemCursorContext($from);
5778
+ if (ctx?.isInChildrenZone) {
5779
+ if (ctx.paragraphIsEmpty) {
5780
+ if (insertChildrenZoneSibling(state$1, view.dispatch, ctx)) return true;
5781
+ } else {
5782
+ if (commands.splitBlock(state$1, view.dispatch)) return true;
5783
+ }
5784
+ }
5057
5785
  if (schemaList.splitListItem(this.nodeType)(state$1, view.dispatch)) return true;
5058
5786
  const listDepth = $from.depth - 2;
5059
5787
  const taskItemType = state$1.schema.nodes["taskItem"];
@@ -5072,13 +5800,25 @@ var ListItem = Node.create({
5072
5800
  }
5073
5801
  }
5074
5802
  return schemaList.liftListItem(this.nodeType)(state$1, view.dispatch);
5803
+ },
5804
+ Backspace: () => {
5805
+ if (!this.editor || !this.nodeType) return false;
5806
+ const { state, view } = this.editor;
5807
+ const { $from, empty } = state.selection;
5808
+ if (!empty || $from.parentOffset !== 0) return false;
5809
+ if ($from.parent.content.size !== 0) return false;
5810
+ const ctx = getListItemCursorContext($from);
5811
+ if (ctx && ctx.isInChildrenZone && ctx.paragraphIsEmpty) {
5812
+ if (liftEmptyChildrenZoneParagraph(state, view.dispatch, ctx)) return true;
5813
+ }
5814
+ return false;
5075
5815
  }
5076
5816
  };
5077
5817
  }
5078
5818
  });
5079
5819
 
5080
5820
  // src/nodes/BulletList.ts
5081
- var BulletList = Node.create({
5821
+ var BulletList = Node2.create({
5082
5822
  name: "bulletList",
5083
5823
  group: "block list",
5084
5824
  content: "listItem+",
@@ -5117,6 +5857,24 @@ var BulletList = Node.create({
5117
5857
  }
5118
5858
  ];
5119
5859
  },
5860
+ addFloatingMenuItems() {
5861
+ return [
5862
+ {
5863
+ name: "bullet-list",
5864
+ label: "Bulleted list",
5865
+ description: "Create a simple bulleted list",
5866
+ icon: "listBullets",
5867
+ group: "Lists",
5868
+ priority: 200,
5869
+ keywords: ["bullet", "list", "unordered", "ul"],
5870
+ shortcut: "- ",
5871
+ command: "toggleBulletList",
5872
+ // Don't offer "Bulleted list" while cursor is already inside one,
5873
+ // otherwise picking it lifts the user out of the list.
5874
+ hideWhenInside: ["bulletList"]
5875
+ }
5876
+ ];
5877
+ },
5120
5878
  addKeyboardShortcuts() {
5121
5879
  const { editor } = this;
5122
5880
  return {
@@ -5145,7 +5903,7 @@ var BulletList = Node.create({
5145
5903
  });
5146
5904
 
5147
5905
  // src/nodes/OrderedList.ts
5148
- var OrderedList = Node.create({
5906
+ var OrderedList = Node2.create({
5149
5907
  name: "orderedList",
5150
5908
  group: "block list",
5151
5909
  content: "listItem+",
@@ -5202,6 +5960,22 @@ var OrderedList = Node.create({
5202
5960
  }
5203
5961
  ];
5204
5962
  },
5963
+ addFloatingMenuItems() {
5964
+ return [
5965
+ {
5966
+ name: "ordered-list",
5967
+ label: "Numbered list",
5968
+ description: "Create a numbered list",
5969
+ icon: "listNumbers",
5970
+ group: "Lists",
5971
+ priority: 190,
5972
+ keywords: ["ordered", "numbered", "list", "ol", "1."],
5973
+ shortcut: "1. ",
5974
+ command: "toggleOrderedList",
5975
+ hideWhenInside: ["orderedList"]
5976
+ }
5977
+ ];
5978
+ },
5205
5979
  addKeyboardShortcuts() {
5206
5980
  const { editor } = this;
5207
5981
  return {
@@ -5232,7 +6006,7 @@ var OrderedList = Node.create({
5232
6006
  ];
5233
6007
  }
5234
6008
  });
5235
- var HorizontalRule = Node.create({
6009
+ var HorizontalRule = Node2.create({
5236
6010
  name: "horizontalRule",
5237
6011
  group: "block",
5238
6012
  addOptions() {
@@ -5253,19 +6027,31 @@ var HorizontalRule = Node.create({
5253
6027
  const { $from } = tr.selection;
5254
6028
  const parent = $from.parent;
5255
6029
  if (!parent.isTextblock) return false;
6030
+ const paragraphType = state$1.schema.nodes["paragraph"];
6031
+ const hrNode = this.nodeType.create();
6032
+ const trailingParagraph = paragraphType?.create();
6033
+ const nodes = trailingParagraph ? [hrNode, trailingParagraph] : [hrNode];
6034
+ const listRange = splitListForInsert(state$1, tr);
6035
+ if (listRange) {
6036
+ if (!dispatch) return true;
6037
+ tr.replaceWith(listRange.from, listRange.to, nodes);
6038
+ const sel = state.TextSelection.findFrom(
6039
+ tr.doc.resolve(listRange.from + 1),
6040
+ 1
6041
+ );
6042
+ if (sel) tr.setSelection(sel);
6043
+ dispatch(tr.scrollIntoView());
6044
+ return true;
6045
+ }
5256
6046
  if (dispatch) {
5257
6047
  if (parent.content.size === 0 && parent.type.name === "paragraph") {
5258
6048
  const from = $from.before();
5259
6049
  const to = $from.after();
5260
- const paragraph = state$1.schema.nodes["paragraph"]?.create();
5261
- const nodes = paragraph ? [this.nodeType.create(), paragraph] : [this.nodeType.create()];
5262
6050
  tr.replaceWith(from, to, nodes);
5263
6051
  const sel = state.TextSelection.findFrom(tr.doc.resolve(from + 1), 1);
5264
6052
  if (sel) tr.setSelection(sel);
5265
6053
  } else {
5266
6054
  const end = $from.after();
5267
- const paragraph = state$1.schema.nodes["paragraph"]?.create();
5268
- const nodes = paragraph ? [this.nodeType.create(), paragraph] : [this.nodeType.create()];
5269
6055
  tr.insert(end, nodes);
5270
6056
  const sel = state.TextSelection.findFrom(tr.doc.resolve(end + 1), 1);
5271
6057
  if (sel) tr.setSelection(sel);
@@ -5289,6 +6075,21 @@ var HorizontalRule = Node.create({
5289
6075
  }
5290
6076
  ];
5291
6077
  },
6078
+ addFloatingMenuItems() {
6079
+ return [
6080
+ {
6081
+ name: "horizontal-rule",
6082
+ label: "Divider",
6083
+ description: "Insert a horizontal rule",
6084
+ icon: "minus",
6085
+ group: "Basic",
6086
+ priority: 150,
6087
+ keywords: ["divider", "hr", "line", "separator", "horizontal rule"],
6088
+ shortcut: "--- ",
6089
+ command: "setHorizontalRule"
6090
+ }
6091
+ ];
6092
+ },
5292
6093
  addInputRules() {
5293
6094
  const { nodeType } = this;
5294
6095
  if (!nodeType) {
@@ -5319,7 +6120,7 @@ var HorizontalRule = Node.create({
5319
6120
  });
5320
6121
 
5321
6122
  // src/nodes/HardBreak.ts
5322
- var HardBreak = Node.create({
6123
+ var HardBreak = Node2.create({
5323
6124
  name: "hardBreak",
5324
6125
  group: "inline",
5325
6126
  inline: true,
@@ -5379,9 +6180,84 @@ var HardBreak = Node.create({
5379
6180
  };
5380
6181
  }
5381
6182
  });
5382
- var TaskItem = Node.create({
6183
+
6184
+ // src/nodes/TaskItemNodeView.ts
6185
+ var TaskItemNodeView = class {
6186
+ dom;
6187
+ contentDOM;
6188
+ label;
6189
+ input;
6190
+ view;
6191
+ nodeType;
6192
+ getPos;
6193
+ node;
6194
+ constructor({ options, node, view, getPos }) {
6195
+ this.view = view;
6196
+ this.nodeType = node.type;
6197
+ this.node = node;
6198
+ this.getPos = getPos;
6199
+ this.dom = document.createElement("li");
6200
+ for (const [key, value] of Object.entries(options.HTMLAttributes)) {
6201
+ if (value !== null && value !== void 0) {
6202
+ this.dom.setAttribute(key, typeof value === "string" ? value : JSON.stringify(value));
6203
+ }
6204
+ }
6205
+ this.dom.setAttribute("data-type", "taskItem");
6206
+ this.dom.setAttribute("data-checked", node.attrs["checked"] ? "true" : "false");
6207
+ this.label = document.createElement("label");
6208
+ this.label.setAttribute("contenteditable", "false");
6209
+ this.input = document.createElement("input");
6210
+ this.input.type = "checkbox";
6211
+ this.input.checked = !!node.attrs["checked"];
6212
+ this.input.disabled = !view.editable;
6213
+ this.input.setAttribute("aria-label", "Task status");
6214
+ this.label.appendChild(this.input);
6215
+ this.dom.appendChild(this.label);
6216
+ this.contentDOM = document.createElement("div");
6217
+ this.dom.appendChild(this.contentDOM);
6218
+ this.input.addEventListener("change", this.handleChange);
6219
+ }
6220
+ handleChange = (event) => {
6221
+ event.preventDefault();
6222
+ if (!this.view.editable) {
6223
+ this.input.checked = !!this.node.attrs["checked"];
6224
+ return;
6225
+ }
6226
+ const pos = this.getPos();
6227
+ if (pos === void 0) return;
6228
+ const { state, dispatch } = this.view;
6229
+ const tr = state.tr.setNodeMarkup(pos, void 0, {
6230
+ ...this.node.attrs,
6231
+ checked: this.input.checked
6232
+ });
6233
+ dispatch(tr);
6234
+ };
6235
+ update(node) {
6236
+ if (node.type !== this.nodeType) return false;
6237
+ this.node = node;
6238
+ const checked = !!node.attrs["checked"];
6239
+ this.dom.setAttribute("data-checked", checked ? "true" : "false");
6240
+ this.input.checked = checked;
6241
+ this.input.disabled = !this.view.editable;
6242
+ return true;
6243
+ }
6244
+ stopEvent(event) {
6245
+ return event.target instanceof Node && this.label.contains(event.target);
6246
+ }
6247
+ ignoreMutation(mutation) {
6248
+ return mutation.target instanceof Node && this.label.contains(mutation.target);
6249
+ }
6250
+ destroy() {
6251
+ this.input.removeEventListener("change", this.handleChange);
6252
+ }
6253
+ };
6254
+
6255
+ // src/nodes/TaskItem.ts
6256
+ var TaskItem = Node2.create({
5383
6257
  name: "taskItem",
5384
- content: "block+",
6258
+ // Paragraph must be the first child so flex alignment binds to the label
6259
+ // baseline; a heading-first child would visually break the checkbox.
6260
+ content: "paragraph block*",
5385
6261
  defining: true,
5386
6262
  addOptions() {
5387
6263
  return {
@@ -5435,6 +6311,10 @@ var TaskItem = Node.create({
5435
6311
  ["div", 0]
5436
6312
  ];
5437
6313
  },
6314
+ addNodeView() {
6315
+ const options = this.options;
6316
+ return (node, view, getPos) => new TaskItemNodeView({ options, node, view, getPos });
6317
+ },
5438
6318
  addCommands() {
5439
6319
  const { name } = this;
5440
6320
  return {
@@ -5470,7 +6350,16 @@ var TaskItem = Node.create({
5470
6350
  const { state: state$1, view } = this.editor;
5471
6351
  const { $from } = state$1.selection;
5472
6352
  if ($from.depth < 1 || $from.node(-1).type !== this.nodeType) return false;
5473
- if (schemaList.splitListItem(this.nodeType)(state$1, view.dispatch)) return true;
6353
+ if ($from.parent.type.name !== "paragraph") return false;
6354
+ const ctx = getListItemCursorContext($from);
6355
+ if (ctx?.isInChildrenZone) {
6356
+ if (ctx.paragraphIsEmpty) {
6357
+ if (insertChildrenZoneSibling(state$1, view.dispatch, ctx)) return true;
6358
+ } else {
6359
+ if (commands.splitBlock(state$1, view.dispatch)) return true;
6360
+ }
6361
+ }
6362
+ if (schemaList.splitListItem(this.nodeType, { checked: false })(state$1, view.dispatch)) return true;
5474
6363
  if ($from.parent.content.size === 0) {
5475
6364
  const listItemType = state$1.schema.nodes["listItem"];
5476
6365
  if (listItemType) {
@@ -5526,6 +6415,12 @@ var TaskItem = Node.create({
5526
6415
  const { state, view } = this.editor;
5527
6416
  const { $from, empty } = state.selection;
5528
6417
  if (!empty || $from.parentOffset !== 0) return false;
6418
+ if ($from.parent.content.size === 0) {
6419
+ const ctx = getListItemCursorContext($from);
6420
+ if (ctx && ctx.isInChildrenZone && ctx.paragraphIsEmpty) {
6421
+ if (liftEmptyChildrenZoneParagraph(state, view.dispatch, ctx)) return true;
6422
+ }
6423
+ }
5529
6424
  let taskItemDepth = -1;
5530
6425
  for (let d = $from.depth; d > 0; d--) {
5531
6426
  if ($from.node(d).type === this.nodeType) {
@@ -5545,7 +6440,7 @@ var TaskItem = Node.create({
5545
6440
  });
5546
6441
 
5547
6442
  // src/nodes/TaskList.ts
5548
- var TaskList = Node.create({
6443
+ var TaskList = Node2.create({
5549
6444
  name: "taskList",
5550
6445
  group: "block list",
5551
6446
  content: "taskItem+",
@@ -5606,6 +6501,22 @@ var TaskList = Node.create({
5606
6501
  }
5607
6502
  ];
5608
6503
  },
6504
+ addFloatingMenuItems() {
6505
+ return [
6506
+ {
6507
+ name: "task-list",
6508
+ label: "To-do list",
6509
+ description: "Track tasks with a checkbox list",
6510
+ icon: "listChecks",
6511
+ group: "Lists",
6512
+ priority: 180,
6513
+ keywords: ["todo", "task", "checkbox", "check"],
6514
+ shortcut: "[ ] ",
6515
+ command: "toggleTaskList",
6516
+ hideWhenInside: ["taskList"]
6517
+ }
6518
+ ];
6519
+ },
5609
6520
  addExtensions() {
5610
6521
  return [TaskItem];
5611
6522
  },
@@ -6531,7 +7442,8 @@ var TextStyle = Mark.create({
6531
7442
  getAttrs: (element) => {
6532
7443
  if (typeof element === "string") return false;
6533
7444
  const hasStyles = element.hasAttribute("style");
6534
- if (!hasStyles) return false;
7445
+ const hasColorTokens = element.hasAttribute("data-text-color") || element.hasAttribute("data-bg-color");
7446
+ if (!hasStyles && !hasColorTokens) return false;
6535
7447
  return {};
6536
7448
  }
6537
7449
  },
@@ -6800,6 +7712,123 @@ var Placeholder = Extension.create({
6800
7712
  ];
6801
7713
  }
6802
7714
  });
7715
+ var LIST_ITEM_TYPES3 = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
7716
+ var LIST_WRAPPER_TYPES2 = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
7717
+ function isCursorInsideListItem(state) {
7718
+ const { $from } = state.selection;
7719
+ for (let d = $from.depth; d > 0; d--) {
7720
+ if (LIST_ITEM_TYPES3.has($from.node(d).type.name)) return true;
7721
+ }
7722
+ return false;
7723
+ }
7724
+ function indentBlockAsListChild(state$1, dispatch) {
7725
+ const { selection } = state$1;
7726
+ let blockIndex;
7727
+ let blockNode;
7728
+ let blockStart;
7729
+ let blockEnd;
7730
+ if (selection instanceof state.NodeSelection) {
7731
+ const node = selection.node;
7732
+ const $pos = selection.$from;
7733
+ if ($pos.depth !== 0) return false;
7734
+ if (isCursorInsideListItem(state$1)) return false;
7735
+ blockIndex = $pos.index(0);
7736
+ blockNode = node;
7737
+ blockStart = selection.from;
7738
+ blockEnd = selection.to;
7739
+ } else {
7740
+ if (!selection.empty) return false;
7741
+ if (isCursorInsideListItem(state$1)) return false;
7742
+ const { $from } = selection;
7743
+ if ($from.depth !== 1) return false;
7744
+ blockIndex = $from.index(0);
7745
+ blockNode = $from.node(1);
7746
+ blockStart = $from.before(1);
7747
+ blockEnd = $from.after(1);
7748
+ }
7749
+ if (blockIndex === 0) return false;
7750
+ const prevSibling = state$1.doc.child(blockIndex - 1);
7751
+ if (!LIST_WRAPPER_TYPES2.has(prevSibling.type.name)) return false;
7752
+ let wrapperPos = 0;
7753
+ for (let i = 0; i < blockIndex - 1; i++) {
7754
+ wrapperPos += state$1.doc.child(i).nodeSize;
7755
+ }
7756
+ const tr = state$1.tr;
7757
+ const result = insertAsListItemChild({
7758
+ tr,
7759
+ wrapperPos,
7760
+ blockNode,
7761
+ sourceRange: { from: blockStart, to: blockEnd }
7762
+ });
7763
+ if (!result.ok || result.insertedAt === void 0) return false;
7764
+ if (!dispatch) return true;
7765
+ tr.setSelection(state.Selection.near(tr.doc.resolve(result.insertedAt + 1)));
7766
+ dispatch(tr.scrollIntoView());
7767
+ return true;
7768
+ }
7769
+ function outdentBlockFromListItem(state$1, dispatch) {
7770
+ const { selection } = state$1;
7771
+ if (!selection.empty) return false;
7772
+ const { $from } = selection;
7773
+ let listItemDepth = -1;
7774
+ for (let d = $from.depth; d > 0; d--) {
7775
+ if (LIST_ITEM_TYPES3.has($from.node(d).type.name)) {
7776
+ listItemDepth = d;
7777
+ break;
7778
+ }
7779
+ }
7780
+ if (listItemDepth === -1) return false;
7781
+ const blockDepth = listItemDepth + 1;
7782
+ if ($from.depth < blockDepth) return false;
7783
+ const listItem = $from.node(listItemDepth);
7784
+ const blockIndexInItem = $from.index(listItemDepth);
7785
+ if (blockIndexInItem === 0) return false;
7786
+ if (blockIndexInItem !== listItem.childCount - 1) return false;
7787
+ const wrapperDepth = listItemDepth - 1;
7788
+ if (wrapperDepth < 0) return false;
7789
+ const liIndexInWrapper = $from.index(wrapperDepth);
7790
+ const wrapper = $from.node(wrapperDepth);
7791
+ if (liIndexInWrapper !== wrapper.childCount - 1) return false;
7792
+ if (wrapperDepth - 1 < 0) return false;
7793
+ const wrapperParent = $from.node(wrapperDepth - 1);
7794
+ const wrapperIndexInParent = $from.index(wrapperDepth - 1);
7795
+ const blockNode = $from.node(blockDepth);
7796
+ if (!wrapperParent.canReplaceWith(
7797
+ wrapperIndexInParent + 1,
7798
+ wrapperIndexInParent + 1,
7799
+ blockNode.type
7800
+ )) {
7801
+ return false;
7802
+ }
7803
+ if (!dispatch) return true;
7804
+ const blockStart = $from.before(blockDepth);
7805
+ const blockEnd = $from.after(blockDepth);
7806
+ const wrapperEnd = $from.after(wrapperDepth);
7807
+ const tr = state$1.tr;
7808
+ const blockSize = blockEnd - blockStart;
7809
+ tr.delete(blockStart, blockEnd);
7810
+ const insertAt = wrapperEnd - blockSize;
7811
+ tr.insert(insertAt, blockNode);
7812
+ tr.setSelection(state.Selection.near(tr.doc.resolve(insertAt + 1)));
7813
+ dispatch(tr.scrollIntoView());
7814
+ return true;
7815
+ }
7816
+ var ListIndent = Extension.create({
7817
+ name: "listIndent",
7818
+ addKeyboardShortcuts() {
7819
+ const { editor } = this;
7820
+ return {
7821
+ Tab: () => {
7822
+ if (!editor) return false;
7823
+ return indentBlockAsListChild(editor.state, editor.view.dispatch);
7824
+ },
7825
+ "Shift-Tab": () => {
7826
+ if (!editor) return false;
7827
+ return outdentBlockFromListItem(editor.state, editor.view.dispatch);
7828
+ }
7829
+ };
7830
+ }
7831
+ });
6803
7832
  var characterCountPluginKey = new state.PluginKey("characterCount");
6804
7833
  var CharacterCount = Extension.create({
6805
7834
  name: "characterCount",
@@ -7226,6 +8255,10 @@ var LineHeight = Extension.create({
7226
8255
  }
7227
8256
  });
7228
8257
  function generateUUID() {
8258
+ if (typeof globalThis !== "undefined") {
8259
+ const c = globalThis.crypto;
8260
+ if (c && typeof c.randomUUID === "function") return c.randomUUID();
8261
+ }
7229
8262
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
7230
8263
  const r = Math.random() * 16 | 0;
7231
8264
  const v = c === "x" ? r : r & 3 | 8;
@@ -7311,15 +8344,31 @@ var UniqueID = Extension.create({
7311
8344
  );
7312
8345
  };
7313
8346
  const assignMissingIDs = (doc, tr) => {
8347
+ const seen = /* @__PURE__ */ new Set();
7314
8348
  doc.descendants((node, pos) => {
7315
8349
  if (!types.includes(node.type.name)) return;
7316
8350
  const existingID = node.attrs[attributeName];
7317
8351
  if (!existingID) {
7318
- tr.setNodeMarkup(pos, void 0, {
8352
+ let id = generateID();
8353
+ while (seen.has(id)) id = generateID();
8354
+ seen.add(id);
8355
+ tr.setNodeMarkup(tr.mapping.map(pos), void 0, {
8356
+ ...node.attrs,
8357
+ [attributeName]: id
8358
+ });
8359
+ return;
8360
+ }
8361
+ if (seen.has(existingID)) {
8362
+ let id = generateID();
8363
+ while (seen.has(id)) id = generateID();
8364
+ seen.add(id);
8365
+ tr.setNodeMarkup(tr.mapping.map(pos), void 0, {
7319
8366
  ...node.attrs,
7320
- [attributeName]: generateID()
8367
+ [attributeName]: id
7321
8368
  });
8369
+ return;
7322
8370
  }
8371
+ seen.add(existingID);
7323
8372
  });
7324
8373
  };
7325
8374
  return [
@@ -7356,7 +8405,148 @@ var UniqueID = Extension.create({
7356
8405
  ];
7357
8406
  }
7358
8407
  });
7359
- var Selection3 = Extension.create({
8408
+
8409
+ // src/extensions/BlockColor.ts
8410
+ var DEFAULT_BLOCK_COLORS = [
8411
+ "gray",
8412
+ "brown",
8413
+ "orange",
8414
+ "yellow",
8415
+ "green",
8416
+ "blue",
8417
+ "purple",
8418
+ "pink",
8419
+ "red"
8420
+ ];
8421
+ var DEFAULT_BLOCK_COLOR_TYPES = [
8422
+ "paragraph",
8423
+ "heading",
8424
+ "blockquote",
8425
+ "bulletList",
8426
+ "orderedList",
8427
+ "taskList",
8428
+ "listItem",
8429
+ "taskItem"
8430
+ ];
8431
+ function stripInlineColorConflicts(tr, state, from, to, which) {
8432
+ const textStyleType = state.schema.marks["textStyle"];
8433
+ if (!textStyleType) return;
8434
+ const inlineKeys = [];
8435
+ if (which === "text" || which === "both") inlineKeys.push("color", "colorToken");
8436
+ if (which === "bg" || which === "both") inlineKeys.push("backgroundColor", "backgroundColorToken");
8437
+ state.doc.nodesBetween(from, to, (node, pos) => {
8438
+ if (!node.isText) return true;
8439
+ const existing = node.marks.find((m) => m.type === textStyleType);
8440
+ if (!existing) return false;
8441
+ const hasConflict = inlineKeys.some((k) => existing.attrs[k] !== null && existing.attrs[k] !== void 0);
8442
+ if (!hasConflict) return false;
8443
+ const start = Math.max(pos, from);
8444
+ const end = Math.min(pos + node.nodeSize, to);
8445
+ const newAttrs = { ...existing.attrs };
8446
+ for (const k of inlineKeys) newAttrs[k] = null;
8447
+ const stillUsed = Object.values(newAttrs).some((v) => v !== null && v !== void 0);
8448
+ tr.removeMark(start, end, existing);
8449
+ if (stillUsed) tr.addMark(start, end, textStyleType.create(newAttrs));
8450
+ return false;
8451
+ });
8452
+ }
8453
+ var BlockColor = Extension.create({
8454
+ name: "blockColor",
8455
+ addOptions() {
8456
+ return {
8457
+ types: DEFAULT_BLOCK_COLOR_TYPES,
8458
+ bgColors: DEFAULT_BLOCK_COLORS,
8459
+ textColors: DEFAULT_BLOCK_COLORS
8460
+ };
8461
+ },
8462
+ // Defensive fallback: an empty `bgColors`/`textColors` array would render
8463
+ // a broken Colors UI (section title with only the null-reset swatch).
8464
+ // Replace empties with the default palette so users who pass `{}` as an
8465
+ // override still get a working picker. Runs AFTER `addOptions()` merges
8466
+ // user config with defaults, so this only fires for explicit empty arrays.
8467
+ onBeforeCreate() {
8468
+ if (this.options.bgColors.length === 0) this.options.bgColors = DEFAULT_BLOCK_COLORS;
8469
+ if (this.options.textColors.length === 0) this.options.textColors = DEFAULT_BLOCK_COLORS;
8470
+ },
8471
+ addGlobalAttributes() {
8472
+ return [
8473
+ {
8474
+ types: this.options.types,
8475
+ attributes: {
8476
+ bgColor: {
8477
+ default: null,
8478
+ parseHTML: (element) => element.getAttribute("data-bg-color"),
8479
+ renderHTML: (attributes) => {
8480
+ const v = attributes["bgColor"];
8481
+ if (!v) return null;
8482
+ return { "data-bg-color": v };
8483
+ }
8484
+ },
8485
+ textColor: {
8486
+ default: null,
8487
+ parseHTML: (element) => element.getAttribute("data-text-color"),
8488
+ renderHTML: (attributes) => {
8489
+ const v = attributes["textColor"];
8490
+ if (!v) return null;
8491
+ return { "data-text-color": v };
8492
+ }
8493
+ }
8494
+ }
8495
+ }
8496
+ ];
8497
+ },
8498
+ addCommands() {
8499
+ const types = this.options.types;
8500
+ const bgColors = this.options.bgColors;
8501
+ const textColors = this.options.textColors;
8502
+ function findTargetPos(state) {
8503
+ const { $from } = state.selection;
8504
+ for (let depth = $from.depth; depth >= 0; depth--) {
8505
+ const node = $from.node(depth);
8506
+ if (types.includes(node.type.name)) {
8507
+ return depth === 0 ? 0 : $from.before(depth);
8508
+ }
8509
+ }
8510
+ return null;
8511
+ }
8512
+ function setAttr(attr, palette) {
8513
+ return (color) => ({ state, dispatch }) => {
8514
+ if (color !== null && !palette.includes(color)) return false;
8515
+ const pos = findTargetPos(state);
8516
+ if (pos === null) return false;
8517
+ const node = state.doc.nodeAt(pos);
8518
+ if (!node) return false;
8519
+ if (dispatch) {
8520
+ const tr = state.tr.setNodeMarkup(pos, void 0, { ...node.attrs, [attr]: color });
8521
+ stripInlineColorConflicts(tr, state, pos, pos + node.nodeSize, attr === "textColor" ? "text" : "bg");
8522
+ dispatch(tr);
8523
+ }
8524
+ return true;
8525
+ };
8526
+ }
8527
+ return {
8528
+ setBlockBgColor: setAttr("bgColor", bgColors),
8529
+ setBlockTextColor: setAttr("textColor", textColors),
8530
+ unsetBlockColors: () => ({ state, dispatch }) => {
8531
+ const pos = findTargetPos(state);
8532
+ if (pos === null) return false;
8533
+ const node = state.doc.nodeAt(pos);
8534
+ if (!node) return false;
8535
+ if (dispatch) {
8536
+ const tr = state.tr.setNodeMarkup(pos, void 0, {
8537
+ ...node.attrs,
8538
+ bgColor: null,
8539
+ textColor: null
8540
+ });
8541
+ stripInlineColorConflicts(tr, state, pos, pos + node.nodeSize, "both");
8542
+ dispatch(tr);
8543
+ }
8544
+ return true;
8545
+ }
8546
+ };
8547
+ }
8548
+ });
8549
+ var Selection4 = Extension.create({
7360
8550
  name: "selection",
7361
8551
  addStorage() {
7362
8552
  return {
@@ -7746,9 +8936,19 @@ var TextColor = Extension.create({
7746
8936
  },
7747
8937
  renderHTML: (attributes) => {
7748
8938
  const color = attributes["color"];
7749
- if (!color) return null;
8939
+ const token = attributes["colorToken"];
8940
+ if (!color || token) return null;
7750
8941
  return { style: `color: ${color}` };
7751
8942
  }
8943
+ },
8944
+ colorToken: {
8945
+ default: null,
8946
+ parseHTML: (element) => element.getAttribute("data-text-color"),
8947
+ renderHTML: (attributes) => {
8948
+ const token = attributes["colorToken"];
8949
+ if (!token) return null;
8950
+ return { "data-text-color": token };
8951
+ }
7752
8952
  }
7753
8953
  }
7754
8954
  }
@@ -7756,13 +8956,28 @@ var TextColor = Extension.create({
7756
8956
  },
7757
8957
  addCommands() {
7758
8958
  return {
8959
+ // Hex-based color. Mutual exclusion: clears the named token so the
8960
+ // legacy hex picker and Notion-style picker can't write conflicting
8961
+ // values to the same mark.
7759
8962
  setTextColor: (color) => ({ commands }) => {
7760
- return commands.setMark("textStyle", { color });
8963
+ return commands.setMark("textStyle", { color, colorToken: null });
7761
8964
  },
7762
8965
  unsetTextColor: () => ({ commands }) => {
7763
8966
  if (!commands.setMark("textStyle", { color: null })) return false;
7764
8967
  commands.removeEmptyTextStyle();
7765
8968
  return true;
8969
+ },
8970
+ // Named-token color. Mutual exclusion: clears the hex `color` so the
8971
+ // theme-aware data attribute is the only source of truth.
8972
+ setTextColorToken: (token) => ({ commands }) => {
8973
+ if (!commands.setMark("textStyle", { colorToken: token, color: null })) return false;
8974
+ if (token === null) commands.removeEmptyTextStyle();
8975
+ return true;
8976
+ },
8977
+ unsetTextColorToken: () => ({ commands }) => {
8978
+ if (!commands.setMark("textStyle", { colorToken: null })) return false;
8979
+ commands.removeEmptyTextStyle();
8980
+ return true;
7766
8981
  }
7767
8982
  };
7768
8983
  },
@@ -7864,9 +9079,19 @@ var Highlight = Extension.create({
7864
9079
  },
7865
9080
  renderHTML: (attributes) => {
7866
9081
  const bg = attributes["backgroundColor"];
7867
- if (!bg) return null;
9082
+ const token = attributes["backgroundColorToken"];
9083
+ if (!bg || token) return null;
7868
9084
  return { style: `background-color: ${bg}` };
7869
9085
  }
9086
+ },
9087
+ backgroundColorToken: {
9088
+ default: null,
9089
+ parseHTML: (element) => element.getAttribute("data-bg-color"),
9090
+ renderHTML: (attributes) => {
9091
+ const token = attributes["backgroundColorToken"];
9092
+ if (!token) return null;
9093
+ return { "data-bg-color": token };
9094
+ }
7870
9095
  }
7871
9096
  }
7872
9097
  }
@@ -7875,9 +9100,12 @@ var Highlight = Extension.create({
7875
9100
  addCommands() {
7876
9101
  const defaultColor = this.options.defaultColor;
7877
9102
  return {
9103
+ // Hex-based highlight. Mutual exclusion: clears any named bg token so
9104
+ // the legacy hex picker and the Notion-style picker can't write
9105
+ // conflicting values to the same mark.
7878
9106
  setHighlight: (attributes) => ({ commands }) => {
7879
9107
  const color = attributes?.color ?? defaultColor;
7880
- return commands.setMark("textStyle", { backgroundColor: color });
9108
+ return commands.setMark("textStyle", { backgroundColor: color, backgroundColorToken: null });
7881
9109
  },
7882
9110
  unsetHighlight: () => ({ commands }) => {
7883
9111
  if (!commands.setMark("textStyle", { backgroundColor: null })) return false;
@@ -7893,12 +9121,12 @@ var Highlight = Extension.create({
7893
9121
  if (empty) {
7894
9122
  const marks = state.storedMarks ?? state.doc.resolve(from).marks();
7895
9123
  const mark = markType.isInSet(marks);
7896
- hasHighlight = !!mark?.attrs["backgroundColor"];
9124
+ hasHighlight = !!mark?.attrs["backgroundColor"] || !!mark?.attrs["backgroundColorToken"];
7897
9125
  } else {
7898
9126
  state.doc.nodesBetween(from, to, (node) => {
7899
9127
  if (hasHighlight) return false;
7900
9128
  const mark = markType.isInSet(node.marks);
7901
- if (mark?.attrs["backgroundColor"]) {
9129
+ if (mark?.attrs["backgroundColor"] || mark?.attrs["backgroundColorToken"]) {
7902
9130
  hasHighlight = true;
7903
9131
  return false;
7904
9132
  }
@@ -7906,11 +9134,26 @@ var Highlight = Extension.create({
7906
9134
  });
7907
9135
  }
7908
9136
  if (hasHighlight) {
7909
- commands.setMark("textStyle", { backgroundColor: null });
9137
+ commands.setMark("textStyle", { backgroundColor: null, backgroundColorToken: null });
7910
9138
  commands.removeEmptyTextStyle();
7911
9139
  return true;
7912
9140
  }
7913
- return commands.setMark("textStyle", { backgroundColor: color });
9141
+ return commands.setMark("textStyle", { backgroundColor: color, backgroundColorToken: null });
9142
+ },
9143
+ // Named-token highlight. Mutual exclusion: clears the hex
9144
+ // backgroundColor so the theme-aware data attribute is the sole source
9145
+ // of truth.
9146
+ setBackgroundColorToken: (token) => ({ commands }) => {
9147
+ if (!commands.setMark("textStyle", { backgroundColorToken: token, backgroundColor: null })) {
9148
+ return false;
9149
+ }
9150
+ if (token === null) commands.removeEmptyTextStyle();
9151
+ return true;
9152
+ },
9153
+ unsetBackgroundColorToken: () => ({ commands }) => {
9154
+ if (!commands.setMark("textStyle", { backgroundColorToken: null })) return false;
9155
+ commands.removeEmptyTextStyle();
9156
+ return true;
7914
9157
  }
7915
9158
  };
7916
9159
  },
@@ -8154,6 +9397,61 @@ var FontSize = Extension.create({
8154
9397
  }
8155
9398
  });
8156
9399
 
9400
+ // src/extensions/NotionColorPicker.ts
9401
+ var DEFAULT_NOTION_COLOR_PALETTE = Object.freeze([
9402
+ "gray",
9403
+ "brown",
9404
+ "orange",
9405
+ "yellow",
9406
+ "green",
9407
+ "blue",
9408
+ "purple",
9409
+ "pink",
9410
+ "red"
9411
+ ]);
9412
+ var NotionColorPicker = Extension.create({
9413
+ name: "notionColorPicker",
9414
+ // The picker writes textStyle.colorToken / textStyle.backgroundColorToken,
9415
+ // so TextStyle must be present. TextColor and Highlight are recommended
9416
+ // (they own those attribute schemas) but not strictly required: a host app
9417
+ // could supply its own attribute providers if it really wants to.
9418
+ dependencies: ["textStyle"],
9419
+ addOptions() {
9420
+ return {
9421
+ palette: DEFAULT_NOTION_COLOR_PALETTE
9422
+ };
9423
+ },
9424
+ addStorage() {
9425
+ return {
9426
+ isOpen: false
9427
+ };
9428
+ },
9429
+ addToolbarItems() {
9430
+ return [
9431
+ {
9432
+ type: "button",
9433
+ name: "notionColor",
9434
+ // emitEvent takes priority over `command` at click time. The `command`
9435
+ // field is required by the ToolbarButton schema, so we point it at a
9436
+ // harmless built-in that fires only if a host disables the popover UI.
9437
+ command: "focus",
9438
+ // The "A with an underline" glyph is the closest match in our icon
9439
+ // set; the framework wrapper can repaint the underline to reflect
9440
+ // the current selection's color.
9441
+ icon: "textAUnderline",
9442
+ label: "Text and background color",
9443
+ emitEvent: "notionColorOpen",
9444
+ group: "textStyle",
9445
+ priority: 250,
9446
+ // Bubble-menu-only: the legacy hex pickers (TextColor / Highlight)
9447
+ // stay in the main toolbar; this token-based picker is a Notion-mode
9448
+ // affordance surfaced beside the text-format buttons in the bubble.
9449
+ toolbar: false
9450
+ }
9451
+ ];
9452
+ }
9453
+ });
9454
+
8157
9455
  // src/extensions/ClearFormatting.ts
8158
9456
  var ClearFormatting = Extension.create({
8159
9457
  name: "clearFormatting",
@@ -8242,9 +9540,18 @@ function linkPopoverPlugin({ editor, markType, protocols }) {
8242
9540
  return new DOMRect(coords.left, coords.top, 0, coords.bottom - coords.top);
8243
9541
  }
8244
9542
  };
9543
+ if (anchorElement) {
9544
+ const editorEl = anchorElement.closest(".dm-editor");
9545
+ if (editorEl && el.parentElement !== editorEl) {
9546
+ editorEl.appendChild(el);
9547
+ }
9548
+ } else if (el.parentElement !== document.body) {
9549
+ document.body.appendChild(el);
9550
+ }
9551
+ copyThemeClass(editor.view, el);
8245
9552
  cleanupFloating?.();
8246
9553
  cleanupFloating = positionFloating(reference, el, {
8247
- placement: "bottom",
9554
+ placement: anchorElement ? "bottom-start" : "bottom",
8248
9555
  offsetValue: 4
8249
9556
  });
8250
9557
  input.focus();
@@ -8501,6 +9808,7 @@ function createBubbleMenuPlugin(options) {
8501
9808
  if (!target) return;
8502
9809
  if (element.contains(target)) return;
8503
9810
  if (editor.view.dom.contains(target)) return;
9811
+ if (target instanceof HTMLElement && target.closest("[data-dm-editor-ui]")) return;
8504
9812
  hideMenu();
8505
9813
  suppressed = true;
8506
9814
  };
@@ -8577,8 +9885,10 @@ function createBubbleMenuPlugin(options) {
8577
9885
  });
8578
9886
  };
8579
9887
  const onBlur = ({ event }) => {
8580
- if (event.relatedTarget && element.contains(event.relatedTarget)) {
8581
- return;
9888
+ const related = event.relatedTarget;
9889
+ if (related instanceof HTMLElement) {
9890
+ if (element.contains(related)) return;
9891
+ if (related.closest("[data-dm-editor-ui]")) return;
8582
9892
  }
8583
9893
  hideMenu();
8584
9894
  };
@@ -8678,132 +9988,6 @@ var BubbleMenu = Extension.create({
8678
9988
  ];
8679
9989
  }
8680
9990
  });
8681
- var floatingMenuPluginKey = new state.PluginKey("floatingMenu");
8682
- function defaultShouldShow2({
8683
- editor,
8684
- state
8685
- }) {
8686
- if (!editor.isEditable) return false;
8687
- const { selection } = state;
8688
- const { $from, empty } = selection;
8689
- if (!empty) return false;
8690
- if ($from.parent.type.name !== "paragraph") return false;
8691
- if ($from.parent.content.size !== 0) return false;
8692
- if ($from.parentOffset !== 0) return false;
8693
- return true;
8694
- }
8695
- function createFloatingMenuPlugin(options) {
8696
- const {
8697
- pluginKey,
8698
- editor,
8699
- element,
8700
- shouldShow = defaultShouldShow2,
8701
- offset: offset2 = 0
8702
- } = options;
8703
- if (!element.getAttribute("role")) {
8704
- element.setAttribute("role", "toolbar");
8705
- element.setAttribute("aria-label", "Floating menu");
8706
- }
8707
- let cleanupFloating = null;
8708
- const updatePosition = (view) => {
8709
- const { selection } = view.state;
8710
- const { $from } = selection;
8711
- const depth = $from.depth;
8712
- const startPos = $from.start(depth);
8713
- const domNode = view.nodeDOM(startPos - 1);
8714
- if (domNode instanceof HTMLElement) {
8715
- cleanupFloating?.();
8716
- cleanupFloating = positionFloatingOnce(domNode, element, {
8717
- placement: "bottom-start",
8718
- offsetValue: offset2
8719
- });
8720
- element.setAttribute("data-show", "");
8721
- }
8722
- };
8723
- const hideMenu = () => {
8724
- cleanupFloating?.();
8725
- cleanupFloating = null;
8726
- element.removeAttribute("data-show");
8727
- };
8728
- hideMenu();
8729
- return new state.Plugin({
8730
- key: pluginKey,
8731
- view: (editorView) => {
8732
- const editorEl = editorView.dom.closest(".dm-editor");
8733
- if (editorEl && element.parentElement !== editorEl) {
8734
- editorEl.appendChild(element);
8735
- }
8736
- const onFocus = () => {
8737
- const visible = shouldShow({
8738
- editor,
8739
- view: editor.view,
8740
- state: editor.view.state
8741
- });
8742
- if (visible) {
8743
- updatePosition(editor.view);
8744
- } else {
8745
- hideMenu();
8746
- }
8747
- };
8748
- const onBlur = ({ event }) => {
8749
- if (event.relatedTarget && element.contains(event.relatedTarget)) {
8750
- return;
8751
- }
8752
- hideMenu();
8753
- };
8754
- editor.on("focus", onFocus);
8755
- editor.on("blur", onBlur);
8756
- return {
8757
- update: (view) => {
8758
- const visible = shouldShow({
8759
- editor,
8760
- view,
8761
- state: view.state
8762
- });
8763
- if (visible) {
8764
- updatePosition(view);
8765
- } else {
8766
- hideMenu();
8767
- }
8768
- },
8769
- destroy: () => {
8770
- hideMenu();
8771
- editor.off("focus", onFocus);
8772
- editor.off("blur", onBlur);
8773
- }
8774
- };
8775
- }
8776
- });
8777
- }
8778
- var FloatingMenu = Extension.create({
8779
- name: "floatingMenu",
8780
- addOptions() {
8781
- return {
8782
- element: null,
8783
- shouldShow: defaultShouldShow2,
8784
- offset: 0
8785
- };
8786
- },
8787
- addProseMirrorPlugins() {
8788
- const { element, shouldShow, offset: offset2 } = this.options;
8789
- if (!element) {
8790
- return [];
8791
- }
8792
- const editor = this.editor;
8793
- if (!editor) {
8794
- return [];
8795
- }
8796
- return [
8797
- createFloatingMenuPlugin({
8798
- pluginKey: floatingMenuPluginKey,
8799
- editor,
8800
- element,
8801
- shouldShow,
8802
- offset: offset2
8803
- })
8804
- ];
8805
- }
8806
- });
8807
9991
 
8808
9992
  // src/extensions/StarterKit.ts
8809
9993
  var StarterKit = Extension.create({
@@ -8846,6 +10030,7 @@ var StarterKit = Extension.create({
8846
10030
  maybeAdd(Gapcursor, this.options.gapcursor);
8847
10031
  maybeAdd(TrailingNode, this.options.trailingNode);
8848
10032
  maybeAdd(ListKeymap, this.options.listKeymap);
10033
+ maybeAdd(ListIndent, this.options.listIndent);
8849
10034
  maybeAdd(LinkPopover, this.options.linkPopover);
8850
10035
  maybeAdd(SelectionDecoration, this.options.selectionDecoration);
8851
10036
  return extensions;
@@ -8860,6 +10045,7 @@ Object.defineProperty(exports, "PluginKey", {
8860
10045
  get: function () { return state.PluginKey; }
8861
10046
  });
8862
10047
  exports.BaseKeymap = BaseKeymap;
10048
+ exports.BlockColor = BlockColor;
8863
10049
  exports.Blockquote = Blockquote;
8864
10050
  exports.Bold = Bold;
8865
10051
  exports.BubbleMenu = BubbleMenu;
@@ -8871,7 +10057,10 @@ exports.ClearFormatting = ClearFormatting;
8871
10057
  exports.Code = Code;
8872
10058
  exports.CodeBlock = CodeBlock;
8873
10059
  exports.CommandManager = CommandManager;
10060
+ exports.DEFAULT_BLOCK_COLORS = DEFAULT_BLOCK_COLORS;
10061
+ exports.DEFAULT_BLOCK_COLOR_TYPES = DEFAULT_BLOCK_COLOR_TYPES;
8874
10062
  exports.DEFAULT_HIGHLIGHT_COLORS = DEFAULT_HIGHLIGHT_COLORS;
10063
+ exports.DEFAULT_NOTION_COLOR_PALETTE = DEFAULT_NOTION_COLOR_PALETTE;
8875
10064
  exports.DEFAULT_TEXT_COLORS = DEFAULT_TEXT_COLORS;
8876
10065
  exports.Document = Document;
8877
10066
  exports.Dropcursor = Dropcursor;
@@ -8879,7 +10068,8 @@ exports.Editor = Editor;
8879
10068
  exports.EventEmitter = EventEmitter;
8880
10069
  exports.Extension = Extension;
8881
10070
  exports.ExtensionManager = ExtensionManager;
8882
- exports.FloatingMenu = FloatingMenu;
10071
+ exports.FLOATING_MENU_NO_FOCUS = FLOATING_MENU_NO_FOCUS;
10072
+ exports.FloatingMenuController = FloatingMenuController;
8883
10073
  exports.Focus = Focus;
8884
10074
  exports.FontFamily = FontFamily;
8885
10075
  exports.FontSize = FontSize;
@@ -8891,17 +10081,20 @@ exports.History = History;
8891
10081
  exports.HorizontalRule = HorizontalRule;
8892
10082
  exports.InvisibleChars = InvisibleChars;
8893
10083
  exports.Italic = Italic;
10084
+ exports.LIST_ITEM_TYPE_NAMES = LIST_ITEM_TYPE_NAMES;
8894
10085
  exports.LineHeight = LineHeight;
8895
10086
  exports.Link = Link;
8896
10087
  exports.LinkPopover = LinkPopover;
10088
+ exports.ListIndent = ListIndent;
8897
10089
  exports.ListItem = ListItem;
8898
10090
  exports.ListKeymap = ListKeymap;
8899
10091
  exports.Mark = Mark;
8900
- exports.Node = Node;
10092
+ exports.Node = Node2;
10093
+ exports.NotionColorPicker = NotionColorPicker;
8901
10094
  exports.OrderedList = OrderedList;
8902
10095
  exports.Paragraph = Paragraph;
8903
10096
  exports.Placeholder = Placeholder;
8904
- exports.Selection = Selection3;
10097
+ exports.Selection = Selection4;
8905
10098
  exports.SelectionDecoration = SelectionDecoration;
8906
10099
  exports.StarterKit = StarterKit;
8907
10100
  exports.Strike = Strike;
@@ -8929,32 +10122,42 @@ exports.builtInCommands = builtInCommands;
8929
10122
  exports.callOrReturn = callOrReturn;
8930
10123
  exports.characterCountPluginKey = characterCountPluginKey;
8931
10124
  exports.clearContent = clearContent;
10125
+ exports.copyThemeClass = copyThemeClass;
8932
10126
  exports.createAccumulatingDispatch = createAccumulatingDispatch;
8933
10127
  exports.createBubbleMenuPlugin = createBubbleMenuPlugin;
8934
10128
  exports.createCanChecker = createCanChecker;
8935
10129
  exports.createChainBuilder = createChainBuilder;
8936
10130
  exports.createDocument = createDocument;
8937
- exports.createFloatingMenuPlugin = createFloatingMenuPlugin;
8938
10131
  exports.defaultBlockAt = defaultBlockAt;
10132
+ exports.defaultBubbleContexts = defaultBubbleContexts;
8939
10133
  exports.defaultIcons = defaultIcons;
8940
10134
  exports.deleteSelection = deleteSelection;
8941
10135
  exports.findChildren = findChildren;
10136
+ exports.findListItemAncestorDepth = findListItemAncestorDepth;
8942
10137
  exports.findParentNode = findParentNode;
8943
- exports.floatingMenuPluginKey = floatingMenuPluginKey;
8944
10138
  exports.focus = focus;
8945
10139
  exports.focusPluginKey = focusPluginKey;
8946
10140
  exports.generateHTML = generateHTML;
8947
10141
  exports.generateJSON = generateJSON;
8948
10142
  exports.generateText = generateText;
10143
+ exports.getListItemCursorContext = getListItemCursorContext;
8949
10144
  exports.getMarkRange = getMarkRange;
10145
+ exports.groupFloatingMenuItems = groupFloatingMenuItems;
10146
+ exports.indentBlockAsListChild = indentBlockAsListChild;
8950
10147
  exports.inlineStyles = inlineStyles;
10148
+ exports.insertAsListItemChild = insertAsListItemChild;
10149
+ exports.insertChildrenZoneSibling = insertChildrenZoneSibling;
8951
10150
  exports.insertContent = insertContent;
8952
10151
  exports.insertText = insertText;
8953
10152
  exports.invisibleCharsPluginKey = invisibleCharsPluginKey;
8954
10153
  exports.isDocumentEmpty = isDocumentEmpty;
10154
+ exports.isInListItemLabel = isInListItemLabel;
10155
+ exports.isInsideListItem = isInsideListItem;
8955
10156
  exports.isNodeEmpty = isNodeEmpty;
8956
10157
  exports.isValidUrl = isValidUrl;
8957
10158
  exports.lift = lift;
10159
+ exports.liftCurrentListItem = liftCurrentListItem;
10160
+ exports.liftEmptyChildrenZoneParagraph = liftEmptyChildrenZoneParagraph;
8958
10161
  exports.linkClickPlugin = linkClickPlugin;
8959
10162
  exports.linkClickPluginKey = linkClickPluginKey;
8960
10163
  exports.linkExitPlugin = linkExitPlugin;
@@ -8964,6 +10167,7 @@ exports.linkPastePluginKey = linkPastePluginKey;
8964
10167
  exports.markInputRule = markInputRule;
8965
10168
  exports.markInputRulePatterns = markInputRulePatterns;
8966
10169
  exports.nodeInputRule = nodeInputRule;
10170
+ exports.outdentBlockFromListItem = outdentBlockFromListItem;
8967
10171
  exports.placeholderPluginKey = placeholderPluginKey;
8968
10172
  exports.positionFloating = positionFloating;
8969
10173
  exports.positionFloatingOnce = positionFloatingOnce;
@@ -8974,6 +10178,8 @@ exports.selectionDecorationPluginKey = selectionDecorationPluginKey;
8974
10178
  exports.setBlockType = setBlockType;
8975
10179
  exports.setContent = setContent;
8976
10180
  exports.setMark = setMark;
10181
+ exports.splitListForInsert = splitListForInsert;
10182
+ exports.stripInlineColorConflicts = stripInlineColorConflicts;
8977
10183
  exports.textInputRule = textInputRule;
8978
10184
  exports.textblockTypeInputRule = textblockTypeInputRule;
8979
10185
  exports.toggleBlockType = toggleBlockType;
@@ -8986,5 +10192,6 @@ exports.unsetMark = unsetMark;
8986
10192
  exports.updateAttributes = updateAttributes;
8987
10193
  exports.wrapIn = wrapIn;
8988
10194
  exports.wrappingInputRule = wrappingInputRule;
10195
+ exports.writeToClipboard = writeToClipboard;
8989
10196
  //# sourceMappingURL=index.cjs.map
8990
10197
  //# sourceMappingURL=index.cjs.map