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