@domternal/core 0.12.1 → 0.14.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
@@ -1379,9 +1379,9 @@ var defaultBlockAt = (match) => {
1379
1379
  };
1380
1380
 
1381
1381
  // src/commands/contentCommands.ts
1382
- var setContent = (content, options = {}) => ({ state, tr, dispatch }) => {
1382
+ var setContent = (content, options = {}) => ({ state: state$1, tr, dispatch }) => {
1383
1383
  const { emitUpdate = true, parseOptions } = options;
1384
- const { schema } = state;
1384
+ const { schema } = state$1;
1385
1385
  let doc;
1386
1386
  try {
1387
1387
  doc = createDocument(content, schema, { parseOptions });
@@ -1392,6 +1392,8 @@ var setContent = (content, options = {}) => ({ state, tr, dispatch }) => {
1392
1392
  return true;
1393
1393
  }
1394
1394
  tr.replaceWith(0, tr.doc.content.size, doc.content);
1395
+ const $end = tr.doc.resolve(tr.doc.content.size);
1396
+ tr.setSelection(state.TextSelection.between($end, $end, -1));
1395
1397
  if (!emitUpdate) {
1396
1398
  tr.setMeta("addToHistory", false);
1397
1399
  tr.setMeta("skipUpdate", true);
@@ -4064,16 +4066,50 @@ var Editor = class _Editor extends EventEmitter {
4064
4066
  return super.emit(event, ...args);
4065
4067
  }
4066
4068
  };
4067
- function positionFloating(reference, floating, options) {
4068
- const placementOpt = options?.placement ?? "bottom";
4069
+ var OPPOSITE_SIDE = {
4070
+ top: "bottom",
4071
+ bottom: "top",
4072
+ left: "right",
4073
+ right: "left"
4074
+ };
4075
+ function oppositePlacement(placement) {
4076
+ const [side = "", alignment] = placement.split("-");
4077
+ const opposite = OPPOSITE_SIDE[side] ?? side;
4078
+ return alignment ? `${opposite}-${alignment}` : opposite;
4079
+ }
4080
+ function buildMiddleware(options, placementOpt) {
4069
4081
  const paddingOpt = options?.padding ?? 10;
4070
4082
  const overflowOpts = options?.boundary ? { padding: paddingOpt, boundary: options.boundary } : { padding: paddingOpt };
4071
- const middleware = [
4072
- dom.offset(options?.offsetValue ?? 4),
4073
- dom.flip(overflowOpts),
4074
- dom.shift(overflowOpts),
4075
- dom.hide()
4076
- ];
4083
+ const constrain = options?.constrainHeight;
4084
+ const middleware = [dom.offset(options?.offsetValue ?? 4)];
4085
+ if (constrain) {
4086
+ middleware.push(
4087
+ dom.size({
4088
+ ...overflowOpts,
4089
+ apply({ availableHeight, elements }) {
4090
+ elements.floating.style.setProperty(
4091
+ "--dm-available-height",
4092
+ `${String(Math.max(constrain.minHeight, Math.floor(availableHeight)))}px`
4093
+ );
4094
+ }
4095
+ })
4096
+ );
4097
+ middleware.push(
4098
+ dom.flip({
4099
+ ...overflowOpts,
4100
+ crossAxis: false,
4101
+ fallbackPlacements: [oppositePlacement(placementOpt)]
4102
+ })
4103
+ );
4104
+ } else {
4105
+ middleware.push(dom.flip(overflowOpts));
4106
+ }
4107
+ middleware.push(dom.shift(overflowOpts));
4108
+ return middleware;
4109
+ }
4110
+ function positionFloating(reference, floating, options) {
4111
+ const placementOpt = options?.placement ?? "bottom";
4112
+ const middleware = [...buildMiddleware(options, placementOpt), dom.hide()];
4077
4113
  const update = () => {
4078
4114
  void dom.computePosition(
4079
4115
  reference,
@@ -4101,13 +4137,7 @@ function positionFloating(reference, floating, options) {
4101
4137
  }
4102
4138
  function positionFloatingOnce(reference, floating, options) {
4103
4139
  const placementOpt = options?.placement ?? "bottom";
4104
- const paddingOpt = options?.padding ?? 10;
4105
- const overflowOpts = options?.boundary ? { padding: paddingOpt, boundary: options.boundary } : { padding: paddingOpt };
4106
- const middleware = [
4107
- dom.offset(options?.offsetValue ?? 4),
4108
- dom.flip(overflowOpts),
4109
- dom.shift(overflowOpts)
4110
- ];
4140
+ const middleware = buildMiddleware(options, placementOpt);
4111
4141
  const update = () => {
4112
4142
  void dom.computePosition(
4113
4143
  reference,
@@ -4175,6 +4205,23 @@ function copyThemeClass(view, target) {
4175
4205
  });
4176
4206
  }
4177
4207
 
4208
+ // src/utils/announce.ts
4209
+ function announce(view, message) {
4210
+ const editorEl = view.dom.closest(".dm-editor");
4211
+ if (!(editorEl instanceof HTMLElement)) return;
4212
+ let region = editorEl.querySelector(":scope > .dm-live-region");
4213
+ if (!region) {
4214
+ region = document.createElement("div");
4215
+ region.className = "dm-live-region";
4216
+ region.setAttribute("data-dm-editor-ui", "");
4217
+ region.setAttribute("role", "status");
4218
+ region.setAttribute("aria-live", "polite");
4219
+ region.style.cssText = "position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;";
4220
+ editorEl.appendChild(region);
4221
+ }
4222
+ region.textContent = region.textContent === message ? `${message}\xA0` : message;
4223
+ }
4224
+
4178
4225
  // src/utils/refocusEditorAfterCommand.ts
4179
4226
  function refocusEditorAfterCommand(view) {
4180
4227
  requestAnimationFrame(() => {
@@ -4193,6 +4240,10 @@ function refocusEditorAfterCommand(view) {
4193
4240
  // src/utils/defaultBubbleContexts.ts
4194
4241
  var NOTION_MODE_CLASS = "dm-notion-mode";
4195
4242
  var NOTION_TEXT_CONTEXT = Object.freeze([
4243
+ // `ai` leads (Notion's "Ask AI"); skipped with its leading separator when
4244
+ // the pro extension is absent, exactly like `mathInline`.
4245
+ "ai",
4246
+ "|",
4196
4247
  "bold",
4197
4248
  "italic",
4198
4249
  "underline",
@@ -4678,6 +4729,7 @@ var ToolbarController = class _ToolbarController {
4678
4729
  * Executes a toolbar button's command.
4679
4730
  */
4680
4731
  executeCommand(item) {
4732
+ if (!this.editor.isEditable && item.allowReadOnly !== true) return;
4681
4733
  _ToolbarController.executeItem(this.editor, item);
4682
4734
  this.updateActiveStates();
4683
4735
  }
@@ -4920,16 +4972,20 @@ var ToolbarController = class _ToolbarController {
4920
4972
  checkButtonDisabled(item, canProxy) {
4921
4973
  const wasDisabled = this._disabledMap.get(item.name) ?? false;
4922
4974
  let nowDisabled = false;
4923
- try {
4924
- if (item.emitEvent) {
4925
- nowDisabled = this.editor.isActive("codeBlock");
4926
- } else if (canProxy) {
4927
- const canCmd = canProxy[item.command];
4928
- if (canCmd) {
4929
- nowDisabled = item.commandArgs?.length ? !canCmd(...item.commandArgs) : !canCmd();
4975
+ if (!this.editor.isEditable && item.allowReadOnly !== true) {
4976
+ nowDisabled = true;
4977
+ } else {
4978
+ try {
4979
+ if (item.emitEvent) {
4980
+ nowDisabled = this.editor.isActive("codeBlock");
4981
+ } else if (canProxy) {
4982
+ const canCmd = canProxy[item.command];
4983
+ if (canCmd) {
4984
+ nowDisabled = item.commandArgs?.length ? !canCmd(...item.commandArgs) : !canCmd();
4985
+ }
4930
4986
  }
4987
+ } catch {
4931
4988
  }
4932
- } catch {
4933
4989
  }
4934
4990
  if (wasDisabled !== nowDisabled) {
4935
4991
  this._disabledMap.set(item.name, nowDisabled);
@@ -5341,6 +5397,7 @@ function createFloatingMenuPlugin(options) {
5341
5397
  };
5342
5398
  hideMenu();
5343
5399
  const isVisibleNow = (view) => {
5400
+ if (!editor.isEditable) return false;
5344
5401
  const wantsShow = shouldShow({ editor, view, state: view.state });
5345
5402
  if (!requireExplicitTrigger) return wantsShow;
5346
5403
  const triggered = pluginKey.getState(view.state)?.triggered ?? false;
@@ -5720,7 +5777,8 @@ var Heading = Node2.create({
5720
5777
  key: new state.PluginKey("headingKeydownFix"),
5721
5778
  props: {
5722
5779
  handleDOMEvents: {
5723
- keydown(_view, event) {
5780
+ keydown(view, event) {
5781
+ if (!view.editable) return false;
5724
5782
  if (!event.altKey || !(event.metaKey || event.ctrlKey)) return false;
5725
5783
  const level = codeToLevel[event.code];
5726
5784
  if (level === void 0) return false;
@@ -8173,6 +8231,18 @@ var Placeholder = Extension.create({
8173
8231
  new state.Plugin({
8174
8232
  key: placeholderPluginKey,
8175
8233
  props: {
8234
+ // Focus and blur repaint the decorations: PM only recomputes
8235
+ // them when a transaction dispatches.
8236
+ handleDOMEvents: {
8237
+ focus: (view) => {
8238
+ view.dispatch(view.state.tr.setMeta(placeholderPluginKey, true));
8239
+ return false;
8240
+ },
8241
+ blur: (view) => {
8242
+ view.dispatch(view.state.tr.setMeta(placeholderPluginKey, false));
8243
+ return false;
8244
+ }
8245
+ },
8176
8246
  decorations: ({ doc, selection }) => {
8177
8247
  const editor = this.editor;
8178
8248
  if (!editor) return view.DecorationSet.empty;
@@ -8188,6 +8258,10 @@ var Placeholder = Extension.create({
8188
8258
  "data-placeholder": getPlaceholderText(node, pos)
8189
8259
  });
8190
8260
  if (showOnlyCurrent) {
8261
+ const view$1 = editor.view;
8262
+ if (!isDocEmpty && view$1?.hasFocus() !== true) {
8263
+ return view.DecorationSet.empty;
8264
+ }
8191
8265
  const { $anchor } = selection;
8192
8266
  if ($anchor.depth === 0) return view.DecorationSet.empty;
8193
8267
  const node = $anchor.parent;
@@ -8783,7 +8857,13 @@ var UniqueID = Extension.create({
8783
8857
  "listItem",
8784
8858
  "taskItem",
8785
8859
  "image",
8786
- "horizontalRule"
8860
+ "horizontalRule",
8861
+ // Listed even though it lives in a separate package, exactly as
8862
+ // `image` already is: a global attribute only installs on types the
8863
+ // schema actually has, so naming it is inert when the table extension
8864
+ // is not loaded. Without it a table is the one block the block menu
8865
+ // cannot Copy link, and the one an id-anchored feature cannot address.
8866
+ "table"
8787
8867
  ],
8788
8868
  attributeName: "id",
8789
8869
  generateID: generateUUID,
@@ -8811,7 +8891,8 @@ var UniqueID = Extension.create({
8811
8891
  addProseMirrorPlugins() {
8812
8892
  const { types, attributeName, generateID, filterDuplicates } = this.options;
8813
8893
  const editor = this.editor;
8814
- const transformPastedSlice = (slice) => {
8894
+ const transformPastedSlice = (slice, view) => {
8895
+ if (view?.dragging?.move === true) return slice;
8815
8896
  const existingIDs = /* @__PURE__ */ new Set();
8816
8897
  editor?.state.doc.descendants((node) => {
8817
8898
  const id = node.attrs[attributeName];
@@ -8845,7 +8926,36 @@ var UniqueID = Extension.create({
8845
8926
  slice.openEnd
8846
8927
  );
8847
8928
  };
8848
- const assignMissingIDs = (doc, tr) => {
8929
+ const incumbentPositions = (oldDoc, mapPos) => {
8930
+ const incumbents = /* @__PURE__ */ new Map();
8931
+ oldDoc.descendants((node, pos) => {
8932
+ if (!types.includes(node.type.name)) return;
8933
+ const id = node.attrs[attributeName];
8934
+ if (id && !incumbents.has(id)) incumbents.set(id, mapPos(pos));
8935
+ });
8936
+ return incumbents;
8937
+ };
8938
+ const assignMissingIDs = (doc, tr, incumbents) => {
8939
+ const winners = /* @__PURE__ */ new Map();
8940
+ if (incumbents && incumbents.size > 0) {
8941
+ const occurrences = /* @__PURE__ */ new Map();
8942
+ doc.descendants((node, pos) => {
8943
+ if (!types.includes(node.type.name)) return;
8944
+ const id = node.attrs[attributeName];
8945
+ if (!id) return;
8946
+ const list = occurrences.get(id);
8947
+ if (list) list.push(pos);
8948
+ else occurrences.set(id, [pos]);
8949
+ });
8950
+ for (const [id, positions] of occurrences) {
8951
+ if (positions.length < 2) continue;
8952
+ const incumbentPos = incumbents.get(id);
8953
+ winners.set(
8954
+ id,
8955
+ incumbentPos !== void 0 && positions.includes(incumbentPos) ? incumbentPos : positions[0]
8956
+ );
8957
+ }
8958
+ }
8849
8959
  const seen = /* @__PURE__ */ new Set();
8850
8960
  doc.descendants((node, pos) => {
8851
8961
  if (!types.includes(node.type.name)) return;
@@ -8860,7 +8970,9 @@ var UniqueID = Extension.create({
8860
8970
  });
8861
8971
  return;
8862
8972
  }
8863
- if (seen.has(existingID)) {
8973
+ const winner = winners.get(existingID);
8974
+ const yieldsToWinner = winner !== void 0 && winner !== pos;
8975
+ if (seen.has(existingID) || yieldsToWinner) {
8864
8976
  let id = generateID();
8865
8977
  while (seen.has(id)) id = generateID();
8866
8978
  seen.add(id);
@@ -8882,6 +8994,7 @@ var UniqueID = Extension.create({
8882
8994
  const tr = editorView.state.tr;
8883
8995
  assignMissingIDs(editorView.state.doc, tr);
8884
8996
  if (tr.docChanged) {
8997
+ tr.setMeta("addToHistory", false);
8885
8998
  editorView.dispatch(tr);
8886
8999
  }
8887
9000
  }, 0);
@@ -8892,12 +9005,18 @@ var UniqueID = Extension.create({
8892
9005
  };
8893
9006
  },
8894
9007
  // Ensure new nodes get IDs
8895
- appendTransaction(transactions, _oldState, newState) {
9008
+ appendTransaction(transactions, oldState, newState) {
8896
9009
  const docChanged = transactions.some((tr2) => tr2.docChanged);
8897
9010
  if (!docChanged) return null;
9011
+ const mapForward = (pos) => transactions.reduce((p, transaction) => transaction.mapping.map(p), pos);
8898
9012
  const tr = newState.tr;
8899
- assignMissingIDs(newState.doc, tr);
8900
- return tr.docChanged ? tr : null;
9013
+ assignMissingIDs(newState.doc, tr, incumbentPositions(oldState.doc, mapForward));
9014
+ if (!tr.docChanged) return null;
9015
+ const ridesAlongWithAnEdit = transactions.some(
9016
+ (transaction) => transaction.docChanged && transaction.getMeta("addToHistory") !== false
9017
+ );
9018
+ if (!ridesAlongWithAnEdit) tr.setMeta("addToHistory", false);
9019
+ return tr;
8901
9020
  },
8902
9021
  // Handle paste - filter duplicates
8903
9022
  props: filterDuplicates ? {
@@ -9866,14 +9985,14 @@ var FontSize = Extension.create({
9866
9985
  addToolbarItems() {
9867
9986
  if (this.options.fontSizes.length === 0) return [];
9868
9987
  const sizes = this.options.fontSizes;
9869
- const items = sizes.map((size, i) => ({
9988
+ const items = sizes.map((size2, i) => ({
9870
9989
  type: "button",
9871
- name: `fontSize-${size}`,
9990
+ name: `fontSize-${size2}`,
9872
9991
  command: "setFontSize",
9873
- commandArgs: [size],
9874
- isActive: { name: "textStyle", attributes: { fontSize: size } },
9992
+ commandArgs: [size2],
9993
+ isActive: { name: "textStyle", attributes: { fontSize: size2 } },
9875
9994
  icon: "textSize",
9876
- label: size,
9995
+ label: size2,
9877
9996
  priority: 200 - i
9878
9997
  }));
9879
9998
  if (this.options.showReset) {
@@ -10337,7 +10456,7 @@ function createBubbleMenuPlugin(options) {
10337
10456
  if (from !== prevValue.from || to !== prevValue.to) {
10338
10457
  suppressed = false;
10339
10458
  }
10340
- const visible = !suppressed && !selection.empty && shouldShow({
10459
+ const visible = !suppressed && !selection.empty && editor.isEditable && shouldShow({
10341
10460
  editor,
10342
10461
  view: editor.view,
10343
10462
  state: newState,
@@ -10377,7 +10496,7 @@ function createBubbleMenuPlugin(options) {
10377
10496
  if (mouseDown) return;
10378
10497
  const { selection } = editor.view.state;
10379
10498
  const { from, to } = selection;
10380
- const show = !selection.empty && shouldShow({
10499
+ const show = !selection.empty && editor.isEditable && shouldShow({
10381
10500
  editor,
10382
10501
  view: editor.view,
10383
10502
  state: editor.view.state,
@@ -10413,6 +10532,10 @@ function createBubbleMenuPlugin(options) {
10413
10532
  return {
10414
10533
  update: (view, prevState) => {
10415
10534
  if (view.composing) return;
10535
+ if (!view.editable) {
10536
+ hideMenu();
10537
+ return;
10538
+ }
10416
10539
  const state = pluginKey.getState(view.state);
10417
10540
  const prevPluginState = pluginKey.getState(prevState);
10418
10541
  if (state?.visible === prevPluginState?.visible && state?.from === prevPluginState?.from && state?.to === prevPluginState?.to && !(state?.visible && view.state.doc !== prevState.doc)) {
@@ -10545,7 +10668,7 @@ var StarterKit = Extension.create({
10545
10668
  });
10546
10669
 
10547
10670
  // src/index.ts
10548
- var VERSION = "0.1.0";
10671
+ var VERSION = "0.14.0";
10549
10672
 
10550
10673
  Object.defineProperty(exports, "PluginKey", {
10551
10674
  enumerable: true,
@@ -10621,6 +10744,7 @@ exports.Typography = Typography;
10621
10744
  exports.Underline = Underline;
10622
10745
  exports.UniqueID = UniqueID;
10623
10746
  exports.VERSION = VERSION;
10747
+ exports.announce = announce;
10624
10748
  exports.applyInlineStyles = applyInlineStyles;
10625
10749
  exports.autolinkPlugin = autolinkPlugin;
10626
10750
  exports.autolinkPluginKey = autolinkPluginKey;