@domternal/core 0.8.0 → 0.9.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
@@ -1018,6 +1018,27 @@ var deleteSelection = () => ({ tr, dispatch }) => {
1018
1018
  dispatch(tr);
1019
1019
  return true;
1020
1020
  };
1021
+ var LIST_WRAPPER_TYPES = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
1022
+ function isEmptyPlaceholderList(node) {
1023
+ if (!LIST_WRAPPER_TYPES.has(node.type.name) || node.childCount !== 1) return false;
1024
+ const item = node.child(0);
1025
+ return item.childCount === 1 && item.child(0).type.name === "paragraph" && item.child(0).content.size === 0;
1026
+ }
1027
+ function stripFitterArtifacts(node) {
1028
+ if (!node.isBlock || node.isLeaf) return node;
1029
+ const children = [];
1030
+ node.forEach((child) => children.push(stripFitterArtifacts(child)));
1031
+ const kept = [];
1032
+ children.forEach((cur, i) => {
1033
+ const next = children[i + 1];
1034
+ if (isEmptyPlaceholderList(cur) && next && LIST_WRAPPER_TYPES.has(next.type.name) && next.type !== cur.type) {
1035
+ return;
1036
+ }
1037
+ kept.push(cur);
1038
+ });
1039
+ const unchanged = kept.length === node.childCount && kept.every((c, i) => c === node.child(i));
1040
+ return unchanged ? node : node.copy(model.Fragment.fromArray(kept));
1041
+ }
1021
1042
  function isJSONContent(content) {
1022
1043
  return typeof content === "object" && content !== null && "type" in content && typeof content.type === "string";
1023
1044
  }
@@ -1040,7 +1061,7 @@ function parseHTMLContent(html, schema, options) {
1040
1061
  const element = document.createElement("div");
1041
1062
  element.innerHTML = html;
1042
1063
  const parser = model.DOMParser.fromSchema(schema);
1043
- return parser.parse(element, options?.parseOptions);
1064
+ return stripFitterArtifacts(parser.parse(element, options?.parseOptions));
1044
1065
  }
1045
1066
  function createDocument(content, schema, options) {
1046
1067
  if (content === null || content === void 0 || content === "") {
@@ -2279,7 +2300,7 @@ function joinListForwards(tr, listType) {
2279
2300
  }
2280
2301
  }
2281
2302
  }
2282
- var toggleList = (listNodeName, listItemNodeName, attributes) => ({ state: state$1, tr, dispatch }) => {
2303
+ var toggleList = (listNodeName, listItemNodeName, attributes, options) => ({ state: state$1, tr, dispatch }) => {
2283
2304
  const listType = state$1.schema.nodes[listNodeName];
2284
2305
  const listItemType = state$1.schema.nodes[listItemNodeName];
2285
2306
  if (!listType || !listItemType) {
@@ -2411,6 +2432,17 @@ var toggleList = (listNodeName, listItemNodeName, attributes) => ({ state: state
2411
2432
  const contentBlocks = collectListContext(tr.doc, from, to);
2412
2433
  const allInTargetList = contentBlocks.length > 0 && contentBlocks.every((b) => b.inTargetList);
2413
2434
  const allInSomeList = contentBlocks.length > 0 && contentBlocks.every((b) => b.inSomeList);
2435
+ if (options?.perItem && from === to && allInSomeList && !allInTargetList) {
2436
+ if (!dispatch) return true;
2437
+ if (liftCurrentListItem(state$1, tr)) {
2438
+ const blockRange = tr.selection.$from.blockRange();
2439
+ if (blockRange) schemaList.wrapRangeInList(tr, blockRange, listType, attributes);
2440
+ joinListBackwards(tr, listType);
2441
+ joinListForwards(tr, listType);
2442
+ dispatch(tr.scrollIntoView());
2443
+ return true;
2444
+ }
2445
+ }
2414
2446
  if (allInTargetList) {
2415
2447
  const first = contentBlocks[0];
2416
2448
  const last = contentBlocks[contentBlocks.length - 1];
@@ -3497,7 +3529,7 @@ var Editor = class extends EventEmitter {
3497
3529
  * Checks if the editor is editable
3498
3530
  */
3499
3531
  get isEditable() {
3500
- return this.view.editable;
3532
+ return this.view ? this.view.editable : this.options.editable ?? true;
3501
3533
  }
3502
3534
  /**
3503
3535
  * Checks if the editor content is empty
@@ -3994,10 +4026,11 @@ var Editor = class extends EventEmitter {
3994
4026
  function positionFloating(reference, floating, options) {
3995
4027
  const placementOpt = options?.placement ?? "bottom";
3996
4028
  const paddingOpt = options?.padding ?? 10;
4029
+ const overflowOpts = options?.boundary ? { padding: paddingOpt, boundary: options.boundary } : { padding: paddingOpt };
3997
4030
  const middleware = [
3998
4031
  dom.offset(options?.offsetValue ?? 4),
3999
- dom.flip({ padding: paddingOpt }),
4000
- dom.shift({ padding: paddingOpt }),
4032
+ dom.flip(overflowOpts),
4033
+ dom.shift(overflowOpts),
4001
4034
  dom.hide()
4002
4035
  ];
4003
4036
  const update = () => {
@@ -4028,10 +4061,11 @@ function positionFloating(reference, floating, options) {
4028
4061
  function positionFloatingOnce(reference, floating, options) {
4029
4062
  const placementOpt = options?.placement ?? "bottom";
4030
4063
  const paddingOpt = options?.padding ?? 10;
4064
+ const overflowOpts = options?.boundary ? { padding: paddingOpt, boundary: options.boundary } : { padding: paddingOpt };
4031
4065
  const middleware = [
4032
4066
  dom.offset(options?.offsetValue ?? 4),
4033
- dom.flip({ padding: paddingOpt }),
4034
- dom.shift({ padding: paddingOpt })
4067
+ dom.flip(overflowOpts),
4068
+ dom.shift(overflowOpts)
4035
4069
  ];
4036
4070
  const update = () => {
4037
4071
  void dom.computePosition(
@@ -4130,12 +4164,12 @@ function defaultBubbleContexts(editor) {
4130
4164
 
4131
4165
  // src/utils/insertAsListItemChild.ts
4132
4166
  var LIST_ITEM_TYPES3 = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
4133
- var LIST_WRAPPER_TYPES = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
4167
+ var LIST_WRAPPER_TYPES2 = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
4134
4168
  function insertAsListItemChild(args) {
4135
4169
  const { tr, wrapperPos, targetItemPos, blockNode, sourceRange, childIndex } = args;
4136
4170
  if (wrapperPos < 0 || wrapperPos >= tr.doc.content.size) return { ok: false };
4137
4171
  const wrapper = tr.doc.nodeAt(wrapperPos);
4138
- if (!wrapper || !LIST_WRAPPER_TYPES.has(wrapper.type.name)) return { ok: false };
4172
+ if (!wrapper || !LIST_WRAPPER_TYPES2.has(wrapper.type.name)) return { ok: false };
4139
4173
  if (wrapper.childCount === 0) return { ok: false };
4140
4174
  let targetItem;
4141
4175
  let targetItemStart;
@@ -5123,6 +5157,226 @@ var FloatingMenuController = class _FloatingMenuController {
5123
5157
  if (changed) this.onChange();
5124
5158
  }
5125
5159
  };
5160
+ var floatingMenuPluginKey = new state.PluginKey("floatingMenu");
5161
+ function defaultFloatingMenuShouldShow({
5162
+ editor,
5163
+ state
5164
+ }) {
5165
+ if (!editor.isEditable) return false;
5166
+ const { selection } = state;
5167
+ const { $from, empty } = selection;
5168
+ if (!empty) return false;
5169
+ if ($from.parent.type.name !== "paragraph") return false;
5170
+ if ($from.parent.content.size !== 0) return false;
5171
+ if ($from.parentOffset !== 0) return false;
5172
+ return true;
5173
+ }
5174
+ var DEFAULT_ENTER_MENU_SHORTCUTS = ["Alt-F10", "Mod-/"];
5175
+ var FLOATING_MENU_META = "dm:floatingMenuTrigger";
5176
+ function showFloatingMenu(view) {
5177
+ view.dispatch(view.state.tr.setMeta(FLOATING_MENU_META, "show"));
5178
+ }
5179
+ function hideFloatingMenu(view) {
5180
+ view.dispatch(view.state.tr.setMeta(FLOATING_MENU_META, "hide"));
5181
+ }
5182
+ var IS_MAC = typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/i.test(navigator.userAgent);
5183
+ function matchShortcut(event, shortcut) {
5184
+ const parts = shortcut.split("-");
5185
+ let keyPart = parts.pop();
5186
+ if (!keyPart) return false;
5187
+ if (keyPart === "Space") keyPart = " ";
5188
+ let needCtrl = false;
5189
+ let needMeta = false;
5190
+ let needAlt = false;
5191
+ let needShift = false;
5192
+ for (const mod of parts) {
5193
+ switch (mod) {
5194
+ case "Mod":
5195
+ case "mod":
5196
+ if (IS_MAC) needMeta = true;
5197
+ else needCtrl = true;
5198
+ break;
5199
+ case "Cmd":
5200
+ case "cmd":
5201
+ case "Meta":
5202
+ case "meta":
5203
+ case "m":
5204
+ needMeta = true;
5205
+ break;
5206
+ case "Ctrl":
5207
+ case "ctrl":
5208
+ case "Control":
5209
+ case "control":
5210
+ case "c":
5211
+ needCtrl = true;
5212
+ break;
5213
+ case "Alt":
5214
+ case "alt":
5215
+ case "a":
5216
+ needAlt = true;
5217
+ break;
5218
+ case "Shift":
5219
+ case "shift":
5220
+ case "s":
5221
+ needShift = true;
5222
+ break;
5223
+ default:
5224
+ return false;
5225
+ }
5226
+ }
5227
+ if (event.altKey !== needAlt) return false;
5228
+ if (event.ctrlKey !== needCtrl) return false;
5229
+ if (event.metaKey !== needMeta) return false;
5230
+ if (keyPart.length > 1 && event.shiftKey !== needShift) return false;
5231
+ return event.key === keyPart || event.key.toLowerCase() === keyPart.toLowerCase();
5232
+ }
5233
+ function createFloatingMenuPlugin(options) {
5234
+ const {
5235
+ pluginKey,
5236
+ editor,
5237
+ element,
5238
+ shouldShow = defaultFloatingMenuShouldShow,
5239
+ offset: offset2 = 0,
5240
+ keymap: keymap4,
5241
+ requireExplicitTrigger = false
5242
+ } = options;
5243
+ const enterShortcuts = keymap4?.enterMenu ?? DEFAULT_ENTER_MENU_SHORTCUTS;
5244
+ if (!element.getAttribute("role")) {
5245
+ element.setAttribute("role", "menu");
5246
+ element.setAttribute("aria-label", "Insert block");
5247
+ }
5248
+ let cleanupFloating = null;
5249
+ let editorEl = null;
5250
+ let clickOutsideHandler = null;
5251
+ let dismissOverlayHandler = null;
5252
+ const isVisible = () => element.hasAttribute("data-show");
5253
+ const updatePosition = (view) => {
5254
+ const { selection } = view.state;
5255
+ const { $from } = selection;
5256
+ const depth = $from.depth;
5257
+ const startPos = $from.start(depth);
5258
+ const domNode = view.nodeDOM(startPos - 1);
5259
+ if (domNode instanceof HTMLElement) {
5260
+ cleanupFloating?.();
5261
+ cleanupFloating = positionFloatingOnce(domNode, element, {
5262
+ placement: "bottom-start",
5263
+ offsetValue: offset2
5264
+ });
5265
+ element.setAttribute("data-show", "");
5266
+ }
5267
+ };
5268
+ const hideMenu = () => {
5269
+ cleanupFloating?.();
5270
+ cleanupFloating = null;
5271
+ element.removeAttribute("data-show");
5272
+ };
5273
+ const clearTriggeredFlag = (view) => {
5274
+ if (!requireExplicitTrigger) return;
5275
+ const triggered = pluginKey.getState(view.state)?.triggered ?? false;
5276
+ if (triggered) view.dispatch(view.state.tr.setMeta(FLOATING_MENU_META, "hide"));
5277
+ };
5278
+ const focusFirstItem = () => {
5279
+ const first = element.querySelector('[data-floating-menu-item]:not([aria-disabled="true"])') ?? element.querySelector('button, [tabindex]:not([tabindex="-1"])');
5280
+ if (!first) return false;
5281
+ first.focus();
5282
+ return true;
5283
+ };
5284
+ hideMenu();
5285
+ const isVisibleNow = (view) => {
5286
+ const wantsShow = shouldShow({ editor, view, state: view.state });
5287
+ if (!requireExplicitTrigger) return wantsShow;
5288
+ const triggered = pluginKey.getState(view.state)?.triggered ?? false;
5289
+ return triggered && wantsShow;
5290
+ };
5291
+ return new state.Plugin({
5292
+ key: pluginKey,
5293
+ state: {
5294
+ init: () => ({ triggered: false }),
5295
+ apply: (tr, prev) => {
5296
+ const meta = tr.getMeta(FLOATING_MENU_META);
5297
+ if (meta === "show") return { triggered: true };
5298
+ if (meta === "hide") return { triggered: false };
5299
+ return prev;
5300
+ }
5301
+ },
5302
+ props: {
5303
+ // Keyboard entry. handleKeyDown fires before browser defaults; we only
5304
+ // act while the menu is visible.
5305
+ handleKeyDown(_view, event) {
5306
+ if (!isVisible()) return false;
5307
+ for (const shortcut of enterShortcuts) {
5308
+ if (matchShortcut(event, shortcut)) {
5309
+ if (focusFirstItem()) {
5310
+ event.preventDefault();
5311
+ return true;
5312
+ }
5313
+ return false;
5314
+ }
5315
+ }
5316
+ return false;
5317
+ }
5318
+ },
5319
+ view: (editorView) => {
5320
+ editorEl = editorView.dom.closest(".dm-editor");
5321
+ if (editorEl && element.parentElement !== editorEl) {
5322
+ editorEl.appendChild(element);
5323
+ }
5324
+ const dismiss = () => {
5325
+ hideMenu();
5326
+ clearTriggeredFlag(editor.view);
5327
+ };
5328
+ const onFocus = () => {
5329
+ if (isVisibleNow(editor.view)) updatePosition(editor.view);
5330
+ else dismiss();
5331
+ };
5332
+ const onBlur = ({ event }) => {
5333
+ const related = event.relatedTarget;
5334
+ if (related instanceof Node && element.contains(related)) return;
5335
+ dismiss();
5336
+ };
5337
+ clickOutsideHandler = (e) => {
5338
+ if (!isVisible()) return;
5339
+ const target = e.target;
5340
+ if (!(target instanceof Node)) return;
5341
+ if (element.contains(target)) return;
5342
+ if (editor.view.dom.contains(target)) return;
5343
+ dismiss();
5344
+ };
5345
+ document.addEventListener("mousedown", clickOutsideHandler, true);
5346
+ if (editorEl) {
5347
+ dismissOverlayHandler = () => {
5348
+ dismiss();
5349
+ };
5350
+ editorEl.addEventListener("dm:dismiss-overlays", dismissOverlayHandler);
5351
+ }
5352
+ editor.on("focus", onFocus);
5353
+ editor.on("blur", onBlur);
5354
+ return {
5355
+ update: (view) => {
5356
+ if (isVisibleNow(view)) updatePosition(view);
5357
+ else {
5358
+ hideMenu();
5359
+ clearTriggeredFlag(view);
5360
+ }
5361
+ },
5362
+ destroy: () => {
5363
+ hideMenu();
5364
+ editor.off("focus", onFocus);
5365
+ editor.off("blur", onBlur);
5366
+ if (clickOutsideHandler) {
5367
+ document.removeEventListener("mousedown", clickOutsideHandler, true);
5368
+ clickOutsideHandler = null;
5369
+ }
5370
+ if (dismissOverlayHandler && editorEl) {
5371
+ editorEl.removeEventListener("dm:dismiss-overlays", dismissOverlayHandler);
5372
+ dismissOverlayHandler = null;
5373
+ }
5374
+ editorEl = null;
5375
+ }
5376
+ };
5377
+ }
5378
+ });
5379
+ }
5126
5380
 
5127
5381
  // src/icons/phosphor.ts
5128
5382
  var defaultIcons = {
@@ -5696,6 +5950,50 @@ var CodeBlock = Node2.create({
5696
5950
  ];
5697
5951
  }
5698
5952
  });
5953
+ var LIST_ITEM_TYPES4 = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
5954
+ function liftCrossTypeListItem(state$1, dispatch) {
5955
+ const { $from } = state$1.selection;
5956
+ if (!state$1.selection.empty) return false;
5957
+ let itemDepth = -1;
5958
+ for (let d = $from.depth; d > 0; d--) {
5959
+ if (LIST_ITEM_TYPES4.has($from.node(d).type.name)) {
5960
+ itemDepth = d;
5961
+ break;
5962
+ }
5963
+ }
5964
+ if (itemDepth < 4) return false;
5965
+ const item = $from.node(itemDepth);
5966
+ const wrapper = $from.node(itemDepth - 1);
5967
+ const parentItem = $from.node(itemDepth - 2);
5968
+ if (!LIST_ITEM_TYPES4.has(parentItem.type.name)) return false;
5969
+ if (parentItem.type === item.type) return false;
5970
+ const parentWrapper = $from.node(itemDepth - 3);
5971
+ const grandParent = $from.node(itemDepth - 4);
5972
+ const gpIndex = $from.index(itemDepth - 4);
5973
+ if (!grandParent.canReplaceWith(gpIndex + 1, gpIndex + 1, wrapper.type)) return false;
5974
+ if (!dispatch) return true;
5975
+ const onlyChild = wrapper.childCount === 1;
5976
+ const removeFrom = onlyChild ? $from.before(itemDepth - 1) : $from.before(itemDepth);
5977
+ const removeTo = onlyChild ? $from.after(itemDepth - 1) : $from.after(itemDepth);
5978
+ const parentIsLast = $from.index(itemDepth - 3) === parentWrapper.childCount - 1;
5979
+ const tr = state$1.tr;
5980
+ tr.delete(removeFrom, removeTo);
5981
+ const newList = wrapper.type.create(wrapper.attrs, item);
5982
+ if (parentIsLast) {
5983
+ const insertAt = tr.mapping.map($from.after(itemDepth - 3));
5984
+ tr.insert(insertAt, newList);
5985
+ tr.setSelection(state.Selection.near(tr.doc.resolve(insertAt + 2)));
5986
+ } else {
5987
+ const splitAt = tr.mapping.map($from.after(itemDepth - 2));
5988
+ tr.split(splitAt, 1);
5989
+ tr.insert(splitAt + 1, newList);
5990
+ tr.setSelection(state.Selection.near(tr.doc.resolve(splitAt + 2)));
5991
+ }
5992
+ dispatch(tr.scrollIntoView());
5993
+ return true;
5994
+ }
5995
+
5996
+ // src/extensions/ListKeymap.ts
5699
5997
  var LIST_GROUP_TYPES = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
5700
5998
  function getListItemContext(editor, listItemName) {
5701
5999
  const { state, view } = editor;
@@ -5737,6 +6035,7 @@ var ListKeymap = Extension.create({
5737
6035
  if (!this.editor) return false;
5738
6036
  const ctx = getListItemContext(this.editor, this.options.listItem);
5739
6037
  if (!ctx) return false;
6038
+ if (liftCrossTypeListItem(ctx.state, ctx.view.dispatch)) return true;
5740
6039
  return schemaList.liftListItem(ctx.listItemType)(ctx.state, ctx.view.dispatch);
5741
6040
  },
5742
6041
  // Backspace at start of list item to lift
@@ -5790,6 +6089,7 @@ var ListKeymap = Extension.create({
5790
6089
  if (firstChild?.isTextblock) {
5791
6090
  const posInListItem = $from.pos - $from.start(listItemDepth);
5792
6091
  if (posInListItem <= 1) {
6092
+ if (liftCrossTypeListItem(state$1, view.dispatch)) return true;
5793
6093
  return schemaList.liftListItem(listItemType)(state$1, view.dispatch);
5794
6094
  }
5795
6095
  }
@@ -5836,7 +6136,22 @@ var ListItem = Node2.create({
5836
6136
  if (commands.splitBlock(state$1, view.dispatch)) return true;
5837
6137
  }
5838
6138
  }
5839
- if (schemaList.splitListItem(this.nodeType)(state$1, view.dispatch)) return true;
6139
+ const item = $from.node(-1);
6140
+ const hasChildren = item.childCount > 1;
6141
+ const atEnd = $from.parentOffset === $from.parent.content.size;
6142
+ const labelEmpty = $from.parent.content.size === 0;
6143
+ if (!ctx?.isInChildrenZone && hasChildren && atEnd && !labelEmpty) {
6144
+ const newItem = this.nodeType.createAndFill();
6145
+ if (newItem) {
6146
+ const insertAt = $from.after($from.depth - 1);
6147
+ const tr = state$1.tr.insert(insertAt, newItem);
6148
+ tr.setSelection(state.Selection.near(tr.doc.resolve(insertAt + 2)));
6149
+ view.dispatch(tr.scrollIntoView());
6150
+ return true;
6151
+ }
6152
+ }
6153
+ const skipSplit = !ctx?.isInChildrenZone && labelEmpty && hasChildren;
6154
+ if (!skipSplit && schemaList.splitListItem(this.nodeType)(state$1, view.dispatch)) return true;
5840
6155
  const listDepth = $from.depth - 2;
5841
6156
  const taskItemType = state$1.schema.nodes["taskItem"];
5842
6157
  if ($from.parent.content.size === 0 && listDepth > 0 && taskItemType && $from.node(listDepth - 1).type === taskItemType) {
@@ -5845,9 +6160,9 @@ var ListItem = Node2.create({
5845
6160
  tr.delete($from.before(delDepth), $from.after(delDepth));
5846
6161
  const taskItemDepth = listDepth - 1;
5847
6162
  const end = tr.mapping.map($from.after(taskItemDepth));
5848
- const item = taskItemType.createAndFill();
5849
- if (item) {
5850
- tr.insert(end, item);
6163
+ const item2 = taskItemType.createAndFill();
6164
+ if (item2) {
6165
+ tr.insert(end, item2);
5851
6166
  tr.setSelection(state.Selection.near(tr.doc.resolve(end + 2)));
5852
6167
  view.dispatch(tr.scrollIntoView());
5853
6168
  return true;
@@ -5893,6 +6208,11 @@ var BulletList = Node2.create({
5893
6208
  return {
5894
6209
  toggleBulletList: () => ({ commands }) => {
5895
6210
  return commands.toggleList(name, options.itemTypeName);
6211
+ },
6212
+ // Notion "turn into" (slash menu / block menu): convert only the cursor's
6213
+ // item and split the run, instead of retyping the whole list.
6214
+ turnIntoBulletList: () => ({ commands }) => {
6215
+ return commands.toggleList(name, options.itemTypeName, void 0, { perItem: true });
5896
6216
  }
5897
6217
  };
5898
6218
  },
@@ -5922,7 +6242,7 @@ var BulletList = Node2.create({
5922
6242
  priority: 200,
5923
6243
  keywords: ["bullet", "list", "unordered", "ul"],
5924
6244
  shortcut: "- ",
5925
- command: "toggleBulletList",
6245
+ command: "turnIntoBulletList",
5926
6246
  // Don't offer "Bulleted list" while cursor is already inside one,
5927
6247
  // otherwise picking it lifts the user out of the list.
5928
6248
  hideWhenInside: ["bulletList"]
@@ -5971,16 +6291,20 @@ var OrderedList = Node2.create({
5971
6291
  return {
5972
6292
  start: {
5973
6293
  default: 1,
6294
+ // Clamp to a finite integer >= 1. Without this, a malformed
6295
+ // `start="abc"` parses to NaN; `JSON.stringify(NaN)` is `null`, so
6296
+ // getJSON() serializes `start: null` and a save/load cycle
6297
+ // permanently dirties the document (reloads as `<ol start="null">`).
5974
6298
  parseHTML: (element) => {
5975
- const start = element.getAttribute("start");
5976
- return start ? parseInt(start, 10) : 1;
6299
+ const n = parseInt(element.getAttribute("start") ?? "", 10);
6300
+ return Number.isFinite(n) && n >= 1 ? Math.floor(n) : 1;
5977
6301
  },
5978
6302
  renderHTML: (attributes) => {
5979
6303
  const start = attributes["start"];
5980
- if (start === 1) {
6304
+ if (!Number.isFinite(start) || start <= 1) {
5981
6305
  return {};
5982
6306
  }
5983
- return { start: String(start) };
6307
+ return { start: String(Math.floor(start)) };
5984
6308
  }
5985
6309
  }
5986
6310
  };
@@ -5996,6 +6320,11 @@ var OrderedList = Node2.create({
5996
6320
  return {
5997
6321
  toggleOrderedList: () => ({ commands }) => {
5998
6322
  return commands.toggleList(name, options.itemTypeName);
6323
+ },
6324
+ // Notion "turn into" (slash menu / block menu): convert only the cursor's
6325
+ // item and split the run, instead of retyping the whole list.
6326
+ turnIntoOrderedList: () => ({ commands }) => {
6327
+ return commands.toggleList(name, options.itemTypeName, void 0, { perItem: true });
5999
6328
  }
6000
6329
  };
6001
6330
  },
@@ -6025,7 +6354,7 @@ var OrderedList = Node2.create({
6025
6354
  priority: 190,
6026
6355
  keywords: ["ordered", "numbered", "list", "ol", "1."],
6027
6356
  shortcut: "1. ",
6028
- command: "toggleOrderedList",
6357
+ command: "turnIntoOrderedList",
6029
6358
  hideWhenInside: ["orderedList"]
6030
6359
  }
6031
6360
  ];
@@ -6054,8 +6383,8 @@ var OrderedList = Node2.create({
6054
6383
  guard: notInsideList,
6055
6384
  joinForward: true,
6056
6385
  getAttributes: (match) => {
6057
- const num = match[1];
6058
- return { start: num ? parseInt(num, 10) : 1 };
6386
+ const n = parseInt(match[1] ?? "", 10);
6387
+ return { start: Number.isFinite(n) && n >= 1 ? Math.floor(n) : 1 };
6059
6388
  }
6060
6389
  })
6061
6390
  ];
@@ -6327,7 +6656,10 @@ var TaskItem = Node2.create({
6327
6656
  keepOnSplit: false,
6328
6657
  parseHTML: (element) => {
6329
6658
  const dataChecked = element.getAttribute("data-checked");
6330
- return dataChecked === "true" || dataChecked === "";
6659
+ if (dataChecked !== null) {
6660
+ return dataChecked.toLowerCase() === "true" || dataChecked === "";
6661
+ }
6662
+ return element.querySelector('input[type="checkbox"]')?.hasAttribute("checked") ?? false;
6331
6663
  },
6332
6664
  renderHTML: (attributes) => ({
6333
6665
  "data-checked": attributes["checked"] ? "true" : "false"
@@ -6340,6 +6672,14 @@ var TaskItem = Node2.create({
6340
6672
  {
6341
6673
  tag: `li[data-type="${this.name}"]`,
6342
6674
  priority: 51
6675
+ },
6676
+ // GFM / markdown task lists: `<li class="task-list-item">` carries no
6677
+ // data-type. Priority 51 keeps it ahead of the generic `li` (listItem,
6678
+ // 50); class-scoped so it never swallows ordinary bullet items. The
6679
+ // `checked` attribute is derived from the descendant `<input>` above.
6680
+ {
6681
+ tag: "li.task-list-item",
6682
+ priority: 51
6343
6683
  }
6344
6684
  ];
6345
6685
  },
@@ -6414,7 +6754,33 @@ var TaskItem = Node2.create({
6414
6754
  if (commands.splitBlock(state$1, view.dispatch)) return true;
6415
6755
  }
6416
6756
  }
6417
- if (schemaList.splitListItem(this.nodeType, { checked: false })(state$1, view.dispatch)) return true;
6757
+ if (!ctx?.isInChildrenZone && $from.node(-1).attrs["checked"] && $from.parentOffset > 0 && $from.parentOffset < $from.parent.content.size) {
6758
+ const tr = state$1.tr;
6759
+ const types = [
6760
+ { type: this.nodeType, attrs: { ...$from.node(-1).attrs, checked: false } },
6761
+ { type: $from.parent.type }
6762
+ ];
6763
+ if (transform.canSplit(tr.doc, $from.pos, 2, types)) {
6764
+ view.dispatch(tr.split($from.pos, 2, types).scrollIntoView());
6765
+ return true;
6766
+ }
6767
+ }
6768
+ const item = $from.node(-1);
6769
+ const hasChildren = item.childCount > 1;
6770
+ const labelEmpty = $from.parent.content.size === 0;
6771
+ const atEnd = $from.parentOffset === $from.parent.content.size;
6772
+ if (!ctx?.isInChildrenZone && hasChildren && atEnd && !labelEmpty) {
6773
+ const newItem = this.nodeType.createAndFill({ checked: false });
6774
+ if (newItem) {
6775
+ const insertAt = $from.after($from.depth - 1);
6776
+ const tr = state$1.tr.insert(insertAt, newItem);
6777
+ tr.setSelection(state.Selection.near(tr.doc.resolve(insertAt + 2)));
6778
+ view.dispatch(tr.scrollIntoView());
6779
+ return true;
6780
+ }
6781
+ }
6782
+ const skipSplit = !ctx?.isInChildrenZone && labelEmpty && hasChildren;
6783
+ if (!skipSplit && schemaList.splitListItem(this.nodeType, { checked: false })(state$1, view.dispatch)) return true;
6418
6784
  if ($from.parent.content.size === 0) {
6419
6785
  const listItemType = state$1.schema.nodes["listItem"];
6420
6786
  if (listItemType) {
@@ -6461,9 +6827,11 @@ var TaskItem = Node2.create({
6461
6827
  },
6462
6828
  "Shift-Tab": () => {
6463
6829
  if (!this.editor || !this.nodeType) return false;
6464
- const { $from } = this.editor.state.selection;
6830
+ const { state, view } = this.editor;
6831
+ const { $from } = state.selection;
6465
6832
  if ($from.depth < 1 || $from.node(-1).type !== this.nodeType) return false;
6466
- return schemaList.liftListItem(this.nodeType)(this.editor.state, this.editor.view.dispatch);
6833
+ if (liftCrossTypeListItem(state, view.dispatch)) return true;
6834
+ return schemaList.liftListItem(this.nodeType)(state, view.dispatch);
6467
6835
  },
6468
6836
  Backspace: () => {
6469
6837
  if (!this.editor || !this.nodeType) return false;
@@ -6485,6 +6853,7 @@ var TaskItem = Node2.create({
6485
6853
  }
6486
6854
  if (taskItemDepth === -1) return false;
6487
6855
  if ($from.index(taskItemDepth) !== 0) return false;
6856
+ if (liftCrossTypeListItem(state, view.dispatch)) return true;
6488
6857
  return schemaList.liftListItem(this.nodeType)(state, view.dispatch);
6489
6858
  },
6490
6859
  "Mod-Enter": () => {
@@ -6511,6 +6880,13 @@ var TaskList = Node2.create({
6511
6880
  tag: `ul[data-type="${this.name}"]`,
6512
6881
  priority: 51
6513
6882
  // Higher priority than regular bulletList
6883
+ },
6884
+ // GFM / markdown task lists: `<ul class="contains-task-list">`. Priority
6885
+ // 51 keeps it ahead of the generic `ul` (bulletList, 50); class-scoped so
6886
+ // it only matches real GFM task-list containers, not ordinary bullets.
6887
+ {
6888
+ tag: "ul.contains-task-list",
6889
+ priority: 51
6514
6890
  }
6515
6891
  ];
6516
6892
  },
@@ -6530,6 +6906,11 @@ var TaskList = Node2.create({
6530
6906
  return {
6531
6907
  toggleTaskList: () => ({ commands }) => {
6532
6908
  return commands.toggleList(name, options.itemTypeName);
6909
+ },
6910
+ // Notion "turn into" (slash menu / block menu): convert only the cursor's
6911
+ // item and split the run, instead of retyping the whole list.
6912
+ turnIntoTaskList: () => ({ commands }) => {
6913
+ return commands.toggleList(name, options.itemTypeName, void 0, { perItem: true });
6533
6914
  }
6534
6915
  };
6535
6916
  },
@@ -6567,7 +6948,7 @@ var TaskList = Node2.create({
6567
6948
  priority: 180,
6568
6949
  keywords: ["todo", "task", "checkbox", "check"],
6569
6950
  shortcut: "[ ] ",
6570
- command: "toggleTaskList",
6951
+ command: "turnIntoTaskList",
6571
6952
  hideWhenInside: ["taskList"]
6572
6953
  }
6573
6954
  ];
@@ -7733,9 +8114,8 @@ var Placeholder = Extension.create({
7733
8114
  props: {
7734
8115
  decorations: ({ doc, selection }) => {
7735
8116
  const editor = this.editor;
7736
- if (!editor?.view) return view.DecorationSet.empty;
7737
- const isEditable = editor.view.editable;
7738
- if (!isEditable && this.options.showOnlyWhenEditable) {
8117
+ if (!editor) return view.DecorationSet.empty;
8118
+ if (!editor.isEditable && this.options.showOnlyWhenEditable) {
7739
8119
  return view.DecorationSet.empty;
7740
8120
  }
7741
8121
  const isDocEmpty = doc.childCount === 1 && doc.firstChild?.isTextblock && doc.firstChild.content.size === 0;
@@ -7767,12 +8147,12 @@ var Placeholder = Extension.create({
7767
8147
  ];
7768
8148
  }
7769
8149
  });
7770
- var LIST_ITEM_TYPES4 = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
7771
- var LIST_WRAPPER_TYPES2 = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
8150
+ var LIST_ITEM_TYPES5 = /* @__PURE__ */ new Set(["listItem", "taskItem"]);
8151
+ var LIST_WRAPPER_TYPES3 = /* @__PURE__ */ new Set(["bulletList", "orderedList", "taskList"]);
7772
8152
  function isCursorInsideListItem(state) {
7773
8153
  const { $from } = state.selection;
7774
8154
  for (let d = $from.depth; d > 0; d--) {
7775
- if (LIST_ITEM_TYPES4.has($from.node(d).type.name)) return true;
8155
+ if (LIST_ITEM_TYPES5.has($from.node(d).type.name)) return true;
7776
8156
  }
7777
8157
  return false;
7778
8158
  }
@@ -7803,7 +8183,7 @@ function indentBlockAsListChild(state$1, dispatch) {
7803
8183
  }
7804
8184
  if (blockIndex === 0) return false;
7805
8185
  const prevSibling = state$1.doc.child(blockIndex - 1);
7806
- if (!LIST_WRAPPER_TYPES2.has(prevSibling.type.name)) return false;
8186
+ if (!LIST_WRAPPER_TYPES3.has(prevSibling.type.name)) return false;
7807
8187
  let wrapperPos = 0;
7808
8188
  for (let i = 0; i < blockIndex - 1; i++) {
7809
8189
  wrapperPos += state$1.doc.child(i).nodeSize;
@@ -7827,7 +8207,7 @@ function outdentBlockFromListItem(state$1, dispatch) {
7827
8207
  const { $from } = selection;
7828
8208
  let listItemDepth = -1;
7829
8209
  for (let d = $from.depth; d > 0; d--) {
7830
- if (LIST_ITEM_TYPES4.has($from.node(d).type.name)) {
8210
+ if (LIST_ITEM_TYPES5.has($from.node(d).type.name)) {
7831
8211
  listItemDepth = d;
7832
8212
  break;
7833
8213
  }
@@ -7843,7 +8223,7 @@ function outdentBlockFromListItem(state$1, dispatch) {
7843
8223
  if (wrapperDepth < 0) return false;
7844
8224
  const liIndexInWrapper = $from.index(wrapperDepth);
7845
8225
  const wrapper = $from.node(wrapperDepth);
7846
- if (liIndexInWrapper !== wrapper.childCount - 1) return false;
8226
+ const isLastItem = liIndexInWrapper === wrapper.childCount - 1;
7847
8227
  if (wrapperDepth - 1 < 0) return false;
7848
8228
  const wrapperParent = $from.node(wrapperDepth - 1);
7849
8229
  const wrapperIndexInParent = $from.index(wrapperDepth - 1);
@@ -7858,13 +8238,19 @@ function outdentBlockFromListItem(state$1, dispatch) {
7858
8238
  if (!dispatch) return true;
7859
8239
  const blockStart = $from.before(blockDepth);
7860
8240
  const blockEnd = $from.after(blockDepth);
7861
- const wrapperEnd = $from.after(wrapperDepth);
7862
- const tr = state$1.tr;
7863
8241
  const blockSize = blockEnd - blockStart;
8242
+ const tr = state$1.tr;
7864
8243
  tr.delete(blockStart, blockEnd);
7865
- const insertAt = wrapperEnd - blockSize;
7866
- tr.insert(insertAt, blockNode);
7867
- tr.setSelection(state.Selection.near(tr.doc.resolve(insertAt + 1)));
8244
+ if (isLastItem) {
8245
+ const insertAt = $from.after(wrapperDepth) - blockSize;
8246
+ tr.insert(insertAt, blockNode);
8247
+ tr.setSelection(state.Selection.near(tr.doc.resolve(insertAt + 1)));
8248
+ } else {
8249
+ const splitPos = $from.after(listItemDepth) - blockSize;
8250
+ tr.split(splitPos, 1);
8251
+ tr.insert(splitPos + 1, blockNode);
8252
+ tr.setSelection(state.Selection.near(tr.doc.resolve(splitPos + 2)));
8253
+ }
7868
8254
  dispatch(tr.scrollIntoView());
7869
8255
  return true;
7870
8256
  }
@@ -8601,7 +8987,7 @@ var BlockColor = Extension.create({
8601
8987
  };
8602
8988
  }
8603
8989
  });
8604
- var Selection4 = Extension.create({
8990
+ var Selection5 = Extension.create({
8605
8991
  name: "selection",
8606
8992
  addStorage() {
8607
8993
  return {
@@ -10128,6 +10514,7 @@ exports.Editor = Editor;
10128
10514
  exports.EventEmitter = EventEmitter;
10129
10515
  exports.Extension = Extension;
10130
10516
  exports.ExtensionManager = ExtensionManager;
10517
+ exports.FLOATING_MENU_META = FLOATING_MENU_META;
10131
10518
  exports.FLOATING_MENU_NO_FOCUS = FLOATING_MENU_NO_FOCUS;
10132
10519
  exports.FloatingMenuController = FloatingMenuController;
10133
10520
  exports.Focus = Focus;
@@ -10154,7 +10541,7 @@ exports.NotionColorPicker = NotionColorPicker;
10154
10541
  exports.OrderedList = OrderedList;
10155
10542
  exports.Paragraph = Paragraph;
10156
10543
  exports.Placeholder = Placeholder;
10157
- exports.Selection = Selection4;
10544
+ exports.Selection = Selection5;
10158
10545
  exports.SelectionDecoration = SelectionDecoration;
10159
10546
  exports.StarterKit = StarterKit;
10160
10547
  exports.Strike = Strike;
@@ -10188,13 +10575,16 @@ exports.createBubbleMenuPlugin = createBubbleMenuPlugin;
10188
10575
  exports.createCanChecker = createCanChecker;
10189
10576
  exports.createChainBuilder = createChainBuilder;
10190
10577
  exports.createDocument = createDocument;
10578
+ exports.createFloatingMenuPlugin = createFloatingMenuPlugin;
10191
10579
  exports.defaultBlockAt = defaultBlockAt;
10192
10580
  exports.defaultBubbleContexts = defaultBubbleContexts;
10581
+ exports.defaultFloatingMenuShouldShow = defaultFloatingMenuShouldShow;
10193
10582
  exports.defaultIcons = defaultIcons;
10194
10583
  exports.deleteSelection = deleteSelection;
10195
10584
  exports.findChildren = findChildren;
10196
10585
  exports.findListItemAncestorDepth = findListItemAncestorDepth;
10197
10586
  exports.findParentNode = findParentNode;
10587
+ exports.floatingMenuPluginKey = floatingMenuPluginKey;
10198
10588
  exports.focus = focus;
10199
10589
  exports.focusPluginKey = focusPluginKey;
10200
10590
  exports.generateHTML = generateHTML;
@@ -10203,6 +10593,7 @@ exports.generateText = generateText;
10203
10593
  exports.getListItemCursorContext = getListItemCursorContext;
10204
10594
  exports.getMarkRange = getMarkRange;
10205
10595
  exports.groupFloatingMenuItems = groupFloatingMenuItems;
10596
+ exports.hideFloatingMenu = hideFloatingMenu;
10206
10597
  exports.indentBlockAsListChild = indentBlockAsListChild;
10207
10598
  exports.inlineStyles = inlineStyles;
10208
10599
  exports.insertAsListItemChild = insertAsListItemChild;
@@ -10238,6 +10629,7 @@ exports.selectionDecorationPluginKey = selectionDecorationPluginKey;
10238
10629
  exports.setBlockType = setBlockType;
10239
10630
  exports.setContent = setContent;
10240
10631
  exports.setMark = setMark;
10632
+ exports.showFloatingMenu = showFloatingMenu;
10241
10633
  exports.splitListForInsert = splitListForInsert;
10242
10634
  exports.stripInlineColorConflicts = stripInlineColorConflicts;
10243
10635
  exports.textInputRule = textInputRule;