superdoc 1.12.0-next.11 → 1.12.0-next.13

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.
Files changed (33) hide show
  1. package/dist/chunks/{PdfViewer-1H_e-blH.cjs → PdfViewer-BM8GbrHb.cjs} +2 -2
  2. package/dist/chunks/{PdfViewer-2GmHPcuP.es.js → PdfViewer-D9duMPBd.es.js} +2 -2
  3. package/dist/chunks/{SuperConverter-DKC3eAnw.cjs → SuperConverter-BMjvGr5r.cjs} +14 -3
  4. package/dist/chunks/{SuperConverter-Cyd0kGmJ.es.js → SuperConverter-Dx95dNlr.es.js} +14 -3
  5. package/dist/chunks/{index-BKXPlsQF.cjs → index-BF7DfWLs.cjs} +756 -390
  6. package/dist/chunks/{index-BlzZBR33.es.js → index-BTrpAvox.es.js} +4 -4
  7. package/dist/chunks/{index-C22k0MUW.es.js → index-C-t9SDCr.es.js} +756 -390
  8. package/dist/chunks/{index-CpgpR-lf.cjs → index-DcUDN9u9.cjs} +4 -4
  9. package/dist/style.css +39 -29
  10. package/dist/super-editor/converter.cjs +1 -1
  11. package/dist/super-editor/converter.es.js +1 -1
  12. package/dist/super-editor/core/Editor.d.ts.map +1 -1
  13. package/dist/super-editor/core/presentation-editor/PresentationEditor.d.ts.map +1 -1
  14. package/dist/super-editor/core/super-converter/v3/handlers/w/sdt/helpers/handle-structured-content-node.d.ts.map +1 -1
  15. package/dist/super-editor/extensions/structured-content/StructuredContentBlockView.d.ts.map +1 -1
  16. package/dist/super-editor/extensions/structured-content/StructuredContentInlineView.d.ts.map +1 -1
  17. package/dist/super-editor/extensions/structured-content/StructuredContentViewBase.d.ts +3 -0
  18. package/dist/super-editor/extensions/structured-content/StructuredContentViewBase.d.ts.map +1 -1
  19. package/dist/super-editor/extensions/structured-content/structured-content-block.d.ts.map +1 -1
  20. package/dist/super-editor/extensions/structured-content/structured-content-lock-plugin.d.ts +5 -0
  21. package/dist/super-editor/extensions/structured-content/structured-content-lock-plugin.d.ts.map +1 -0
  22. package/dist/super-editor/extensions/structured-content/structured-content-select-plugin.d.ts +14 -0
  23. package/dist/super-editor/extensions/structured-content/structured-content-select-plugin.d.ts.map +1 -0
  24. package/dist/super-editor/extensions/structured-content/structured-content.d.ts.map +1 -1
  25. package/dist/super-editor/extensions/types/node-attributes.d.ts +4 -0
  26. package/dist/super-editor/extensions/types/node-attributes.d.ts.map +1 -1
  27. package/dist/super-editor.cjs +2 -2
  28. package/dist/super-editor.es.js +3 -3
  29. package/dist/superdoc.cjs +3 -3
  30. package/dist/superdoc.es.js +3 -3
  31. package/dist/superdoc.umd.js +770 -393
  32. package/dist/superdoc.umd.js.map +1 -1
  33. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { g as generateDocxRandomId, T as TextSelection$1, o as objectIncludes, w as wrapTextsInRuns, D as DOMParser$1, c as createDocFromMarkdown, a as createDocFromHTML, F as Fragment, b as chainableEditorState, d as convertMarkdownToHTML, f as findParentNode, e as findParentNodeClosestToPos, h as generateRandom32BitHex, i as generateRandomSigned32BitIntStrId, P as Plugin, j as PluginKey, M as Mapping, N as NodeSelection, k as Selection, l as Slice, m as DOMSerializer, n as Mark$1, p as dropPoint, A as AllSelection, q as Schema$2, s as canSplit, t as resolveRunProperties, u as encodeMarksFromRPr, v as liftTarget, x as replaceStep$1, y as canJoin, z as joinPoint, R as ReplaceAroundStep$1, B as htmlHandler, C as ReplaceStep, E as getResolvedParagraphProperties, G as changeListLevel, H as isList$1, I as updateNumberingProperties, L as ListHelpers, J as inputRulesPlugin, K as TrackDeleteMarkName, O as TrackInsertMarkName, Q as TrackFormatMarkName, U as AddMarkStep, V as RemoveMarkStep, W as CommandService, S as SuperConverter, X as EditorState, Y as unflattenListsInHtml, Z as SelectionRange, _ as Transform, $ as resolveParagraphProperties, a0 as resolveDocxFontFamily, a1 as _getReferencedTableStyles, a2 as resolveTableCellProperties, a3 as decodeRPrFromMarks, a4 as carbonCopy, a5 as calculateResolvedParagraphProperties, a6 as encodeCSSFromPPr, a7 as encodeCSSFromRPr, a8 as generateOrderedListIndex, a9 as docxNumberingHelpers, aa as InputRule, ab as insertNewRelationship, ac as kebabCase$1, ad as getUnderlineCssString, ae as handleClipboardPaste } from "./SuperConverter-Cyd0kGmJ.es.js";
1
+ import { g as generateDocxRandomId, T as TextSelection$1, o as objectIncludes, w as wrapTextsInRuns, D as DOMParser$1, c as createDocFromMarkdown, a as createDocFromHTML, F as Fragment, b as chainableEditorState, d as convertMarkdownToHTML, f as findParentNode, e as findParentNodeClosestToPos, h as generateRandom32BitHex, i as generateRandomSigned32BitIntStrId, P as Plugin, j as PluginKey, M as Mapping, N as NodeSelection, k as Selection, l as Slice, m as DOMSerializer, n as Mark$1, p as dropPoint, A as AllSelection, q as Schema$2, s as canSplit, t as resolveRunProperties, u as encodeMarksFromRPr, v as liftTarget, x as replaceStep$1, y as canJoin, z as joinPoint, R as ReplaceAroundStep$1, B as htmlHandler, C as ReplaceStep, E as getResolvedParagraphProperties, G as changeListLevel, H as isList$1, I as updateNumberingProperties, L as ListHelpers, J as inputRulesPlugin, K as TrackDeleteMarkName, O as TrackInsertMarkName, Q as TrackFormatMarkName, U as AddMarkStep, V as RemoveMarkStep, W as CommandService, S as SuperConverter, X as EditorState, Y as unflattenListsInHtml, Z as SelectionRange, _ as Transform, $ as resolveParagraphProperties, a0 as resolveDocxFontFamily, a1 as _getReferencedTableStyles, a2 as resolveTableCellProperties, a3 as decodeRPrFromMarks, a4 as carbonCopy, a5 as calculateResolvedParagraphProperties, a6 as encodeCSSFromPPr, a7 as encodeCSSFromRPr, a8 as generateOrderedListIndex, a9 as docxNumberingHelpers, aa as InputRule, ab as insertNewRelationship, ac as kebabCase$1, ad as getUnderlineCssString, ae as handleClipboardPaste } from "./SuperConverter-Dx95dNlr.es.js";
2
2
  import { j as twipsToInches, m as inchesToTwips, p as ptToTwips, d as linesToTwips, f as twipsToLines, k as pixelsToTwips, z as resolveOpcTargetPath, B as Buffer$3, D as getArrayBufferFromUrl, h as halfPointToPoints, c as twipsToPixels$2, E as convertSizeToCSS, F as inchesToPixels } from "./helpers-BqdwtJE0.es.js";
3
3
  import { p as process$1$2, r as ref, b as computed, c as createElementBlock, F as Fragment$1, U as renderList, O as withModifiers, o as openBlock, Q as normalizeClass, M as toDisplayString, i as createCommentVNode, a as createBaseVNode, V as createApp, e as onMounted, f as onUnmounted, u as unref, S as withDirectives, Y as vModelText, D as nextTick, N as normalizeStyle, B as watch, Z as withKeys, _ as createTextVNode, g as createVNode, h as h$1, $ as readonly, E as getCurrentInstance, l as onBeforeUnmount, s as reactive, m as onBeforeMount, j as inject, a0 as onActivated, a1 as onDeactivated, a2 as Comment, d as defineComponent, k as provide, q as Teleport, t as toRef, a3 as renderSlot, a4 as isVNode, K as shallowRef, w as watchEffect, J as global$2, T as Transition, a5 as mergeProps, a6 as vShow, a7 as cloneVNode, a8 as Text$2, x as markRaw, P as createBlock, L as withCtx, a9 as useCssVars, W as resolveDynamicComponent, aa as normalizeProps, ab as guardReactiveProps } from "./vue-DiPLN5sT.es.js";
4
4
  import "./jszip.min-BCZQ7Wq2.es.js";
@@ -976,7 +976,7 @@ const DEFAULT_ENDPOINT = "https://ingest.superdoc.dev/v1/collect";
976
976
  const COMMUNITY_LICENSE_KEY = "community-and-eval-agplv3";
977
977
  function getSuperdocVersion() {
978
978
  try {
979
- return true ? "1.12.0-next.11" : "unknown";
979
+ return true ? "1.12.0-next.13" : "unknown";
980
980
  } catch {
981
981
  return "unknown";
982
982
  }
@@ -17051,7 +17051,7 @@ const canUseDOM = () => {
17051
17051
  return false;
17052
17052
  }
17053
17053
  };
17054
- const summaryVersion = "1.12.0-next.11";
17054
+ const summaryVersion = "1.12.0-next.13";
17055
17055
  const nodeKeys = ["group", "content", "marks", "inline", "atom", "defining", "code", "tableRole", "summary"];
17056
17056
  const markKeys = ["group", "inclusive", "excludes", "spanning", "code"];
17057
17057
  function mapAttributes(attrs) {
@@ -19465,8 +19465,7 @@ class Editor extends EventEmitter {
19465
19465
  "docProps/custom.xml": String(customXml),
19466
19466
  "word/_rels/document.xml.rels": String(rels),
19467
19467
  "word/numbering.xml": String(numbering),
19468
- // Replace & with & in styles.xml as DOCX viewers can't handle it
19469
- "word/styles.xml": String(styles).replace(/&/gi, "&"),
19468
+ "word/styles.xml": String(styles),
19470
19469
  ...updatedHeadersFooters,
19471
19470
  ...coreXml ? { "docProps/core.xml": String(coreXml) } : {}
19472
19471
  };
@@ -19856,7 +19855,7 @@ class Editor extends EventEmitter {
19856
19855
  * Process collaboration migrations
19857
19856
  */
19858
19857
  processCollaborationMigrations() {
19859
- console.debug("[checkVersionMigrations] Current editor version", "1.12.0-next.11");
19858
+ console.debug("[checkVersionMigrations] Current editor version", "1.12.0-next.13");
19860
19859
  if (!this.options.ydoc) return;
19861
19860
  const metaMap = this.options.ydoc.getMap("meta");
19862
19861
  let docVersion = metaMap.get("version");
@@ -20034,6 +20033,300 @@ class Editor extends EventEmitter {
20034
20033
  this.#renderer?.initDevTools?.(this);
20035
20034
  }
20036
20035
  }
20036
+ function getEditorSurfaceElement(editor) {
20037
+ if (!editor) return null;
20038
+ if (typeof editor.hitTest === "function" && editor.element instanceof HTMLElement) {
20039
+ return editor.element;
20040
+ }
20041
+ return editor.presentationEditor?.element ?? editor.view?.dom ?? editor.options?.element ?? null;
20042
+ }
20043
+ function getSurfaceRelativePoint(editor, eventLocation = {}) {
20044
+ const surface = getEditorSurfaceElement(editor);
20045
+ if (!surface) return null;
20046
+ const rect = surface.getBoundingClientRect();
20047
+ let left2;
20048
+ let top2;
20049
+ if (typeof eventLocation.clientX === "number" && typeof eventLocation.clientY === "number") {
20050
+ left2 = eventLocation.clientX - rect.left;
20051
+ top2 = eventLocation.clientY - rect.top;
20052
+ } else if (editor?.state?.selection) {
20053
+ const selectionFrom = editor.state.selection.from;
20054
+ const coords = editor.coordsAtPos?.(selectionFrom);
20055
+ if (coords) {
20056
+ left2 = coords.left - rect.left;
20057
+ top2 = coords.top - rect.top;
20058
+ }
20059
+ }
20060
+ if (!Number.isFinite(left2) || !Number.isFinite(top2)) {
20061
+ return null;
20062
+ }
20063
+ return { left: left2, top: top2 };
20064
+ }
20065
+ function findContainingBlockAncestor(element) {
20066
+ if (!element) return null;
20067
+ let current = element.parentElement;
20068
+ while (current && current !== document.body && current !== document.documentElement) {
20069
+ try {
20070
+ const style2 = window.getComputedStyle(current);
20071
+ const transform = style2.transform;
20072
+ const filter = style2.filter;
20073
+ const backdropFilter = style2.backdropFilter || style2.webkitBackdropFilter;
20074
+ const perspective = style2.perspective;
20075
+ const willChange = style2.willChange;
20076
+ const contain = style2.contain;
20077
+ if (transform && transform !== "none") {
20078
+ return current;
20079
+ }
20080
+ if (filter && filter !== "none") {
20081
+ return current;
20082
+ }
20083
+ if (backdropFilter && backdropFilter !== "none") {
20084
+ return current;
20085
+ }
20086
+ if (perspective && perspective !== "none") {
20087
+ return current;
20088
+ }
20089
+ if (willChange && willChange !== "auto") {
20090
+ const values = willChange.split(",").map((v) => v.trim());
20091
+ if (values.includes("transform") || values.includes("perspective")) {
20092
+ return current;
20093
+ }
20094
+ }
20095
+ if (contain && /paint|layout|strict|content/.test(contain)) {
20096
+ return current;
20097
+ }
20098
+ } catch (error) {
20099
+ console.warn("SlashMenu: Failed to get computed style for element", current, error);
20100
+ }
20101
+ current = current.parentElement;
20102
+ }
20103
+ return null;
20104
+ }
20105
+ const SlashMenuPluginKey = new PluginKey("slashMenu");
20106
+ const MENU_OFFSET_X = 100;
20107
+ const MENU_OFFSET_Y = 28;
20108
+ const CONTEXT_MENU_OFFSET_X = 10;
20109
+ const CONTEXT_MENU_OFFSET_Y = 10;
20110
+ const SLASH_COOLDOWN_MS = 5e3;
20111
+ const SlashMenu = Extension.create({
20112
+ name: "slashMenu",
20113
+ /**
20114
+ * Initialize default options for the SlashMenu extension
20115
+ * @returns {SlashMenuOptions} Empty options object (configuration is inherited from editor options)
20116
+ */
20117
+ addOptions() {
20118
+ return {};
20119
+ },
20120
+ addPmPlugins() {
20121
+ const editor = this.editor;
20122
+ if (editor.options?.isHeadless) {
20123
+ return [];
20124
+ }
20125
+ let slashCooldown = false;
20126
+ let slashCooldownTimeout = null;
20127
+ const isMenuDisabled = () => Boolean(editor.options?.disableContextMenu);
20128
+ const ensureStateShape = (value = {}) => ({
20129
+ open: false,
20130
+ selected: null,
20131
+ anchorPos: null,
20132
+ menuPosition: null,
20133
+ disabled: isMenuDisabled(),
20134
+ ...value
20135
+ });
20136
+ const slashMenuPlugin = new Plugin({
20137
+ key: SlashMenuPluginKey,
20138
+ state: {
20139
+ init: () => ensureStateShape(),
20140
+ /**
20141
+ * Apply transaction to update plugin state
20142
+ * Handles state transitions based on transaction metadata:
20143
+ * - 'open': Opens menu at specified position or cursor location
20144
+ * - 'select': Updates the selected menu item
20145
+ * - 'close': Closes the menu and clears anchor position
20146
+ * - 'updatePosition': Triggers menu position recalculation (no-op in apply)
20147
+ *
20148
+ * @param {import('prosemirror-state').Transaction} tr - The transaction
20149
+ * @param {SlashMenuState} value - Previous plugin state
20150
+ * @returns {SlashMenuState} New plugin state
20151
+ */
20152
+ apply(tr, value) {
20153
+ const meta = tr.getMeta(SlashMenuPluginKey);
20154
+ const disabled = isMenuDisabled();
20155
+ if (disabled) {
20156
+ if (value.open) {
20157
+ editor.emit("slashMenu:close");
20158
+ }
20159
+ return ensureStateShape({ disabled: true });
20160
+ }
20161
+ if (!meta) {
20162
+ if (value.disabled !== disabled) {
20163
+ return ensureStateShape({ ...value, disabled });
20164
+ }
20165
+ return value;
20166
+ }
20167
+ switch (meta.type) {
20168
+ case "open": {
20169
+ if (typeof meta.pos !== "number" || meta.pos < 0 || meta.pos > tr.doc.content.size) {
20170
+ console.warn("SlashMenu: Invalid position", meta.pos);
20171
+ return ensureStateShape(value);
20172
+ }
20173
+ let left2 = 0;
20174
+ let top2 = 0;
20175
+ let isContextMenu = false;
20176
+ if (typeof meta.clientX === "number" && typeof meta.clientY === "number") {
20177
+ left2 = meta.clientX;
20178
+ top2 = meta.clientY;
20179
+ isContextMenu = true;
20180
+ } else {
20181
+ const relativePoint = getSurfaceRelativePoint(editor, meta);
20182
+ if (relativePoint) {
20183
+ const surface = editor.presentationEditor?.element ?? editor.view?.dom ?? editor.options?.element;
20184
+ if (surface) {
20185
+ try {
20186
+ const rect = surface.getBoundingClientRect();
20187
+ left2 = rect.left + relativePoint.left;
20188
+ top2 = rect.top + relativePoint.top;
20189
+ } catch (error) {
20190
+ console.warn("SlashMenu: Failed to get surface bounds", error);
20191
+ return ensureStateShape(value);
20192
+ }
20193
+ }
20194
+ }
20195
+ }
20196
+ const menuSurface = editor.presentationEditor?.element ?? editor.view?.dom ?? editor.options?.element;
20197
+ const containingBlock = findContainingBlockAncestor(menuSurface);
20198
+ if (containingBlock) {
20199
+ try {
20200
+ const cbRect = containingBlock.getBoundingClientRect();
20201
+ left2 -= cbRect.left;
20202
+ top2 -= cbRect.top;
20203
+ left2 += containingBlock.scrollLeft || 0;
20204
+ top2 += containingBlock.scrollTop || 0;
20205
+ } catch (error) {
20206
+ console.warn("SlashMenu: Failed to adjust for containing block", error);
20207
+ }
20208
+ }
20209
+ const offsetX = isContextMenu ? CONTEXT_MENU_OFFSET_X : MENU_OFFSET_X;
20210
+ const offsetY = isContextMenu ? CONTEXT_MENU_OFFSET_Y : MENU_OFFSET_Y;
20211
+ const menuPosition = {
20212
+ left: `${left2 + offsetX}px`,
20213
+ top: `${top2 + offsetY}px`
20214
+ };
20215
+ const newState = {
20216
+ ...value,
20217
+ open: true,
20218
+ anchorPos: meta.pos,
20219
+ menuPosition
20220
+ };
20221
+ editor.emit("slashMenu:open", { menuPosition });
20222
+ return ensureStateShape(newState);
20223
+ }
20224
+ case "select": {
20225
+ return ensureStateShape({ ...value, selected: meta.id });
20226
+ }
20227
+ case "close": {
20228
+ editor.emit("slashMenu:close");
20229
+ return ensureStateShape({ ...value, open: false, anchorPos: null });
20230
+ }
20231
+ default:
20232
+ return ensureStateShape({ ...value, disabled });
20233
+ }
20234
+ }
20235
+ },
20236
+ /**
20237
+ * Create view plugin to handle window event listeners
20238
+ * @param {import('prosemirror-view').EditorView} editorView - The ProseMirror editor view
20239
+ * @returns {Object} View plugin with destroy method
20240
+ */
20241
+ view(editorView) {
20242
+ const updatePosition2 = () => {
20243
+ if (isMenuDisabled()) return;
20244
+ const state = SlashMenuPluginKey.getState(editorView.state);
20245
+ if (state.open) {
20246
+ editorView.dispatch(
20247
+ editorView.state.tr.setMeta(SlashMenuPluginKey, {
20248
+ type: "updatePosition"
20249
+ })
20250
+ );
20251
+ }
20252
+ };
20253
+ window.addEventListener("scroll", updatePosition2, true);
20254
+ window.addEventListener("resize", updatePosition2);
20255
+ return {
20256
+ destroy() {
20257
+ window.removeEventListener("scroll", updatePosition2, true);
20258
+ window.removeEventListener("resize", updatePosition2);
20259
+ if (slashCooldownTimeout) {
20260
+ clearTimeout(slashCooldownTimeout);
20261
+ slashCooldownTimeout = null;
20262
+ }
20263
+ }
20264
+ };
20265
+ },
20266
+ props: {
20267
+ /**
20268
+ * Handle keyboard events to open/close the slash menu
20269
+ * - '/': Opens menu at cursor if conditions are met (in paragraph, after space/start)
20270
+ * - 'Escape' or 'ArrowLeft': Closes menu and restores cursor position
20271
+ *
20272
+ * @param {import('prosemirror-view').EditorView} view - The ProseMirror editor view
20273
+ * @param {KeyboardEvent} event - The keyboard event
20274
+ * @returns {boolean} True if the event was handled, false otherwise
20275
+ */
20276
+ handleKeyDown(view, event) {
20277
+ if (isMenuDisabled()) {
20278
+ return false;
20279
+ }
20280
+ const pluginState = this.getState(view.state);
20281
+ if (event.key === "/" && slashCooldown) {
20282
+ return false;
20283
+ }
20284
+ if (event.key === "/" && !pluginState.open) {
20285
+ const { $cursor } = view.state.selection;
20286
+ if (!$cursor) return false;
20287
+ const isParagraph = $cursor.parent.type.name === "paragraph";
20288
+ if (!isParagraph) return false;
20289
+ const textBefore = $cursor.parent.textContent.slice(0, $cursor.parentOffset);
20290
+ const isEmptyOrAfterSpace = !textBefore || textBefore.endsWith(" ");
20291
+ if (!isEmptyOrAfterSpace) return false;
20292
+ event.preventDefault();
20293
+ slashCooldown = true;
20294
+ if (slashCooldownTimeout) clearTimeout(slashCooldownTimeout);
20295
+ slashCooldownTimeout = setTimeout(() => {
20296
+ slashCooldown = false;
20297
+ slashCooldownTimeout = null;
20298
+ }, SLASH_COOLDOWN_MS);
20299
+ view.dispatch(
20300
+ view.state.tr.setMeta(SlashMenuPluginKey, {
20301
+ type: "open",
20302
+ pos: $cursor.pos
20303
+ })
20304
+ );
20305
+ return true;
20306
+ }
20307
+ if (pluginState.open && (event.key === "Escape" || event.key === "ArrowLeft")) {
20308
+ const { anchorPos } = pluginState;
20309
+ view.dispatch(
20310
+ view.state.tr.setMeta(SlashMenuPluginKey, {
20311
+ type: "close"
20312
+ })
20313
+ );
20314
+ if (anchorPos !== null) {
20315
+ const tr = view.state.tr.setSelection(
20316
+ view.state.selection.constructor.near(view.state.doc.resolve(anchorPos))
20317
+ );
20318
+ view.dispatch(tr);
20319
+ view.focus();
20320
+ }
20321
+ return true;
20322
+ }
20323
+ return false;
20324
+ }
20325
+ }
20326
+ });
20327
+ return [slashMenuPlugin];
20328
+ }
20329
+ });
20037
20330
  let readFromCache;
20038
20331
  let addToCache;
20039
20332
  if (typeof WeakMap != "undefined") {
@@ -25067,21 +25360,21 @@ const SDT_CONTAINER_STYLES = `
25067
25360
 
25068
25361
  /* Structured content drag handle/label - positioned above */
25069
25362
  .superdoc-structured-content__label {
25070
- font-size: 10px;
25363
+ font-size: 11px;
25071
25364
  align-items: center;
25072
25365
  justify-content: center;
25073
25366
  position: absolute;
25074
25367
  left: 2px;
25075
25368
  top: -19px;
25076
25369
  width: calc(100% - 4px);
25077
- max-width: 110px;
25370
+ max-width: 130px;
25078
25371
  min-width: 0;
25079
25372
  height: 18px;
25080
25373
  padding: 0 4px;
25081
25374
  border: 1px solid #629be7;
25082
25375
  border-bottom: none;
25083
25376
  border-radius: 6px 6px 0 0;
25084
- background-color: #629be7dd;
25377
+ background-color: #629be7ee;
25085
25378
  box-sizing: border-box;
25086
25379
  z-index: 10;
25087
25380
  display: none;
@@ -25095,7 +25388,9 @@ const SDT_CONTAINER_STYLES = `
25095
25388
  text-overflow: ellipsis;
25096
25389
  }
25097
25390
 
25098
- .superdoc-structured-content-block:hover .superdoc-structured-content__label {
25391
+ /* Hover effect for block structured content (via event delegation class).
25392
+ * Shows label on hover — border reveal is handled by the lock-mode hover rule below. */
25393
+ .superdoc-structured-content-block.sdt-hover .superdoc-structured-content__label {
25099
25394
  display: inline-flex;
25100
25395
  }
25101
25396
 
@@ -25124,6 +25419,13 @@ const SDT_CONTAINER_STYLES = `
25124
25419
  border-bottom: none;
25125
25420
  }
25126
25421
 
25422
+ /* Collapse double borders between adjacent SDT blocks */
25423
+ .superdoc-structured-content-block + .superdoc-structured-content-block {
25424
+ border-top: none;
25425
+ border-top-left-radius: 0;
25426
+ border-top-right-radius: 0;
25427
+ }
25428
+
25127
25429
  /* Structured Content Inline - Inline wrapper with blue border */
25128
25430
  .superdoc-structured-content-inline {
25129
25431
  padding: 1px;
@@ -25135,21 +25437,15 @@ const SDT_CONTAINER_STYLES = `
25135
25437
  z-index: 10;
25136
25438
  }
25137
25439
 
25138
- /* Hover effect for inline structured content */
25139
- .superdoc-structured-content-inline:hover {
25140
- background-color: rgba(98, 155, 231, 0.15);
25141
- border-color: #4a8ad9;
25142
- }
25143
-
25144
25440
  /* Inline structured content label - shown on hover */
25145
25441
  .superdoc-structured-content-inline__label {
25146
25442
  position: absolute;
25147
25443
  bottom: calc(100% + 2px);
25148
25444
  left: 50%;
25149
25445
  transform: translateX(-50%);
25150
- font-size: 10px;
25151
- padding: 2px 6px;
25152
- background-color: #629be7dd;
25446
+ font-size: 11px;
25447
+ padding: 0 4px;
25448
+ background-color: #629be7ee;
25153
25449
  color: white;
25154
25450
  border-radius: 4px;
25155
25451
  white-space: nowrap;
@@ -25162,6 +25458,17 @@ const SDT_CONTAINER_STYLES = `
25162
25458
  display: block;
25163
25459
  }
25164
25460
 
25461
+ /* Hover highlight for SDT containers.
25462
+ * Blue border is always visible (set in base rules above).
25463
+ * Hover adds background highlight and z-index boost.
25464
+ * Block SDTs use .sdt-hover class (event delegation for multi-fragment coordination).
25465
+ * Inline SDTs use :hover (single element, no coordination needed). */
25466
+ .superdoc-structured-content-block[data-lock-mode].sdt-hover,
25467
+ .superdoc-structured-content-inline[data-lock-mode]:hover {
25468
+ background-color: rgba(98, 155, 231, 0.08);
25469
+ z-index: 9999999;
25470
+ }
25471
+
25165
25472
  /* Viewing mode: remove structured content affordances */
25166
25473
  .presentation-editor--viewing .superdoc-structured-content-block,
25167
25474
  .presentation-editor--viewing .superdoc-structured-content-inline {
@@ -25207,7 +25514,7 @@ const FIELD_ANNOTATION_STYLES = `
25207
25514
  .superdoc-layout .annotation *::selection {
25208
25515
  background: transparent;
25209
25516
  }
25210
-
25517
+
25211
25518
  .superdoc-layout .annotation::-moz-selection,
25212
25519
  .superdoc-layout .annotation *::-moz-selection {
25213
25520
  background: transparent;
@@ -25359,7 +25666,16 @@ const DOM_CLASS_NAMES = {
25359
25666
  * within this wrapper should be used for character-level positioning, not the
25360
25667
  * wrapper itself.
25361
25668
  */
25362
- INLINE_SDT_WRAPPER: "superdoc-structured-content-inline"
25669
+ INLINE_SDT_WRAPPER: "superdoc-structured-content-inline",
25670
+ /**
25671
+ * Class name for block-level structured content containers.
25672
+ */
25673
+ BLOCK_SDT: "superdoc-structured-content-block",
25674
+ /**
25675
+ * Class name added to block SDT fragments on hover via event delegation.
25676
+ * Applied/removed by SdtGroupedHover to highlight all fragments of the same SDT.
25677
+ */
25678
+ SDT_HOVER: "sdt-hover"
25363
25679
  };
25364
25680
  const DEFAULT_ALLOWED_PROTOCOLS = ["http", "https", "mailto", "tel", "sms"];
25365
25681
  const OPTIONAL_PROTOCOLS = ["ftp", "sftp", "irc"];
@@ -25764,6 +26080,11 @@ function applySdtContainerStyling(doc2, container, sdt, containerSdt, boundaryOp
25764
26080
  container.dataset.sdtContainerStart = String(isStart);
25765
26081
  container.dataset.sdtContainerEnd = String(isEnd);
25766
26082
  container.style.overflow = "visible";
26083
+ if (isStructuredContentMetadata(sdt)) {
26084
+ container.dataset.lockMode = sdt.lockMode || "unlocked";
26085
+ } else if (isStructuredContentMetadata(containerSdt)) {
26086
+ container.dataset.lockMode = containerSdt.lockMode || "unlocked";
26087
+ }
25767
26088
  if (boundaryOptions?.widthOverride != null) {
25768
26089
  container.style.width = `${boundaryOptions.widthOverride}px`;
25769
26090
  }
@@ -25776,6 +26097,19 @@ function applySdtContainerStyling(doc2, container, sdt, containerSdt, boundaryOp
25776
26097
  container.appendChild(labelEl);
25777
26098
  }
25778
26099
  }
26100
+ function shouldRebuildForSdtBoundary(element, boundary) {
26101
+ if (!boundary) {
26102
+ return element.dataset.sdtContainerStart !== void 0;
26103
+ }
26104
+ const startAttr = element.dataset.sdtContainerStart;
26105
+ const endAttr = element.dataset.sdtContainerEnd;
26106
+ const expectedStart = String(boundary.isStart ?? true);
26107
+ const expectedEnd = String(boundary.isEnd ?? true);
26108
+ if (startAttr === void 0 || endAttr === void 0) {
26109
+ return true;
26110
+ }
26111
+ return startAttr !== expectedStart || endAttr !== expectedEnd;
26112
+ }
25779
26113
  const TWIPS_PER_INCH$4 = 1440;
25780
26114
  const PX_PER_INCH$3 = 96;
25781
26115
  const PX_PER_PT = 96 / 72;
@@ -27254,6 +27588,65 @@ function assertFragmentPmPositions(fragment, _context) {
27254
27588
  const hasPmEnd = fragment.pmEnd != null;
27255
27589
  globalValidationStats.record(hasPmStart, hasPmEnd);
27256
27590
  }
27591
+ const SDT_BLOCK_SELECTOR = `.${DOM_CLASS_NAMES.BLOCK_SDT}[data-sdt-id]`;
27592
+ const HOVER_CLASS = DOM_CLASS_NAMES.SDT_HOVER;
27593
+ function sdtElementsById(root2, sdtId) {
27594
+ return root2.querySelectorAll(`.${DOM_CLASS_NAMES.BLOCK_SDT}[data-sdt-id="${sdtId}"]`);
27595
+ }
27596
+ class SdtGroupedHover {
27597
+ constructor() {
27598
+ this.hoveredSdtId = null;
27599
+ this.mount = null;
27600
+ this.onMouseOver = null;
27601
+ this.onMouseLeave = null;
27602
+ }
27603
+ /** Attach hover listeners to the mount element. Safe to call again on remount. */
27604
+ bind(mount2) {
27605
+ this.destroy();
27606
+ this.mount = mount2;
27607
+ this.onMouseOver = (e) => {
27608
+ const target = e.target.closest?.(SDT_BLOCK_SELECTOR);
27609
+ const sdtId = target?.dataset.sdtId ?? null;
27610
+ if (sdtId === this.hoveredSdtId) return;
27611
+ if (this.hoveredSdtId) {
27612
+ sdtElementsById(mount2, this.hoveredSdtId).forEach((el) => el.classList.remove(HOVER_CLASS));
27613
+ }
27614
+ this.hoveredSdtId = sdtId;
27615
+ if (sdtId) {
27616
+ sdtElementsById(mount2, sdtId).forEach((el) => el.classList.add(HOVER_CLASS));
27617
+ }
27618
+ };
27619
+ this.onMouseLeave = () => {
27620
+ if (this.hoveredSdtId) {
27621
+ sdtElementsById(mount2, this.hoveredSdtId).forEach((el) => el.classList.remove(HOVER_CLASS));
27622
+ this.hoveredSdtId = null;
27623
+ }
27624
+ };
27625
+ mount2.addEventListener("mouseover", this.onMouseOver);
27626
+ mount2.addEventListener("mouseleave", this.onMouseLeave);
27627
+ }
27628
+ /** Re-apply hover class after render. New/rebuilt elements lose the class. */
27629
+ reapply() {
27630
+ if (this.hoveredSdtId && this.mount) {
27631
+ sdtElementsById(this.mount, this.hoveredSdtId).forEach((el) => el.classList.add(HOVER_CLASS));
27632
+ }
27633
+ }
27634
+ /** Remove listeners and reset state. */
27635
+ destroy() {
27636
+ if (this.mount) {
27637
+ if (this.onMouseOver) {
27638
+ this.mount.removeEventListener("mouseover", this.onMouseOver);
27639
+ }
27640
+ if (this.onMouseLeave) {
27641
+ this.mount.removeEventListener("mouseleave", this.onMouseLeave);
27642
+ }
27643
+ }
27644
+ this.mount = null;
27645
+ this.onMouseOver = null;
27646
+ this.onMouseLeave = null;
27647
+ this.hoveredSdtId = null;
27648
+ }
27649
+ }
27257
27650
  const DEFAULT_PPI = 96;
27258
27651
  const DEFAULT_RULER_HEIGHT = 25;
27259
27652
  const TICK_SPACING_PX = DEFAULT_PPI / 8;
@@ -27962,6 +28355,7 @@ class DomPainter {
27962
28355
  this.onScrollHandler = null;
27963
28356
  this.onWindowScrollHandler = null;
27964
28357
  this.onResizeHandler = null;
28358
+ this.sdtHover = new SdtGroupedHover();
27965
28359
  this.activeCommentId = null;
27966
28360
  this.options = options;
27967
28361
  this.layoutMode = options.layoutMode ?? "vertical";
@@ -28227,6 +28621,7 @@ class DomPainter {
28227
28621
  };
28228
28622
  win.addEventListener("resize", this.onResizeHandler);
28229
28623
  }
28624
+ this.sdtHover.bind(mount2);
28230
28625
  }
28231
28626
  computeVirtualMetrics() {
28232
28627
  if (!this.currentLayout) return;
@@ -28369,6 +28764,7 @@ class DomPainter {
28369
28764
  }
28370
28765
  this.changedBlocks.clear();
28371
28766
  this.processedLayoutVersion = this.layoutVersion;
28767
+ this.sdtHover.reapply();
28372
28768
  }
28373
28769
  updateSpacers(start2, end2) {
28374
28770
  if (!this.topSpacerEl || !this.bottomSpacerEl) return;
@@ -28653,6 +29049,7 @@ class DomPainter {
28653
29049
  this.onScrollHandler = null;
28654
29050
  this.onWindowScrollHandler = null;
28655
29051
  this.onResizeHandler = null;
29052
+ this.sdtHover.destroy();
28656
29053
  this.layoutVersion = 0;
28657
29054
  this.processedLayoutVersion = -1;
28658
29055
  }
@@ -28714,7 +29111,9 @@ class DomPainter {
28714
29111
  if (current) {
28715
29112
  existing.delete(key2);
28716
29113
  const sdtBoundaryMismatch = shouldRebuildForSdtBoundary(current.element, sdtBoundary);
28717
- const needsRebuild = this.changedBlocks.has(fragment.blockId) || current.signature !== fragmentSignature(fragment, this.blockLookup) || sdtBoundaryMismatch;
29114
+ const newPmStart = fragment.pmStart;
29115
+ const mappingUnreliable = this.currentMapping != null && newPmStart != null && current.element.dataset.pmStart != null && this.currentMapping.map(Number(current.element.dataset.pmStart)) !== newPmStart;
29116
+ const needsRebuild = this.changedBlocks.has(fragment.blockId) || current.signature !== fragmentSignature(fragment, this.blockLookup) || sdtBoundaryMismatch || mappingUnreliable;
28718
29117
  if (needsRebuild) {
28719
29118
  const replacement = this.renderFragment(fragment, contextBase, sdtBoundary);
28720
29119
  pageEl.replaceChild(replacement, current.element);
@@ -30800,6 +31199,43 @@ class DomPainter {
30800
31199
  }
30801
31200
  return void 0;
30802
31201
  };
31202
+ let geoSdtWrapper = null;
31203
+ let geoSdtId = null;
31204
+ let geoSdtWrapperLeft = 0;
31205
+ let geoSdtMaxRight = 0;
31206
+ const closeGeoSdtWrapper = () => {
31207
+ if (geoSdtWrapper) {
31208
+ geoSdtWrapper.style.width = `${geoSdtMaxRight - geoSdtWrapperLeft}px`;
31209
+ el.appendChild(geoSdtWrapper);
31210
+ geoSdtWrapper = null;
31211
+ geoSdtId = null;
31212
+ }
31213
+ };
31214
+ const appendToLineGeo = (elem, runForSdt, elemLeftPx, elemWidthPx) => {
31215
+ const resolved = this.resolveRunSdtId(runForSdt);
31216
+ const thisRunSdtId = resolved?.sdtId ?? null;
31217
+ if (thisRunSdtId !== geoSdtId) {
31218
+ closeGeoSdtWrapper();
31219
+ }
31220
+ if (resolved && this.doc) {
31221
+ if (!geoSdtWrapper) {
31222
+ geoSdtWrapper = this.createInlineSdtWrapper(resolved.sdt);
31223
+ geoSdtId = thisRunSdtId;
31224
+ geoSdtWrapperLeft = elemLeftPx;
31225
+ geoSdtMaxRight = elemLeftPx;
31226
+ geoSdtWrapper.style.position = "absolute";
31227
+ geoSdtWrapper.style.left = `${elemLeftPx}px`;
31228
+ geoSdtWrapper.style.top = "0px";
31229
+ geoSdtWrapper.style.height = `${line.lineHeight}px`;
31230
+ }
31231
+ elem.style.left = `${elemLeftPx - geoSdtWrapperLeft}px`;
31232
+ geoSdtMaxRight = Math.max(geoSdtMaxRight, elemLeftPx + elemWidthPx);
31233
+ this.expandSdtWrapperPmRange(geoSdtWrapper, runForSdt.pmStart, runForSdt.pmEnd);
31234
+ geoSdtWrapper.appendChild(elem);
31235
+ } else {
31236
+ el.appendChild(elem);
31237
+ }
31238
+ };
30803
31239
  for (let runIndex = line.fromRun; runIndex <= line.toRun; runIndex += 1) {
30804
31240
  const baseRun = block.runs[runIndex];
30805
31241
  if (!baseRun) continue;
@@ -30831,7 +31267,7 @@ class DomPainter {
30831
31267
  if (baseRun.pmStart != null) tabEl.dataset.pmStart = String(baseRun.pmStart);
30832
31268
  if (baseRun.pmEnd != null) tabEl.dataset.pmEnd = String(baseRun.pmEnd);
30833
31269
  tabEl.dataset.layoutEpoch = String(this.layoutEpoch);
30834
- el.appendChild(tabEl);
31270
+ appendToLineGeo(tabEl, baseRun, tabStartX + indentOffset, actualTabWidth);
30835
31271
  cumulativeX = tabEndX;
30836
31272
  continue;
30837
31273
  }
@@ -30847,7 +31283,7 @@ class DomPainter {
30847
31283
  const segWidth = (runSegments2 && runSegments2[0]?.width !== void 0 ? runSegments2[0].width : elem.offsetWidth) ?? 0;
30848
31284
  elem.style.position = "absolute";
30849
31285
  elem.style.left = `${segX}px`;
30850
- el.appendChild(elem);
31286
+ appendToLineGeo(elem, baseRun, segX, segWidth);
30851
31287
  cumulativeX = baseSegX + segWidth;
30852
31288
  }
30853
31289
  continue;
@@ -30870,7 +31306,7 @@ class DomPainter {
30870
31306
  const segWidth = (runSegments2 && runSegments2[0]?.width !== void 0 ? runSegments2[0].width : 0) ?? 0;
30871
31307
  elem.style.position = "absolute";
30872
31308
  elem.style.left = `${segX}px`;
30873
- el.appendChild(elem);
31309
+ appendToLineGeo(elem, baseRun, segX, segWidth);
30874
31310
  cumulativeX = baseSegX + segWidth;
30875
31311
  }
30876
31312
  continue;
@@ -30905,7 +31341,7 @@ class DomPainter {
30905
31341
  const xPos = baseX + indentOffset;
30906
31342
  elem.style.position = "absolute";
30907
31343
  elem.style.left = `${xPos}px`;
30908
- el.appendChild(elem);
31344
+ appendToLineGeo(elem, segmentRun, xPos, segment.width ?? 0);
30909
31345
  let width = segment.width ?? 0;
30910
31346
  if (width <= 0 && this.doc) {
30911
31347
  const measureEl = elem.cloneNode(true);
@@ -30917,9 +31353,13 @@ class DomPainter {
30917
31353
  this.doc.body.removeChild(measureEl);
30918
31354
  }
30919
31355
  cumulativeX = baseX + width;
31356
+ if (geoSdtWrapper) {
31357
+ geoSdtMaxRight = Math.max(geoSdtMaxRight, xPos + width);
31358
+ }
30920
31359
  }
30921
31360
  });
30922
31361
  }
31362
+ closeGeoSdtWrapper();
30923
31363
  } else {
30924
31364
  let currentInlineSdtWrapper = null;
30925
31365
  let currentInlineSdtId = null;
@@ -30931,12 +31371,12 @@ class DomPainter {
30931
31371
  }
30932
31372
  };
30933
31373
  runsForLine.forEach((run) => {
30934
- const runSdt = run.sdt;
30935
- const isInlineSdt = runSdt?.type === "structuredContent" && runSdt?.scope === "inline";
30936
- const runSdtId = isInlineSdt && runSdt?.id ? String(runSdt.id) : null;
31374
+ const resolved = this.resolveRunSdtId(run);
31375
+ const runSdtId = resolved?.sdtId ?? null;
30937
31376
  if (runSdtId !== currentInlineSdtId) {
30938
31377
  closeCurrentWrapper();
30939
31378
  }
31379
+ let elem = null;
30940
31380
  if (run.kind === "tab") {
30941
31381
  const tabEl = this.doc.createElement("span");
30942
31382
  tabEl.classList.add("superdoc-tab");
@@ -30957,39 +31397,20 @@ class DomPainter {
30957
31397
  if (run.pmStart != null) tabEl.dataset.pmStart = String(run.pmStart);
30958
31398
  if (run.pmEnd != null) tabEl.dataset.pmEnd = String(run.pmEnd);
30959
31399
  tabEl.dataset.layoutEpoch = String(this.layoutEpoch);
30960
- el.appendChild(tabEl);
30961
- return;
31400
+ elem = tabEl;
31401
+ } else {
31402
+ elem = this.renderRun(run, context, trackedConfig);
30962
31403
  }
30963
- const elem = this.renderRun(run, context, trackedConfig);
30964
31404
  if (elem) {
30965
31405
  if (styleId) {
30966
31406
  elem.setAttribute("styleid", styleId);
30967
31407
  }
30968
- if (isInlineSdt && runSdtId && this.doc) {
31408
+ if (resolved && this.doc) {
30969
31409
  if (!currentInlineSdtWrapper) {
30970
- currentInlineSdtWrapper = this.doc.createElement("span");
30971
- currentInlineSdtWrapper.className = DOM_CLASS_NAMES.INLINE_SDT_WRAPPER;
30972
- currentInlineSdtWrapper.dataset.layoutEpoch = String(this.layoutEpoch);
31410
+ currentInlineSdtWrapper = this.createInlineSdtWrapper(resolved.sdt);
30973
31411
  currentInlineSdtId = runSdtId;
30974
- this.applySdtDataset(currentInlineSdtWrapper, runSdt);
30975
- const alias = runSdt?.alias || "Inline content";
30976
- const labelEl = this.doc.createElement("span");
30977
- labelEl.className = `${DOM_CLASS_NAMES.INLINE_SDT_WRAPPER}__label`;
30978
- labelEl.textContent = alias;
30979
- currentInlineSdtWrapper.appendChild(labelEl);
30980
- }
30981
- const wrapperPmStart = currentInlineSdtWrapper.dataset.pmStart;
30982
- const wrapperPmEnd = currentInlineSdtWrapper.dataset.pmEnd;
30983
- if (run.pmStart != null) {
30984
- if (!wrapperPmStart || run.pmStart < parseInt(wrapperPmStart, 10)) {
30985
- currentInlineSdtWrapper.dataset.pmStart = String(run.pmStart);
30986
- }
30987
- }
30988
- if (run.pmEnd != null) {
30989
- if (!wrapperPmEnd || run.pmEnd > parseInt(wrapperPmEnd, 10)) {
30990
- currentInlineSdtWrapper.dataset.pmEnd = String(run.pmEnd);
30991
- }
30992
31412
  }
31413
+ this.expandSdtWrapperPmRange(currentInlineSdtWrapper, run.pmStart, run.pmEnd);
30993
31414
  currentInlineSdtWrapper.appendChild(elem);
30994
31415
  } else {
30995
31416
  el.appendChild(elem);
@@ -31166,6 +31587,7 @@ class DomPainter {
31166
31587
  "sdtScope",
31167
31588
  "sdtTag",
31168
31589
  "sdtAlias",
31590
+ "lockMode",
31169
31591
  "sdtSectionTitle",
31170
31592
  "sdtSectionType",
31171
31593
  "sdtSectionLocked",
@@ -31190,6 +31612,49 @@ class DomPainter {
31190
31612
  el.dataset[key2] = String(value);
31191
31613
  }
31192
31614
  }
31615
+ /**
31616
+ * Resolve the inline SDT id from a run, or null if the run is not inside an inline SDT.
31617
+ */
31618
+ resolveRunSdtId(run) {
31619
+ const sdt = run.sdt;
31620
+ if (sdt?.type === "structuredContent" && sdt?.scope === "inline" && sdt?.id) {
31621
+ return { sdtId: String(sdt.id), sdt };
31622
+ }
31623
+ return null;
31624
+ }
31625
+ /**
31626
+ * Create an inline SDT wrapper `<span>` with className, layoutEpoch, dataset, and label.
31627
+ * Shared by both the geometry and run-based rendering paths.
31628
+ */
31629
+ createInlineSdtWrapper(sdt) {
31630
+ const wrapper = this.doc.createElement("span");
31631
+ wrapper.className = DOM_CLASS_NAMES.INLINE_SDT_WRAPPER;
31632
+ wrapper.dataset.layoutEpoch = String(this.layoutEpoch);
31633
+ this.applySdtDataset(wrapper, sdt);
31634
+ const alias = sdt?.alias || "Inline content";
31635
+ const labelEl = this.doc.createElement("span");
31636
+ labelEl.className = `${DOM_CLASS_NAMES.INLINE_SDT_WRAPPER}__label`;
31637
+ labelEl.textContent = alias;
31638
+ wrapper.appendChild(labelEl);
31639
+ return wrapper;
31640
+ }
31641
+ /**
31642
+ * Expand the PM position range tracked on an SDT wrapper to include a new run's range.
31643
+ */
31644
+ expandSdtWrapperPmRange(wrapper, pmStart, pmEnd) {
31645
+ if (pmStart != null) {
31646
+ const cur = wrapper.dataset.pmStart;
31647
+ if (!cur || pmStart < parseInt(cur, 10)) {
31648
+ wrapper.dataset.pmStart = String(pmStart);
31649
+ }
31650
+ }
31651
+ if (pmEnd != null) {
31652
+ const cur = wrapper.dataset.pmEnd;
31653
+ if (!cur || pmEnd > parseInt(cur, 10)) {
31654
+ wrapper.dataset.pmEnd = String(pmEnd);
31655
+ }
31656
+ }
31657
+ }
31193
31658
  /**
31194
31659
  * Applies SDT (Structured Document Tag) metadata to an element's dataset as data-sdt-* attributes.
31195
31660
  * Supports field annotations, structured content, document sections, and doc parts.
@@ -31217,6 +31682,7 @@ class DomPainter {
31217
31682
  this.setDatasetString(el, "sdtScope", metadata.scope);
31218
31683
  this.setDatasetString(el, "sdtTag", metadata.tag);
31219
31684
  this.setDatasetString(el, "sdtAlias", metadata.alias);
31685
+ this.setDatasetString(el, "lockMode", metadata.lockMode || "unlocked");
31220
31686
  } else if (metadata.type === "documentSection") {
31221
31687
  this.setDatasetString(el, "sdtSectionTitle", metadata.title);
31222
31688
  this.setDatasetString(el, "sdtSectionType", metadata.sectionType);
@@ -31305,17 +31771,6 @@ const computeSdtBoundaries = (fragments, blockLookup) => {
31305
31771
  }
31306
31772
  return boundaries;
31307
31773
  };
31308
- const shouldRebuildForSdtBoundary = (element, boundary) => {
31309
- if (!boundary) return false;
31310
- const startAttr = element.dataset.sdtContainerStart;
31311
- const endAttr = element.dataset.sdtContainerEnd;
31312
- const expectedStart = String(boundary.isStart ?? true);
31313
- const expectedEnd = String(boundary.isEnd ?? true);
31314
- if (startAttr === void 0 || endAttr === void 0) {
31315
- return true;
31316
- }
31317
- return startAttr !== expectedStart || endAttr !== expectedEnd;
31318
- };
31319
31774
  const fragmentKey = (fragment) => {
31320
31775
  if (fragment.kind === "para") {
31321
31776
  return `para:${fragment.blockId}:${fragment.fromLine}:${fragment.toLine}`;
@@ -40760,6 +41215,7 @@ function normalizeStructuredContentMetadata(nodeType, attrs) {
40760
41215
  id: toNullableString(attrs.id),
40761
41216
  tag: toOptionalString(attrs.tag),
40762
41217
  alias: toOptionalString(attrs.alias),
41218
+ lockMode: attrs.lockMode,
40763
41219
  sdtPr: attrs.sdtPr
40764
41220
  };
40765
41221
  }
@@ -41584,7 +42040,8 @@ function tabNodeToRun({
41584
42040
  positions,
41585
42041
  tabOrdinal,
41586
42042
  paragraphAttrs,
41587
- inheritedMarks
42043
+ inheritedMarks,
42044
+ sdtMetadata
41588
42045
  }) {
41589
42046
  const pos = positions.get(node);
41590
42047
  if (!pos) return null;
@@ -41600,6 +42057,9 @@ function tabNodeToRun({
41600
42057
  indent,
41601
42058
  leader: node.attrs?.leader ?? null
41602
42059
  };
42060
+ if (sdtMetadata) {
42061
+ run.sdt = sdtMetadata;
42062
+ }
41603
42063
  const marks = [...node.marks ?? [], ...inheritedMarks ?? []];
41604
42064
  if (marks.length > 0) {
41605
42065
  applyMarksToRun(run, marks);
@@ -64184,7 +64644,8 @@ class PresentationEditor extends EventEmitter {
64184
64644
  }
64185
64645
  const activeEditor = this.getActiveEditor();
64186
64646
  const hasFocus = activeEditor?.view?.hasFocus?.() ?? false;
64187
- if (!hasFocus) {
64647
+ const slashMenuOpen = activeEditor?.state ? !!SlashMenuPluginKey.getState(activeEditor.state)?.open : false;
64648
+ if (!hasFocus && !slashMenuOpen) {
64188
64649
  try {
64189
64650
  this.#clearSelectedFieldAnnotationClass();
64190
64651
  this.#localSelectionLayer.innerHTML = "";
@@ -66421,300 +66882,6 @@ const processAiHighlights = (editor, doc2, highlightColor, customClass = null) =
66421
66882
  });
66422
66883
  return { decorations };
66423
66884
  };
66424
- function getEditorSurfaceElement(editor) {
66425
- if (!editor) return null;
66426
- if (typeof editor.hitTest === "function" && editor.element instanceof HTMLElement) {
66427
- return editor.element;
66428
- }
66429
- return editor.presentationEditor?.element ?? editor.view?.dom ?? editor.options?.element ?? null;
66430
- }
66431
- function getSurfaceRelativePoint(editor, eventLocation = {}) {
66432
- const surface = getEditorSurfaceElement(editor);
66433
- if (!surface) return null;
66434
- const rect = surface.getBoundingClientRect();
66435
- let left2;
66436
- let top2;
66437
- if (typeof eventLocation.clientX === "number" && typeof eventLocation.clientY === "number") {
66438
- left2 = eventLocation.clientX - rect.left;
66439
- top2 = eventLocation.clientY - rect.top;
66440
- } else if (editor?.state?.selection) {
66441
- const selectionFrom = editor.state.selection.from;
66442
- const coords = editor.coordsAtPos?.(selectionFrom);
66443
- if (coords) {
66444
- left2 = coords.left - rect.left;
66445
- top2 = coords.top - rect.top;
66446
- }
66447
- }
66448
- if (!Number.isFinite(left2) || !Number.isFinite(top2)) {
66449
- return null;
66450
- }
66451
- return { left: left2, top: top2 };
66452
- }
66453
- function findContainingBlockAncestor(element) {
66454
- if (!element) return null;
66455
- let current = element.parentElement;
66456
- while (current && current !== document.body && current !== document.documentElement) {
66457
- try {
66458
- const style2 = window.getComputedStyle(current);
66459
- const transform = style2.transform;
66460
- const filter = style2.filter;
66461
- const backdropFilter = style2.backdropFilter || style2.webkitBackdropFilter;
66462
- const perspective = style2.perspective;
66463
- const willChange = style2.willChange;
66464
- const contain = style2.contain;
66465
- if (transform && transform !== "none") {
66466
- return current;
66467
- }
66468
- if (filter && filter !== "none") {
66469
- return current;
66470
- }
66471
- if (backdropFilter && backdropFilter !== "none") {
66472
- return current;
66473
- }
66474
- if (perspective && perspective !== "none") {
66475
- return current;
66476
- }
66477
- if (willChange && willChange !== "auto") {
66478
- const values = willChange.split(",").map((v) => v.trim());
66479
- if (values.includes("transform") || values.includes("perspective")) {
66480
- return current;
66481
- }
66482
- }
66483
- if (contain && /paint|layout|strict|content/.test(contain)) {
66484
- return current;
66485
- }
66486
- } catch (error) {
66487
- console.warn("SlashMenu: Failed to get computed style for element", current, error);
66488
- }
66489
- current = current.parentElement;
66490
- }
66491
- return null;
66492
- }
66493
- const SlashMenuPluginKey = new PluginKey("slashMenu");
66494
- const MENU_OFFSET_X = 100;
66495
- const MENU_OFFSET_Y = 28;
66496
- const CONTEXT_MENU_OFFSET_X = 10;
66497
- const CONTEXT_MENU_OFFSET_Y = 10;
66498
- const SLASH_COOLDOWN_MS = 5e3;
66499
- const SlashMenu = Extension.create({
66500
- name: "slashMenu",
66501
- /**
66502
- * Initialize default options for the SlashMenu extension
66503
- * @returns {SlashMenuOptions} Empty options object (configuration is inherited from editor options)
66504
- */
66505
- addOptions() {
66506
- return {};
66507
- },
66508
- addPmPlugins() {
66509
- const editor = this.editor;
66510
- if (editor.options?.isHeadless) {
66511
- return [];
66512
- }
66513
- let slashCooldown = false;
66514
- let slashCooldownTimeout = null;
66515
- const isMenuDisabled = () => Boolean(editor.options?.disableContextMenu);
66516
- const ensureStateShape = (value = {}) => ({
66517
- open: false,
66518
- selected: null,
66519
- anchorPos: null,
66520
- menuPosition: null,
66521
- disabled: isMenuDisabled(),
66522
- ...value
66523
- });
66524
- const slashMenuPlugin = new Plugin({
66525
- key: SlashMenuPluginKey,
66526
- state: {
66527
- init: () => ensureStateShape(),
66528
- /**
66529
- * Apply transaction to update plugin state
66530
- * Handles state transitions based on transaction metadata:
66531
- * - 'open': Opens menu at specified position or cursor location
66532
- * - 'select': Updates the selected menu item
66533
- * - 'close': Closes the menu and clears anchor position
66534
- * - 'updatePosition': Triggers menu position recalculation (no-op in apply)
66535
- *
66536
- * @param {import('prosemirror-state').Transaction} tr - The transaction
66537
- * @param {SlashMenuState} value - Previous plugin state
66538
- * @returns {SlashMenuState} New plugin state
66539
- */
66540
- apply(tr, value) {
66541
- const meta = tr.getMeta(SlashMenuPluginKey);
66542
- const disabled = isMenuDisabled();
66543
- if (disabled) {
66544
- if (value.open) {
66545
- editor.emit("slashMenu:close");
66546
- }
66547
- return ensureStateShape({ disabled: true });
66548
- }
66549
- if (!meta) {
66550
- if (value.disabled !== disabled) {
66551
- return ensureStateShape({ ...value, disabled });
66552
- }
66553
- return value;
66554
- }
66555
- switch (meta.type) {
66556
- case "open": {
66557
- if (typeof meta.pos !== "number" || meta.pos < 0 || meta.pos > tr.doc.content.size) {
66558
- console.warn("SlashMenu: Invalid position", meta.pos);
66559
- return ensureStateShape(value);
66560
- }
66561
- let left2 = 0;
66562
- let top2 = 0;
66563
- let isContextMenu = false;
66564
- if (typeof meta.clientX === "number" && typeof meta.clientY === "number") {
66565
- left2 = meta.clientX;
66566
- top2 = meta.clientY;
66567
- isContextMenu = true;
66568
- } else {
66569
- const relativePoint = getSurfaceRelativePoint(editor, meta);
66570
- if (relativePoint) {
66571
- const surface = editor.presentationEditor?.element ?? editor.view?.dom ?? editor.options?.element;
66572
- if (surface) {
66573
- try {
66574
- const rect = surface.getBoundingClientRect();
66575
- left2 = rect.left + relativePoint.left;
66576
- top2 = rect.top + relativePoint.top;
66577
- } catch (error) {
66578
- console.warn("SlashMenu: Failed to get surface bounds", error);
66579
- return ensureStateShape(value);
66580
- }
66581
- }
66582
- }
66583
- }
66584
- const menuSurface = editor.presentationEditor?.element ?? editor.view?.dom ?? editor.options?.element;
66585
- const containingBlock = findContainingBlockAncestor(menuSurface);
66586
- if (containingBlock) {
66587
- try {
66588
- const cbRect = containingBlock.getBoundingClientRect();
66589
- left2 -= cbRect.left;
66590
- top2 -= cbRect.top;
66591
- left2 += containingBlock.scrollLeft || 0;
66592
- top2 += containingBlock.scrollTop || 0;
66593
- } catch (error) {
66594
- console.warn("SlashMenu: Failed to adjust for containing block", error);
66595
- }
66596
- }
66597
- const offsetX = isContextMenu ? CONTEXT_MENU_OFFSET_X : MENU_OFFSET_X;
66598
- const offsetY = isContextMenu ? CONTEXT_MENU_OFFSET_Y : MENU_OFFSET_Y;
66599
- const menuPosition = {
66600
- left: `${left2 + offsetX}px`,
66601
- top: `${top2 + offsetY}px`
66602
- };
66603
- const newState = {
66604
- ...value,
66605
- open: true,
66606
- anchorPos: meta.pos,
66607
- menuPosition
66608
- };
66609
- editor.emit("slashMenu:open", { menuPosition });
66610
- return ensureStateShape(newState);
66611
- }
66612
- case "select": {
66613
- return ensureStateShape({ ...value, selected: meta.id });
66614
- }
66615
- case "close": {
66616
- editor.emit("slashMenu:close");
66617
- return ensureStateShape({ ...value, open: false, anchorPos: null });
66618
- }
66619
- default:
66620
- return ensureStateShape({ ...value, disabled });
66621
- }
66622
- }
66623
- },
66624
- /**
66625
- * Create view plugin to handle window event listeners
66626
- * @param {import('prosemirror-view').EditorView} editorView - The ProseMirror editor view
66627
- * @returns {Object} View plugin with destroy method
66628
- */
66629
- view(editorView) {
66630
- const updatePosition2 = () => {
66631
- if (isMenuDisabled()) return;
66632
- const state = SlashMenuPluginKey.getState(editorView.state);
66633
- if (state.open) {
66634
- editorView.dispatch(
66635
- editorView.state.tr.setMeta(SlashMenuPluginKey, {
66636
- type: "updatePosition"
66637
- })
66638
- );
66639
- }
66640
- };
66641
- window.addEventListener("scroll", updatePosition2, true);
66642
- window.addEventListener("resize", updatePosition2);
66643
- return {
66644
- destroy() {
66645
- window.removeEventListener("scroll", updatePosition2, true);
66646
- window.removeEventListener("resize", updatePosition2);
66647
- if (slashCooldownTimeout) {
66648
- clearTimeout(slashCooldownTimeout);
66649
- slashCooldownTimeout = null;
66650
- }
66651
- }
66652
- };
66653
- },
66654
- props: {
66655
- /**
66656
- * Handle keyboard events to open/close the slash menu
66657
- * - '/': Opens menu at cursor if conditions are met (in paragraph, after space/start)
66658
- * - 'Escape' or 'ArrowLeft': Closes menu and restores cursor position
66659
- *
66660
- * @param {import('prosemirror-view').EditorView} view - The ProseMirror editor view
66661
- * @param {KeyboardEvent} event - The keyboard event
66662
- * @returns {boolean} True if the event was handled, false otherwise
66663
- */
66664
- handleKeyDown(view, event) {
66665
- if (isMenuDisabled()) {
66666
- return false;
66667
- }
66668
- const pluginState = this.getState(view.state);
66669
- if (event.key === "/" && slashCooldown) {
66670
- return false;
66671
- }
66672
- if (event.key === "/" && !pluginState.open) {
66673
- const { $cursor } = view.state.selection;
66674
- if (!$cursor) return false;
66675
- const isParagraph = $cursor.parent.type.name === "paragraph";
66676
- if (!isParagraph) return false;
66677
- const textBefore = $cursor.parent.textContent.slice(0, $cursor.parentOffset);
66678
- const isEmptyOrAfterSpace = !textBefore || textBefore.endsWith(" ");
66679
- if (!isEmptyOrAfterSpace) return false;
66680
- event.preventDefault();
66681
- slashCooldown = true;
66682
- if (slashCooldownTimeout) clearTimeout(slashCooldownTimeout);
66683
- slashCooldownTimeout = setTimeout(() => {
66684
- slashCooldown = false;
66685
- slashCooldownTimeout = null;
66686
- }, SLASH_COOLDOWN_MS);
66687
- view.dispatch(
66688
- view.state.tr.setMeta(SlashMenuPluginKey, {
66689
- type: "open",
66690
- pos: $cursor.pos
66691
- })
66692
- );
66693
- return true;
66694
- }
66695
- if (pluginState.open && (event.key === "Escape" || event.key === "ArrowLeft")) {
66696
- const { anchorPos } = pluginState;
66697
- view.dispatch(
66698
- view.state.tr.setMeta(SlashMenuPluginKey, {
66699
- type: "close"
66700
- })
66701
- );
66702
- if (anchorPos !== null) {
66703
- const tr = view.state.tr.setSelection(
66704
- view.state.selection.constructor.near(view.state.doc.resolve(anchorPos))
66705
- );
66706
- view.dispatch(tr);
66707
- view.focus();
66708
- }
66709
- return true;
66710
- }
66711
- return false;
66712
- }
66713
- }
66714
- });
66715
- return [slashMenuPlugin];
66716
- }
66717
- });
66718
66885
  class StructuredContentViewBase {
66719
66886
  node;
66720
66887
  view;
@@ -66854,6 +67021,20 @@ class StructuredContentViewBase {
66854
67021
  dragHandle.append(textElement);
66855
67022
  return dragHandle;
66856
67023
  }
67024
+ isContentLocked() {
67025
+ const lockMode = this.node.attrs.lockMode;
67026
+ return lockMode === "contentLocked" || lockMode === "sdtContentLocked";
67027
+ }
67028
+ isSdtLocked() {
67029
+ const lockMode = this.node.attrs.lockMode;
67030
+ return lockMode === "sdtLocked" || lockMode === "sdtContentLocked";
67031
+ }
67032
+ updateContentEditability() {
67033
+ if (this.dom) {
67034
+ this.dom.classList.toggle("sd-structured-content--content-locked", this.isContentLocked());
67035
+ this.dom.classList.toggle("sd-structured-content--sdt-locked", this.isSdtLocked());
67036
+ }
67037
+ }
66857
67038
  onDragStart(event) {
66858
67039
  const { view } = this.editor;
66859
67040
  const target = event.target;
@@ -66909,10 +67090,12 @@ class StructuredContentInlineView extends StructuredContentViewBase {
66909
67090
  element.prepend(dragHandle);
66910
67091
  element.addEventListener("dragstart", (e) => this.onDragStart(e));
66911
67092
  this.root = element;
67093
+ this.updateContentEditability();
66912
67094
  }
66913
67095
  updateView() {
66914
67096
  const domAttrs = Attribute.mergeAttributes(this.htmlAttributes);
66915
67097
  updateDOMAttributes(this.dom, { ...domAttrs });
67098
+ this.updateContentEditability();
66916
67099
  }
66917
67100
  update(node, decorations, innerDecorations) {
66918
67101
  const result = super.update(node, decorations, innerDecorations);
@@ -66921,6 +67104,157 @@ class StructuredContentInlineView extends StructuredContentViewBase {
66921
67104
  return true;
66922
67105
  }
66923
67106
  }
67107
+ const STRUCTURED_CONTENT_LOCK_KEY = new PluginKey("structuredContentLock");
67108
+ function collectSDTNodes(doc2) {
67109
+ const sdtNodes = [];
67110
+ doc2.descendants((node, pos) => {
67111
+ if (node.type.name === "structuredContent" || node.type.name === "structuredContentBlock") {
67112
+ sdtNodes.push({
67113
+ type: node.type.name,
67114
+ lockMode: node.attrs.lockMode,
67115
+ pos,
67116
+ end: pos + node.nodeSize
67117
+ });
67118
+ }
67119
+ });
67120
+ return sdtNodes;
67121
+ }
67122
+ function checkLockViolation(sdtNodes, from3, to) {
67123
+ for (const sdt of sdtNodes) {
67124
+ const overlaps = from3 < sdt.end && to > sdt.pos;
67125
+ if (!overlaps) continue;
67126
+ const containsSDT = from3 <= sdt.pos && to >= sdt.end;
67127
+ const insideSDT = from3 >= sdt.pos && to <= sdt.end;
67128
+ const crossesStart = from3 < sdt.pos && to > sdt.pos && to < sdt.end;
67129
+ const crossesEnd = from3 > sdt.pos && from3 < sdt.end && to > sdt.end;
67130
+ const wouldDamageWrapper = containsSDT || crossesStart || crossesEnd;
67131
+ const wouldModifyContent = insideSDT && !containsSDT;
67132
+ const isSdtLocked = sdt.lockMode === "sdtLocked" || sdt.lockMode === "sdtContentLocked";
67133
+ const isContentLocked = sdt.lockMode === "contentLocked" || sdt.lockMode === "sdtContentLocked";
67134
+ if (isSdtLocked && wouldDamageWrapper) {
67135
+ return { blocked: true, reason: `Cannot delete SDT wrapper (${sdt.lockMode})` };
67136
+ }
67137
+ if (isContentLocked && wouldModifyContent) {
67138
+ return { blocked: true, reason: `Cannot modify content (${sdt.lockMode})` };
67139
+ }
67140
+ }
67141
+ return { blocked: false };
67142
+ }
67143
+ function createStructuredContentLockPlugin() {
67144
+ return new Plugin({
67145
+ key: STRUCTURED_CONTENT_LOCK_KEY,
67146
+ state: {
67147
+ init(_2, editorState) {
67148
+ return collectSDTNodes(editorState.doc);
67149
+ },
67150
+ apply(tr, cachedSDTNodes, _oldState, newState) {
67151
+ if (!tr.docChanged) return cachedSDTNodes;
67152
+ return collectSDTNodes(newState.doc);
67153
+ }
67154
+ },
67155
+ props: {
67156
+ /**
67157
+ * Intercept key events BEFORE any transaction is created.
67158
+ * This prevents the browser selection from getting out of sync.
67159
+ */
67160
+ handleKeyDown(view, event) {
67161
+ const { state } = view;
67162
+ const { selection } = state;
67163
+ const { from: from3, to } = selection;
67164
+ const isDelete = event.key === "Delete";
67165
+ const isBackspace = event.key === "Backspace";
67166
+ const isCut = (event.metaKey || event.ctrlKey) && event.key === "x";
67167
+ if (!isDelete && !isBackspace && !isCut) {
67168
+ return false;
67169
+ }
67170
+ const sdtNodes = STRUCTURED_CONTENT_LOCK_KEY.getState(state);
67171
+ if (sdtNodes.length === 0) {
67172
+ return false;
67173
+ }
67174
+ let affectedFrom = from3;
67175
+ let affectedTo = to;
67176
+ if (from3 === to) {
67177
+ if (isBackspace && from3 > 0) {
67178
+ affectedFrom = from3 - 1;
67179
+ } else if (isDelete && to < state.doc.content.size) {
67180
+ affectedTo = to + 1;
67181
+ }
67182
+ }
67183
+ const result = checkLockViolation(sdtNodes, affectedFrom, affectedTo);
67184
+ if (result.blocked) {
67185
+ event.preventDefault();
67186
+ return true;
67187
+ }
67188
+ return false;
67189
+ },
67190
+ /**
67191
+ * Handle text input (typing) for content-locked nodes
67192
+ */
67193
+ handleTextInput(view, from3, to, _text) {
67194
+ const sdtNodes = STRUCTURED_CONTENT_LOCK_KEY.getState(view.state);
67195
+ if (sdtNodes.length === 0) {
67196
+ return false;
67197
+ }
67198
+ const result = checkLockViolation(sdtNodes, from3, to);
67199
+ if (result.blocked) {
67200
+ return true;
67201
+ }
67202
+ return false;
67203
+ }
67204
+ },
67205
+ /**
67206
+ * Safety net: filter transactions that slip through
67207
+ * (e.g., programmatic changes, paste, drag-drop)
67208
+ */
67209
+ filterTransaction(tr, state) {
67210
+ if (!tr.docChanged) {
67211
+ return true;
67212
+ }
67213
+ const sdtNodes = STRUCTURED_CONTENT_LOCK_KEY.getState(state);
67214
+ if (sdtNodes.length === 0) {
67215
+ return true;
67216
+ }
67217
+ for (const step of tr.steps) {
67218
+ if (step.from === void 0 || step.to === void 0) {
67219
+ continue;
67220
+ }
67221
+ const result = checkLockViolation(sdtNodes, step.from, step.to);
67222
+ if (result.blocked) {
67223
+ return false;
67224
+ }
67225
+ }
67226
+ return true;
67227
+ }
67228
+ });
67229
+ }
67230
+ function createStructuredContentSelectPlugin() {
67231
+ return new Plugin({
67232
+ appendTransaction(transactions, oldState, newState) {
67233
+ const { selection } = newState;
67234
+ if (!selection.empty) return null;
67235
+ if (oldState.selection.eq(newState.selection)) return null;
67236
+ if (transactions.some((tr) => tr.docChanged)) return null;
67237
+ const $pos = selection.$from;
67238
+ for (let d = $pos.depth; d > 0; d--) {
67239
+ const node = $pos.node(d);
67240
+ if (node.type.name === "structuredContent") {
67241
+ const sdtStart = $pos.before(d);
67242
+ const contentFrom = $pos.start(d);
67243
+ const contentTo = $pos.end(d);
67244
+ if (contentFrom === contentTo) return null;
67245
+ const old$pos = oldState.selection.$from;
67246
+ for (let od = old$pos.depth; od > 0; od--) {
67247
+ if (old$pos.node(od).type.name === "structuredContent" && old$pos.before(od) === sdtStart) {
67248
+ return null;
67249
+ }
67250
+ }
67251
+ return newState.tr.setSelection(TextSelection$1.create(newState.doc, contentFrom, contentTo));
67252
+ }
67253
+ }
67254
+ return null;
67255
+ }
67256
+ });
67257
+ }
66924
67258
  const structuredContentClass = "sd-structured-content";
66925
67259
  const structuredContentInnerClass = "sd-structured-content__content";
66926
67260
  const StructuredContent = Node$1.create({
@@ -66966,6 +67300,14 @@ const StructuredContent = Node$1.create({
66966
67300
  return { "data-alias": attrs.alias };
66967
67301
  }
66968
67302
  },
67303
+ lockMode: {
67304
+ default: "unlocked",
67305
+ parseDOM: (elem) => elem.getAttribute("data-lock-mode") || "unlocked",
67306
+ renderDOM: (attrs) => {
67307
+ if (!attrs.lockMode || attrs.lockMode === "unlocked") return {};
67308
+ return { "data-lock-mode": attrs.lockMode };
67309
+ }
67310
+ },
66969
67311
  sdtPr: {
66970
67312
  rendered: false
66971
67313
  }
@@ -66983,6 +67325,9 @@ const StructuredContent = Node$1.create({
66983
67325
  0
66984
67326
  ];
66985
67327
  },
67328
+ addPmPlugins() {
67329
+ return [createStructuredContentLockPlugin(), createStructuredContentSelectPlugin()];
67330
+ },
66986
67331
  addNodeView() {
66987
67332
  return (props) => {
66988
67333
  return new StructuredContentInlineView({ ...props });
@@ -67017,10 +67362,12 @@ class StructuredContentBlockView extends StructuredContentViewBase {
67017
67362
  element.prepend(dragHandle);
67018
67363
  element.addEventListener("dragstart", (e) => this.onDragStart(e));
67019
67364
  this.root = element;
67365
+ this.updateContentEditability();
67020
67366
  }
67021
67367
  updateView() {
67022
67368
  const domAttrs = Attribute.mergeAttributes(this.htmlAttributes);
67023
67369
  updateDOMAttributes(this.dom, { ...domAttrs });
67370
+ this.updateContentEditability();
67024
67371
  }
67025
67372
  update(node, decorations, innerDecorations) {
67026
67373
  const result = super.update(node, decorations, innerDecorations);
@@ -67073,6 +67420,14 @@ const StructuredContentBlock = Node$1.create({
67073
67420
  return { "data-alias": attrs.alias };
67074
67421
  }
67075
67422
  },
67423
+ lockMode: {
67424
+ default: "unlocked",
67425
+ parseDOM: (elem) => elem.getAttribute("data-lock-mode") || "unlocked",
67426
+ renderDOM: (attrs) => {
67427
+ if (!attrs.lockMode || attrs.lockMode === "unlocked") return {};
67428
+ return { "data-lock-mode": attrs.lockMode };
67429
+ }
67430
+ },
67076
67431
  sdtPr: {
67077
67432
  rendered: false
67078
67433
  }
@@ -86349,18 +86704,25 @@ const _hoisted_3$8 = {
86349
86704
  };
86350
86705
  const _hoisted_4$4 = {
86351
86706
  key: 3,
86352
- class: "link-input-wrapper"
86707
+ class: "link-title"
86353
86708
  };
86354
- const _hoisted_5$2 = { class: "input-row text-input-row" };
86355
- const _hoisted_6$1 = ["onKeydown"];
86356
- const _hoisted_7$1 = { class: "input-row url-input-row" };
86357
- const _hoisted_8$1 = ["innerHTML"];
86358
- const _hoisted_9 = ["onKeydown"];
86359
- const _hoisted_10 = ["innerHTML"];
86360
- const _hoisted_11 = { class: "input-row link-buttons" };
86361
- const _hoisted_12 = ["innerHTML"];
86362
- const _hoisted_13 = {
86709
+ const _hoisted_5$2 = {
86363
86710
  key: 4,
86711
+ class: "link-input-wrapper"
86712
+ };
86713
+ const _hoisted_6$1 = { class: "input-row text-input-row" };
86714
+ const _hoisted_7$1 = ["readonly"];
86715
+ const _hoisted_8$1 = { class: "input-row url-input-row" };
86716
+ const _hoisted_9 = ["innerHTML"];
86717
+ const _hoisted_10 = ["readonly", "onKeydown"];
86718
+ const _hoisted_11 = ["innerHTML"];
86719
+ const _hoisted_12 = {
86720
+ key: 0,
86721
+ class: "input-row link-buttons"
86722
+ };
86723
+ const _hoisted_13 = ["innerHTML"];
86724
+ const _hoisted_14 = {
86725
+ key: 5,
86364
86726
  class: "input-row go-to-anchor clickable"
86365
86727
  };
86366
86728
  const _sfc_main$j = {
@@ -86454,6 +86816,7 @@ const _sfc_main$j = {
86454
86816
  });
86455
86817
  const isEditing = computed(() => !isAnchor.value && !!getLinkHrefAtSelection());
86456
86818
  const isDisabled2 = computed(() => !validUrl.value);
86819
+ const isViewingMode = computed(() => props.editor?.options?.documentMode === "viewing");
86457
86820
  const openLink = () => {
86458
86821
  window.open(url.value, "_blank");
86459
86822
  };
@@ -86479,6 +86842,7 @@ const _sfc_main$j = {
86479
86842
  if (props.showInput) focusInput();
86480
86843
  });
86481
86844
  const handleSubmit = () => {
86845
+ if (isViewingMode.value) return;
86482
86846
  const editor = props.editor;
86483
86847
  if (!editor) return;
86484
86848
  if (!rawUrl.value) {
@@ -86509,36 +86873,38 @@ const _sfc_main$j = {
86509
86873
  return openBlock(), createElementBlock("div", {
86510
86874
  class: normalizeClass(["link-input-ctn", { "high-contrast": unref(isHighContrastMode2) }])
86511
86875
  }, [
86512
- isAnchor.value ? (openBlock(), createElementBlock("div", _hoisted_1$g, "Page anchor")) : isEditing.value ? (openBlock(), createElementBlock("div", _hoisted_2$b, "Edit link")) : (openBlock(), createElementBlock("div", _hoisted_3$8, "Add link")),
86513
- __props.showInput && !isAnchor.value ? (openBlock(), createElementBlock("div", _hoisted_4$4, [
86514
- createBaseVNode("div", _hoisted_5$2, [
86515
- _cache[4] || (_cache[4] = createBaseVNode("div", { class: "input-icon text-input-icon" }, "T", -1)),
86876
+ isAnchor.value ? (openBlock(), createElementBlock("div", _hoisted_1$g, "Page anchor")) : isViewingMode.value ? (openBlock(), createElementBlock("div", _hoisted_2$b, "Link details")) : isEditing.value ? (openBlock(), createElementBlock("div", _hoisted_3$8, "Edit link")) : (openBlock(), createElementBlock("div", _hoisted_4$4, "Add link")),
86877
+ __props.showInput && !isAnchor.value ? (openBlock(), createElementBlock("div", _hoisted_5$2, [
86878
+ createBaseVNode("div", _hoisted_6$1, [
86879
+ _cache[5] || (_cache[5] = createBaseVNode("div", { class: "input-icon text-input-icon" }, "T", -1)),
86516
86880
  withDirectives(createBaseVNode("input", {
86517
86881
  type: "text",
86518
86882
  name: "text",
86519
86883
  placeholder: "Text",
86520
86884
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => text.value = $event),
86521
- onKeydown: withKeys(withModifiers(handleSubmit, ["stop", "prevent"]), ["enter"])
86522
- }, null, 40, _hoisted_6$1), [
86885
+ readonly: isViewingMode.value,
86886
+ onKeydown: _cache[1] || (_cache[1] = withKeys(withModifiers(($event) => !isViewingMode.value && handleSubmit, ["stop", "prevent"]), ["enter"]))
86887
+ }, null, 40, _hoisted_7$1), [
86523
86888
  [vModelText, text.value]
86524
86889
  ])
86525
86890
  ]),
86526
- createBaseVNode("div", _hoisted_7$1, [
86891
+ createBaseVNode("div", _hoisted_8$1, [
86527
86892
  createBaseVNode("div", {
86528
86893
  class: "input-icon",
86529
86894
  innerHTML: unref(toolbarIcons).linkInput
86530
- }, null, 8, _hoisted_8$1),
86895
+ }, null, 8, _hoisted_9),
86531
86896
  withDirectives(createBaseVNode("input", {
86532
86897
  type: "text",
86533
86898
  name: "link",
86534
86899
  placeholder: "Type or paste a link",
86535
86900
  class: normalizeClass({ error: urlError.value }),
86536
- "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => rawUrl.value = $event),
86901
+ "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => rawUrl.value = $event),
86902
+ readonly: isViewingMode.value,
86537
86903
  onKeydown: [
86538
86904
  withKeys(withModifiers(handleSubmit, ["stop", "prevent"]), ["enter"]),
86539
- _cache[2] || (_cache[2] = ($event) => urlError.value = false)
86905
+ _cache[3] || (_cache[3] = ($event) => urlError.value = false)
86540
86906
  ]
86541
- }, null, 42, _hoisted_9), [
86907
+ }, null, 42, _hoisted_10), [
86542
86908
  [vModelText, rawUrl.value]
86543
86909
  ]),
86544
86910
  createBaseVNode("div", {
@@ -86546,9 +86912,9 @@ const _sfc_main$j = {
86546
86912
  innerHTML: unref(toolbarIcons).openLink,
86547
86913
  onClick: openLink,
86548
86914
  "data-item": "btn-link-open"
86549
- }, null, 10, _hoisted_10)
86915
+ }, null, 10, _hoisted_11)
86550
86916
  ]),
86551
- createBaseVNode("div", _hoisted_11, [
86917
+ !isViewingMode.value ? (openBlock(), createElementBlock("div", _hoisted_12, [
86552
86918
  isEditing.value ? (openBlock(), createElementBlock("button", {
86553
86919
  key: 0,
86554
86920
  class: "remove-btn",
@@ -86558,25 +86924,25 @@ const _sfc_main$j = {
86558
86924
  createBaseVNode("div", {
86559
86925
  class: "remove-btn__icon",
86560
86926
  innerHTML: unref(toolbarIcons).removeLink
86561
- }, null, 8, _hoisted_12),
86562
- _cache[5] || (_cache[5] = createTextVNode(" Remove ", -1))
86927
+ }, null, 8, _hoisted_13),
86928
+ _cache[6] || (_cache[6] = createTextVNode(" Remove ", -1))
86563
86929
  ])) : createCommentVNode("", true),
86564
86930
  createBaseVNode("button", {
86565
86931
  class: normalizeClass(["submit-btn", { "disable-btn": isDisabled2.value }]),
86566
86932
  onClick: handleSubmit,
86567
86933
  "data-item": "btn-link-apply"
86568
86934
  }, " Apply ", 2)
86569
- ])
86570
- ])) : isAnchor.value ? (openBlock(), createElementBlock("div", _hoisted_13, [
86935
+ ])) : createCommentVNode("", true)
86936
+ ])) : isAnchor.value ? (openBlock(), createElementBlock("div", _hoisted_14, [
86571
86937
  createBaseVNode("a", {
86572
- onClick: _cache[3] || (_cache[3] = withModifiers((...args) => __props.goToAnchor && __props.goToAnchor(...args), ["stop", "prevent"]))
86938
+ onClick: _cache[4] || (_cache[4] = withModifiers((...args) => __props.goToAnchor && __props.goToAnchor(...args), ["stop", "prevent"]))
86573
86939
  }, "Go to " + toDisplayString(rawUrl.value.startsWith("#_") ? rawUrl.value.substring(2) : rawUrl.value), 1)
86574
86940
  ])) : createCommentVNode("", true)
86575
86941
  ], 2);
86576
86942
  };
86577
86943
  }
86578
86944
  };
86579
- const LinkInput = /* @__PURE__ */ _export_sfc(_sfc_main$j, [["__scopeId", "data-v-f64d35e8"]]);
86945
+ const LinkInput = /* @__PURE__ */ _export_sfc(_sfc_main$j, [["__scopeId", "data-v-198fc596"]]);
86580
86946
  const _hoisted_1$f = ["aria-label", "onClick", "onKeydown"];
86581
86947
  const _hoisted_2$a = ["innerHTML"];
86582
86948
  const _hoisted_3$7 = ["innerHTML"];