@domternal/core 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2279,7 +2279,7 @@ function joinListForwards(tr, listType) {
2279
2279
  }
2280
2280
  }
2281
2281
  }
2282
- var toggleList = (listNodeName, listItemNodeName, attributes) => ({ state: state$1, tr, dispatch }) => {
2282
+ var toggleList = (listNodeName, listItemNodeName, attributes, options) => ({ state: state$1, tr, dispatch }) => {
2283
2283
  const listType = state$1.schema.nodes[listNodeName];
2284
2284
  const listItemType = state$1.schema.nodes[listItemNodeName];
2285
2285
  if (!listType || !listItemType) {
@@ -2411,6 +2411,17 @@ var toggleList = (listNodeName, listItemNodeName, attributes) => ({ state: state
2411
2411
  const contentBlocks = collectListContext(tr.doc, from, to);
2412
2412
  const allInTargetList = contentBlocks.length > 0 && contentBlocks.every((b) => b.inTargetList);
2413
2413
  const allInSomeList = contentBlocks.length > 0 && contentBlocks.every((b) => b.inSomeList);
2414
+ if (options?.perItem && from === to && allInSomeList && !allInTargetList) {
2415
+ if (!dispatch) return true;
2416
+ if (liftCurrentListItem(state$1, tr)) {
2417
+ const blockRange = tr.selection.$from.blockRange();
2418
+ if (blockRange) schemaList.wrapRangeInList(tr, blockRange, listType, attributes);
2419
+ joinListBackwards(tr, listType);
2420
+ joinListForwards(tr, listType);
2421
+ dispatch(tr.scrollIntoView());
2422
+ return true;
2423
+ }
2424
+ }
2414
2425
  if (allInTargetList) {
2415
2426
  const first = contentBlocks[0];
2416
2427
  const last = contentBlocks[contentBlocks.length - 1];
@@ -3497,7 +3508,7 @@ var Editor = class extends EventEmitter {
3497
3508
  * Checks if the editor is editable
3498
3509
  */
3499
3510
  get isEditable() {
3500
- return this.view.editable;
3511
+ return this.view ? this.view.editable : this.options.editable ?? true;
3501
3512
  }
3502
3513
  /**
3503
3514
  * Checks if the editor content is empty
@@ -3994,10 +4005,11 @@ var Editor = class extends EventEmitter {
3994
4005
  function positionFloating(reference, floating, options) {
3995
4006
  const placementOpt = options?.placement ?? "bottom";
3996
4007
  const paddingOpt = options?.padding ?? 10;
4008
+ const overflowOpts = options?.boundary ? { padding: paddingOpt, boundary: options.boundary } : { padding: paddingOpt };
3997
4009
  const middleware = [
3998
4010
  dom.offset(options?.offsetValue ?? 4),
3999
- dom.flip({ padding: paddingOpt }),
4000
- dom.shift({ padding: paddingOpt }),
4011
+ dom.flip(overflowOpts),
4012
+ dom.shift(overflowOpts),
4001
4013
  dom.hide()
4002
4014
  ];
4003
4015
  const update = () => {
@@ -4028,10 +4040,11 @@ function positionFloating(reference, floating, options) {
4028
4040
  function positionFloatingOnce(reference, floating, options) {
4029
4041
  const placementOpt = options?.placement ?? "bottom";
4030
4042
  const paddingOpt = options?.padding ?? 10;
4043
+ const overflowOpts = options?.boundary ? { padding: paddingOpt, boundary: options.boundary } : { padding: paddingOpt };
4031
4044
  const middleware = [
4032
4045
  dom.offset(options?.offsetValue ?? 4),
4033
- dom.flip({ padding: paddingOpt }),
4034
- dom.shift({ padding: paddingOpt })
4046
+ dom.flip(overflowOpts),
4047
+ dom.shift(overflowOpts)
4035
4048
  ];
4036
4049
  const update = () => {
4037
4050
  void dom.computePosition(
@@ -5123,6 +5136,226 @@ var FloatingMenuController = class _FloatingMenuController {
5123
5136
  if (changed) this.onChange();
5124
5137
  }
5125
5138
  };
5139
+ var floatingMenuPluginKey = new state.PluginKey("floatingMenu");
5140
+ function defaultFloatingMenuShouldShow({
5141
+ editor,
5142
+ state
5143
+ }) {
5144
+ if (!editor.isEditable) return false;
5145
+ const { selection } = state;
5146
+ const { $from, empty } = selection;
5147
+ if (!empty) return false;
5148
+ if ($from.parent.type.name !== "paragraph") return false;
5149
+ if ($from.parent.content.size !== 0) return false;
5150
+ if ($from.parentOffset !== 0) return false;
5151
+ return true;
5152
+ }
5153
+ var DEFAULT_ENTER_MENU_SHORTCUTS = ["Alt-F10", "Mod-/"];
5154
+ var FLOATING_MENU_META = "dm:floatingMenuTrigger";
5155
+ function showFloatingMenu(view) {
5156
+ view.dispatch(view.state.tr.setMeta(FLOATING_MENU_META, "show"));
5157
+ }
5158
+ function hideFloatingMenu(view) {
5159
+ view.dispatch(view.state.tr.setMeta(FLOATING_MENU_META, "hide"));
5160
+ }
5161
+ var IS_MAC = typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/i.test(navigator.userAgent);
5162
+ function matchShortcut(event, shortcut) {
5163
+ const parts = shortcut.split("-");
5164
+ let keyPart = parts.pop();
5165
+ if (!keyPart) return false;
5166
+ if (keyPart === "Space") keyPart = " ";
5167
+ let needCtrl = false;
5168
+ let needMeta = false;
5169
+ let needAlt = false;
5170
+ let needShift = false;
5171
+ for (const mod of parts) {
5172
+ switch (mod) {
5173
+ case "Mod":
5174
+ case "mod":
5175
+ if (IS_MAC) needMeta = true;
5176
+ else needCtrl = true;
5177
+ break;
5178
+ case "Cmd":
5179
+ case "cmd":
5180
+ case "Meta":
5181
+ case "meta":
5182
+ case "m":
5183
+ needMeta = true;
5184
+ break;
5185
+ case "Ctrl":
5186
+ case "ctrl":
5187
+ case "Control":
5188
+ case "control":
5189
+ case "c":
5190
+ needCtrl = true;
5191
+ break;
5192
+ case "Alt":
5193
+ case "alt":
5194
+ case "a":
5195
+ needAlt = true;
5196
+ break;
5197
+ case "Shift":
5198
+ case "shift":
5199
+ case "s":
5200
+ needShift = true;
5201
+ break;
5202
+ default:
5203
+ return false;
5204
+ }
5205
+ }
5206
+ if (event.altKey !== needAlt) return false;
5207
+ if (event.ctrlKey !== needCtrl) return false;
5208
+ if (event.metaKey !== needMeta) return false;
5209
+ if (keyPart.length > 1 && event.shiftKey !== needShift) return false;
5210
+ return event.key === keyPart || event.key.toLowerCase() === keyPart.toLowerCase();
5211
+ }
5212
+ function createFloatingMenuPlugin(options) {
5213
+ const {
5214
+ pluginKey,
5215
+ editor,
5216
+ element,
5217
+ shouldShow = defaultFloatingMenuShouldShow,
5218
+ offset: offset2 = 0,
5219
+ keymap: keymap4,
5220
+ requireExplicitTrigger = false
5221
+ } = options;
5222
+ const enterShortcuts = keymap4?.enterMenu ?? DEFAULT_ENTER_MENU_SHORTCUTS;
5223
+ if (!element.getAttribute("role")) {
5224
+ element.setAttribute("role", "menu");
5225
+ element.setAttribute("aria-label", "Insert block");
5226
+ }
5227
+ let cleanupFloating = null;
5228
+ let editorEl = null;
5229
+ let clickOutsideHandler = null;
5230
+ let dismissOverlayHandler = null;
5231
+ const isVisible = () => element.hasAttribute("data-show");
5232
+ const updatePosition = (view) => {
5233
+ const { selection } = view.state;
5234
+ const { $from } = selection;
5235
+ const depth = $from.depth;
5236
+ const startPos = $from.start(depth);
5237
+ const domNode = view.nodeDOM(startPos - 1);
5238
+ if (domNode instanceof HTMLElement) {
5239
+ cleanupFloating?.();
5240
+ cleanupFloating = positionFloatingOnce(domNode, element, {
5241
+ placement: "bottom-start",
5242
+ offsetValue: offset2
5243
+ });
5244
+ element.setAttribute("data-show", "");
5245
+ }
5246
+ };
5247
+ const hideMenu = () => {
5248
+ cleanupFloating?.();
5249
+ cleanupFloating = null;
5250
+ element.removeAttribute("data-show");
5251
+ };
5252
+ const clearTriggeredFlag = (view) => {
5253
+ if (!requireExplicitTrigger) return;
5254
+ const triggered = pluginKey.getState(view.state)?.triggered ?? false;
5255
+ if (triggered) view.dispatch(view.state.tr.setMeta(FLOATING_MENU_META, "hide"));
5256
+ };
5257
+ const focusFirstItem = () => {
5258
+ const first = element.querySelector('[data-floating-menu-item]:not([aria-disabled="true"])') ?? element.querySelector('button, [tabindex]:not([tabindex="-1"])');
5259
+ if (!first) return false;
5260
+ first.focus();
5261
+ return true;
5262
+ };
5263
+ hideMenu();
5264
+ const isVisibleNow = (view) => {
5265
+ const wantsShow = shouldShow({ editor, view, state: view.state });
5266
+ if (!requireExplicitTrigger) return wantsShow;
5267
+ const triggered = pluginKey.getState(view.state)?.triggered ?? false;
5268
+ return triggered && wantsShow;
5269
+ };
5270
+ return new state.Plugin({
5271
+ key: pluginKey,
5272
+ state: {
5273
+ init: () => ({ triggered: false }),
5274
+ apply: (tr, prev) => {
5275
+ const meta = tr.getMeta(FLOATING_MENU_META);
5276
+ if (meta === "show") return { triggered: true };
5277
+ if (meta === "hide") return { triggered: false };
5278
+ return prev;
5279
+ }
5280
+ },
5281
+ props: {
5282
+ // Keyboard entry. handleKeyDown fires before browser defaults; we only
5283
+ // act while the menu is visible.
5284
+ handleKeyDown(_view, event) {
5285
+ if (!isVisible()) return false;
5286
+ for (const shortcut of enterShortcuts) {
5287
+ if (matchShortcut(event, shortcut)) {
5288
+ if (focusFirstItem()) {
5289
+ event.preventDefault();
5290
+ return true;
5291
+ }
5292
+ return false;
5293
+ }
5294
+ }
5295
+ return false;
5296
+ }
5297
+ },
5298
+ view: (editorView) => {
5299
+ editorEl = editorView.dom.closest(".dm-editor");
5300
+ if (editorEl && element.parentElement !== editorEl) {
5301
+ editorEl.appendChild(element);
5302
+ }
5303
+ const dismiss = () => {
5304
+ hideMenu();
5305
+ clearTriggeredFlag(editor.view);
5306
+ };
5307
+ const onFocus = () => {
5308
+ if (isVisibleNow(editor.view)) updatePosition(editor.view);
5309
+ else dismiss();
5310
+ };
5311
+ const onBlur = ({ event }) => {
5312
+ const related = event.relatedTarget;
5313
+ if (related instanceof Node && element.contains(related)) return;
5314
+ dismiss();
5315
+ };
5316
+ clickOutsideHandler = (e) => {
5317
+ if (!isVisible()) return;
5318
+ const target = e.target;
5319
+ if (!(target instanceof Node)) return;
5320
+ if (element.contains(target)) return;
5321
+ if (editor.view.dom.contains(target)) return;
5322
+ dismiss();
5323
+ };
5324
+ document.addEventListener("mousedown", clickOutsideHandler, true);
5325
+ if (editorEl) {
5326
+ dismissOverlayHandler = () => {
5327
+ dismiss();
5328
+ };
5329
+ editorEl.addEventListener("dm:dismiss-overlays", dismissOverlayHandler);
5330
+ }
5331
+ editor.on("focus", onFocus);
5332
+ editor.on("blur", onBlur);
5333
+ return {
5334
+ update: (view) => {
5335
+ if (isVisibleNow(view)) updatePosition(view);
5336
+ else {
5337
+ hideMenu();
5338
+ clearTriggeredFlag(view);
5339
+ }
5340
+ },
5341
+ destroy: () => {
5342
+ hideMenu();
5343
+ editor.off("focus", onFocus);
5344
+ editor.off("blur", onBlur);
5345
+ if (clickOutsideHandler) {
5346
+ document.removeEventListener("mousedown", clickOutsideHandler, true);
5347
+ clickOutsideHandler = null;
5348
+ }
5349
+ if (dismissOverlayHandler && editorEl) {
5350
+ editorEl.removeEventListener("dm:dismiss-overlays", dismissOverlayHandler);
5351
+ dismissOverlayHandler = null;
5352
+ }
5353
+ editorEl = null;
5354
+ }
5355
+ };
5356
+ }
5357
+ });
5358
+ }
5126
5359
 
5127
5360
  // src/icons/phosphor.ts
5128
5361
  var defaultIcons = {
@@ -5893,6 +6126,11 @@ var BulletList = Node2.create({
5893
6126
  return {
5894
6127
  toggleBulletList: () => ({ commands }) => {
5895
6128
  return commands.toggleList(name, options.itemTypeName);
6129
+ },
6130
+ // Notion "turn into" (slash menu / block menu): convert only the cursor's
6131
+ // item and split the run, instead of retyping the whole list.
6132
+ turnIntoBulletList: () => ({ commands }) => {
6133
+ return commands.toggleList(name, options.itemTypeName, void 0, { perItem: true });
5896
6134
  }
5897
6135
  };
5898
6136
  },
@@ -5922,7 +6160,7 @@ var BulletList = Node2.create({
5922
6160
  priority: 200,
5923
6161
  keywords: ["bullet", "list", "unordered", "ul"],
5924
6162
  shortcut: "- ",
5925
- command: "toggleBulletList",
6163
+ command: "turnIntoBulletList",
5926
6164
  // Don't offer "Bulleted list" while cursor is already inside one,
5927
6165
  // otherwise picking it lifts the user out of the list.
5928
6166
  hideWhenInside: ["bulletList"]
@@ -5996,6 +6234,11 @@ var OrderedList = Node2.create({
5996
6234
  return {
5997
6235
  toggleOrderedList: () => ({ commands }) => {
5998
6236
  return commands.toggleList(name, options.itemTypeName);
6237
+ },
6238
+ // Notion "turn into" (slash menu / block menu): convert only the cursor's
6239
+ // item and split the run, instead of retyping the whole list.
6240
+ turnIntoOrderedList: () => ({ commands }) => {
6241
+ return commands.toggleList(name, options.itemTypeName, void 0, { perItem: true });
5999
6242
  }
6000
6243
  };
6001
6244
  },
@@ -6025,7 +6268,7 @@ var OrderedList = Node2.create({
6025
6268
  priority: 190,
6026
6269
  keywords: ["ordered", "numbered", "list", "ol", "1."],
6027
6270
  shortcut: "1. ",
6028
- command: "toggleOrderedList",
6271
+ command: "turnIntoOrderedList",
6029
6272
  hideWhenInside: ["orderedList"]
6030
6273
  }
6031
6274
  ];
@@ -6414,6 +6657,17 @@ var TaskItem = Node2.create({
6414
6657
  if (commands.splitBlock(state$1, view.dispatch)) return true;
6415
6658
  }
6416
6659
  }
6660
+ if (!ctx?.isInChildrenZone && $from.node(-1).attrs["checked"] && $from.parentOffset > 0 && $from.parentOffset < $from.parent.content.size) {
6661
+ const tr = state$1.tr;
6662
+ const types = [
6663
+ { type: this.nodeType, attrs: { ...$from.node(-1).attrs, checked: false } },
6664
+ { type: $from.parent.type }
6665
+ ];
6666
+ if (transform.canSplit(tr.doc, $from.pos, 2, types)) {
6667
+ view.dispatch(tr.split($from.pos, 2, types).scrollIntoView());
6668
+ return true;
6669
+ }
6670
+ }
6417
6671
  if (schemaList.splitListItem(this.nodeType, { checked: false })(state$1, view.dispatch)) return true;
6418
6672
  if ($from.parent.content.size === 0) {
6419
6673
  const listItemType = state$1.schema.nodes["listItem"];
@@ -6530,6 +6784,11 @@ var TaskList = Node2.create({
6530
6784
  return {
6531
6785
  toggleTaskList: () => ({ commands }) => {
6532
6786
  return commands.toggleList(name, options.itemTypeName);
6787
+ },
6788
+ // Notion "turn into" (slash menu / block menu): convert only the cursor's
6789
+ // item and split the run, instead of retyping the whole list.
6790
+ turnIntoTaskList: () => ({ commands }) => {
6791
+ return commands.toggleList(name, options.itemTypeName, void 0, { perItem: true });
6533
6792
  }
6534
6793
  };
6535
6794
  },
@@ -6567,7 +6826,7 @@ var TaskList = Node2.create({
6567
6826
  priority: 180,
6568
6827
  keywords: ["todo", "task", "checkbox", "check"],
6569
6828
  shortcut: "[ ] ",
6570
- command: "toggleTaskList",
6829
+ command: "turnIntoTaskList",
6571
6830
  hideWhenInside: ["taskList"]
6572
6831
  }
6573
6832
  ];
@@ -7733,9 +7992,8 @@ var Placeholder = Extension.create({
7733
7992
  props: {
7734
7993
  decorations: ({ doc, selection }) => {
7735
7994
  const editor = this.editor;
7736
- if (!editor?.view) return view.DecorationSet.empty;
7737
- const isEditable = editor.view.editable;
7738
- if (!isEditable && this.options.showOnlyWhenEditable) {
7995
+ if (!editor) return view.DecorationSet.empty;
7996
+ if (!editor.isEditable && this.options.showOnlyWhenEditable) {
7739
7997
  return view.DecorationSet.empty;
7740
7998
  }
7741
7999
  const isDocEmpty = doc.childCount === 1 && doc.firstChild?.isTextblock && doc.firstChild.content.size === 0;
@@ -7843,7 +8101,7 @@ function outdentBlockFromListItem(state$1, dispatch) {
7843
8101
  if (wrapperDepth < 0) return false;
7844
8102
  const liIndexInWrapper = $from.index(wrapperDepth);
7845
8103
  const wrapper = $from.node(wrapperDepth);
7846
- if (liIndexInWrapper !== wrapper.childCount - 1) return false;
8104
+ const isLastItem = liIndexInWrapper === wrapper.childCount - 1;
7847
8105
  if (wrapperDepth - 1 < 0) return false;
7848
8106
  const wrapperParent = $from.node(wrapperDepth - 1);
7849
8107
  const wrapperIndexInParent = $from.index(wrapperDepth - 1);
@@ -7858,13 +8116,19 @@ function outdentBlockFromListItem(state$1, dispatch) {
7858
8116
  if (!dispatch) return true;
7859
8117
  const blockStart = $from.before(blockDepth);
7860
8118
  const blockEnd = $from.after(blockDepth);
7861
- const wrapperEnd = $from.after(wrapperDepth);
7862
- const tr = state$1.tr;
7863
8119
  const blockSize = blockEnd - blockStart;
8120
+ const tr = state$1.tr;
7864
8121
  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)));
8122
+ if (isLastItem) {
8123
+ const insertAt = $from.after(wrapperDepth) - blockSize;
8124
+ tr.insert(insertAt, blockNode);
8125
+ tr.setSelection(state.Selection.near(tr.doc.resolve(insertAt + 1)));
8126
+ } else {
8127
+ const splitPos = $from.after(listItemDepth) - blockSize;
8128
+ tr.split(splitPos, 1);
8129
+ tr.insert(splitPos + 1, blockNode);
8130
+ tr.setSelection(state.Selection.near(tr.doc.resolve(splitPos + 2)));
8131
+ }
7868
8132
  dispatch(tr.scrollIntoView());
7869
8133
  return true;
7870
8134
  }
@@ -10128,6 +10392,7 @@ exports.Editor = Editor;
10128
10392
  exports.EventEmitter = EventEmitter;
10129
10393
  exports.Extension = Extension;
10130
10394
  exports.ExtensionManager = ExtensionManager;
10395
+ exports.FLOATING_MENU_META = FLOATING_MENU_META;
10131
10396
  exports.FLOATING_MENU_NO_FOCUS = FLOATING_MENU_NO_FOCUS;
10132
10397
  exports.FloatingMenuController = FloatingMenuController;
10133
10398
  exports.Focus = Focus;
@@ -10188,13 +10453,16 @@ exports.createBubbleMenuPlugin = createBubbleMenuPlugin;
10188
10453
  exports.createCanChecker = createCanChecker;
10189
10454
  exports.createChainBuilder = createChainBuilder;
10190
10455
  exports.createDocument = createDocument;
10456
+ exports.createFloatingMenuPlugin = createFloatingMenuPlugin;
10191
10457
  exports.defaultBlockAt = defaultBlockAt;
10192
10458
  exports.defaultBubbleContexts = defaultBubbleContexts;
10459
+ exports.defaultFloatingMenuShouldShow = defaultFloatingMenuShouldShow;
10193
10460
  exports.defaultIcons = defaultIcons;
10194
10461
  exports.deleteSelection = deleteSelection;
10195
10462
  exports.findChildren = findChildren;
10196
10463
  exports.findListItemAncestorDepth = findListItemAncestorDepth;
10197
10464
  exports.findParentNode = findParentNode;
10465
+ exports.floatingMenuPluginKey = floatingMenuPluginKey;
10198
10466
  exports.focus = focus;
10199
10467
  exports.focusPluginKey = focusPluginKey;
10200
10468
  exports.generateHTML = generateHTML;
@@ -10203,6 +10471,7 @@ exports.generateText = generateText;
10203
10471
  exports.getListItemCursorContext = getListItemCursorContext;
10204
10472
  exports.getMarkRange = getMarkRange;
10205
10473
  exports.groupFloatingMenuItems = groupFloatingMenuItems;
10474
+ exports.hideFloatingMenu = hideFloatingMenu;
10206
10475
  exports.indentBlockAsListChild = indentBlockAsListChild;
10207
10476
  exports.inlineStyles = inlineStyles;
10208
10477
  exports.insertAsListItemChild = insertAsListItemChild;
@@ -10238,6 +10507,7 @@ exports.selectionDecorationPluginKey = selectionDecorationPluginKey;
10238
10507
  exports.setBlockType = setBlockType;
10239
10508
  exports.setContent = setContent;
10240
10509
  exports.setMark = setMark;
10510
+ exports.showFloatingMenu = showFloatingMenu;
10241
10511
  exports.splitListForInsert = splitListForInsert;
10242
10512
  exports.stripInlineColorConflicts = stripInlineColorConflicts;
10243
10513
  exports.textInputRule = textInputRule;