superdoc 2.0.0-next.26 → 2.0.0-next.27

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.
@@ -1393,6 +1393,17 @@ function readEntityId(row) {
1393
1393
  const id = record.id ?? record.commentId ?? record.changeId;
1394
1394
  return typeof id === "string" && id.length > 0 ? id : null;
1395
1395
  }
1396
+ function readCollapsedHostTrackChangeId(snapshot) {
1397
+ if (!snapshot || typeof snapshot !== "object") return null;
1398
+ const record = snapshot;
1399
+ const anchor = record.anchor;
1400
+ const focus = record.focus;
1401
+ const anchorVisual = anchor?.visualCaret;
1402
+ const focusVisual = focus?.visualCaret;
1403
+ const anchorId = anchorVisual?.trackChangeId;
1404
+ const focusId = focusVisual?.trackChangeId;
1405
+ return typeof anchorId === "string" && anchorId.length > 0 && anchorId === focusId ? anchorId : null;
1406
+ }
1396
1407
  var trackedChangeIdContextCache = /* @__PURE__ */ new WeakMap();
1397
1408
  function readTrackChangeProvenanceIds(item) {
1398
1409
  const out = [];
@@ -1543,12 +1554,11 @@ function paragraphTarget(blockId, story) {
1543
1554
  ...story && typeof story === "object" ? { story } : {}
1544
1555
  };
1545
1556
  }
1546
- function listsBlockTarget(blockId, story) {
1557
+ function listsBlockTarget(blockId) {
1547
1558
  return {
1548
1559
  kind: "block",
1549
1560
  nodeType: "paragraph",
1550
- nodeId: blockId,
1551
- ...story && typeof story === "object" ? { story } : {}
1561
+ nodeId: blockId
1552
1562
  };
1553
1563
  }
1554
1564
  function listItemTarget(blockId, story) {
@@ -2295,6 +2305,40 @@ function createSuperDocUI(options) {
2295
2305
  const host = getEditor()?.host;
2296
2306
  return host && typeof host === "object" ? host : null;
2297
2307
  };
2308
+ const resolveActiveHeaderFooterSlot = (story) => {
2309
+ if (story.storyType !== "headerFooterPart" || typeof story.refId !== "string") return null;
2310
+ const editor = getEditor();
2311
+ const host = getHost();
2312
+ const pageLayout = editor?.pageLayout ?? host?.pageLayout;
2313
+ if (!pageLayout || typeof pageLayout !== "object") return null;
2314
+ const getActiveRulerContext = pageLayout.getActiveRulerContext;
2315
+ const resolveEditTarget = host?.resolveHeaderFooterEditTarget;
2316
+ if (typeof getActiveRulerContext !== "function" || typeof resolveEditTarget !== "function") return null;
2317
+ const active = safeCall(() => getActiveRulerContext.call(pageLayout), null);
2318
+ if (!active || typeof active.pageIndex !== "number") return null;
2319
+ for (const headerFooterKind of ["header", "footer"]) {
2320
+ const resolved = safeCall(() => resolveEditTarget.call(host, {
2321
+ pageIndex: active.pageIndex,
2322
+ kind: headerFooterKind
2323
+ }), null);
2324
+ if (resolved?.status !== "ready" || resolved.refId !== story.refId || typeof resolved.sectionId !== "string" || ![
2325
+ "default",
2326
+ "first",
2327
+ "even"
2328
+ ].includes(String(resolved.slotVariant))) continue;
2329
+ return {
2330
+ kind: "story",
2331
+ storyType: "headerFooterSlot",
2332
+ section: {
2333
+ kind: "section",
2334
+ sectionId: resolved.sectionId
2335
+ },
2336
+ headerFooterKind,
2337
+ variant: resolved.slotVariant
2338
+ };
2339
+ }
2340
+ return null;
2341
+ };
2298
2342
  const readPendingInlineFormat = () => {
2299
2343
  const host = getHost();
2300
2344
  const fn = host?.getPendingInlineFormat;
@@ -2653,7 +2697,7 @@ function createSuperDocUI(options) {
2653
2697
  recompute();
2654
2698
  }
2655
2699
  });
2656
- const selectionReadToken = () => `${contentToken()}|s${selectionEpoch}`;
2700
+ const selectionReadToken = () => `${contentToken()}|m${readDocumentMode()}|s${selectionEpoch}`;
2657
2701
  const selectionSignature = (selection$1) => JSON.stringify({
2658
2702
  t: selection$1.target ?? null,
2659
2703
  s: selection$1.selectionTarget ?? null
@@ -3094,14 +3138,15 @@ function createSuperDocUI(options) {
3094
3138
  const end = target?.end;
3095
3139
  return start?.kind === "text" && end?.kind === "text" && typeof start.blockId === "string" && start.blockId === end.blockId && typeof start.offset === "number" && start.offset === end.offset;
3096
3140
  };
3097
- const seedCaretSelectionFromHost = () => {
3141
+ const seedCaretSelectionFromHost = (hostSelectionSnapshot) => {
3098
3142
  const host = getHost();
3099
3143
  const read = host?.readLiveSelectionSyncSnapshot;
3100
3144
  if (typeof read !== "function") return;
3101
3145
  const seed = normalizeSelectionInfo(safeCall(() => read.call(host), null));
3102
3146
  if (!seed || !isCollapsedCaretSnapshot(seed)) return;
3103
3147
  const token = selectionReadToken();
3104
- const deferAuthoritativeRead = foregroundMutationActive();
3148
+ const foreground = foregroundMutationState();
3149
+ const deferAuthoritativeRead = Boolean(foreground && (foreground.active > 0 || foreground.pending > 0));
3105
3150
  if (!deferAuthoritativeRead) {
3106
3151
  pendingSelectionSeedValidationToken = null;
3107
3152
  if (issueAsyncRead("selection", token, selectionCurrentRun, normalizeSelectionInfo)) return;
@@ -3111,11 +3156,22 @@ function createSuperDocUI(options) {
3111
3156
  }
3112
3157
  const prev = asyncReads.get("selection");
3113
3158
  const seedMarks = Array.isArray(seed.activeMarks) && seed.activeMarks.length > 0 ? seed.activeMarks : Array.isArray(state.selection.activeMarks) ? state.selection.activeMarks : [];
3159
+ const seedCommentIds = Array.isArray(seed.activeCommentIds) ? seed.activeCommentIds : [];
3160
+ const paintedTrackChangeId = readCollapsedHostTrackChangeId(hostSelectionSnapshot);
3161
+ const previousCaret = collapsedTextAddressFromSelection(state.selection);
3162
+ const nextCaret = collapsedTextAddressFromTarget(seed.selectionTarget) ?? collapsedTextAddressFromTarget(seed.target);
3163
+ const previousRange = previousCaret?.range;
3164
+ const nextRange = nextCaret?.range;
3165
+ const sameActiveTypingCaret = Boolean(foreground && foreground.active > 0 && previousCaret && nextCaret && previousCaret.blockId === nextCaret.blockId && storyLocatorSignature(previousCaret.story) === storyLocatorSignature(nextCaret.story) && typeof previousRange?.start === "number" && typeof nextRange?.start === "number" && Math.abs(nextRange.start - previousRange.start) <= 2);
3166
+ const carriedChangeIds = paintedTrackChangeId != null || sameActiveTypingCaret ? state.selection.activeChangeIds.filter((id) => paintedTrackChangeId == null || buildTrackedChangeIdContext(state.trackChanges.items).aliasesFor(id).includes(paintedTrackChangeId)) : [];
3167
+ const seedChangeIds = Array.isArray(seed.activeChangeIds) && seed.activeChangeIds.length > 0 ? seed.activeChangeIds : carriedChangeIds;
3114
3168
  asyncReads.set("selection", {
3115
3169
  token,
3116
3170
  value: {
3117
3171
  ...seed,
3118
- activeMarks: seedMarks
3172
+ activeMarks: seedMarks,
3173
+ activeCommentIds: seedCommentIds,
3174
+ activeChangeIds: seedChangeIds
3119
3175
  },
3120
3176
  hasSettled: true,
3121
3177
  inflightToken: deferAuthoritativeRead ? null : prev?.inflightToken ?? null
@@ -3706,6 +3762,7 @@ function createSuperDocUI(options) {
3706
3762
  const active = commandActiveState(descriptor, doc, selection$1);
3707
3763
  const value = routedCommandValue(descriptor, doc, selection$1, projectedInlineValues);
3708
3764
  if (descriptor.list?.mode === "indent" || descriptor.list?.mode === "outdent") return computeHybridIndentCommandState(descriptor, doc, readonly, selection$1, active, value);
3765
+ if (descriptor.list?.mode === "toggle-seed") return computeListToggleCommandState(descriptor, doc, readonly, selection$1, active, value);
3709
3766
  if (descriptor.mutates && readonly) return normalizeCommandState({
3710
3767
  enabled: false,
3711
3768
  active,
@@ -3822,47 +3879,81 @@ function createSuperDocUI(options) {
3822
3879
  }
3823
3880
  if (descriptor.id === "link") return readActiveLinkHref(doc, selection$1) ?? void 0;
3824
3881
  };
3825
- const readListStateForBlock = (doc, blockId) => {
3882
+ const readListStateSnapshotForBlock = (doc, blockId, story) => {
3826
3883
  const listsApi = doc?.lists;
3827
- if (!listsApi || typeof listsApi.getState !== "function") return null;
3828
- const { value: result } = readAsync(`lists:${blockId}`, contentToken(), () => listsApi.getState({ target: listsBlockTarget(blockId) }), (raw) => raw && typeof raw === "object" ? raw : null);
3829
- return result?.success === true ? result : null;
3884
+ if (!listsApi) return {
3885
+ value: null,
3886
+ status: "ready"
3887
+ };
3888
+ const isBodyStory = story.storyType === "body";
3889
+ const readState = typeof listsApi.getStateInStory === "function" ? () => listsApi.getStateInStory({
3890
+ target: listsBlockTarget(blockId),
3891
+ story
3892
+ }) : isBodyStory && typeof listsApi.getState === "function" ? () => listsApi.getState({ target: listsBlockTarget(blockId) }) : null;
3893
+ if (!readState) return {
3894
+ value: null,
3895
+ status: "ready"
3896
+ };
3897
+ const { value: result, status } = readAsync(`lists:${storyLocatorSignature(story)}:${blockId}`, contentToken(), readState, (raw) => raw && typeof raw === "object" ? raw : null);
3898
+ return {
3899
+ value: result?.success === true ? result : null,
3900
+ status
3901
+ };
3830
3902
  };
3831
- const readListSeedForBlock = (doc, blockId) => {
3832
- const result = readListStateForBlock(doc, blockId);
3903
+ const readListStateForBlock = (doc, blockId, story) => {
3904
+ return readListStateSnapshotForBlock(doc, blockId, story).value;
3905
+ };
3906
+ const readListSeedForBlock = (doc, blockId, story) => {
3907
+ const result = readListStateForBlock(doc, blockId, story);
3833
3908
  if (!result || result.isListItem !== true) return null;
3834
3909
  return result.seed === "bullet" || result.seed === "ordered" ? result.seed : null;
3835
3910
  };
3836
- const readListMembershipForBlock = (doc, blockId) => {
3837
- const result = readListStateForBlock(doc, blockId);
3838
- return result && typeof result.isListItem === "boolean" ? result.isListItem : null;
3911
+ const readListMembershipSnapshotForBlock = (doc, blockId, story) => {
3912
+ const snapshot = readListStateSnapshotForBlock(doc, blockId, story);
3913
+ const result = snapshot.value;
3914
+ return {
3915
+ value: result && typeof result.isListItem === "boolean" ? result.isListItem : null,
3916
+ status: snapshot.status
3917
+ };
3839
3918
  };
3840
3919
  const readListSeed = (doc, selection$1) => {
3841
3920
  const blockIds = selectionBlockIds(selection$1);
3842
3921
  if (blockIds.length === 0 || !canProbeEverySelectedBlock(blockIds)) return null;
3843
- const firstSeed = readListSeedForBlock(doc, blockIds[0]);
3922
+ const story = selectionStory(selection$1);
3923
+ const firstSeed = readListSeedForBlock(doc, blockIds[0], story);
3844
3924
  if (firstSeed !== "bullet" && firstSeed !== "ordered") return null;
3845
- for (const blockId of blockIds.slice(1)) if (readListSeedForBlock(doc, blockId) !== firstSeed) return null;
3925
+ for (const blockId of blockIds.slice(1)) if (readListSeedForBlock(doc, blockId, story) !== firstSeed) return null;
3846
3926
  return firstSeed;
3847
3927
  };
3848
- const readNodeById = (doc, blockId) => {
3849
- if (!doc || typeof doc.getNodeById !== "function") return {
3928
+ const readNodeById = (doc, blockId, story) => {
3929
+ if (!doc) return {
3850
3930
  value: null,
3851
3931
  status: "ready"
3852
3932
  };
3853
- return readAsync(`node:${blockId}`, contentToken(), () => doc.getNodeById({
3933
+ const storySignature = storyLocatorSignature(story);
3934
+ const isBodyStory = storySignature === "story:body";
3935
+ if (isBodyStory && typeof doc.getNodeById !== "function") return {
3936
+ value: null,
3937
+ status: "ready"
3938
+ };
3939
+ if (!isBodyStory && typeof doc.getNode !== "function") return {
3940
+ value: null,
3941
+ status: "ready"
3942
+ };
3943
+ return readAsync(`node:${storySignature}:${blockId}`, contentToken(), () => isBodyStory ? doc.getNodeById({
3854
3944
  nodeId: blockId,
3855
3945
  nodeType: "paragraph"
3856
- }), (raw) => raw && typeof raw === "object" ? raw : null);
3946
+ }) : doc.getNode(paragraphTarget(blockId, story)), (raw) => raw && typeof raw === "object" ? raw : null);
3857
3947
  };
3858
3948
  const resolveListMembershipForBlockAsync = async (doc, blockId, story) => {
3859
3949
  const listsApi = doc?.lists;
3860
3950
  if (!listsApi) return null;
3861
3951
  try {
3952
+ const isBodyStory = story.storyType === "body";
3862
3953
  const raw = typeof listsApi.getStateInStory === "function" ? await listsApi.getStateInStory({
3863
3954
  target: listsBlockTarget(blockId),
3864
3955
  story
3865
- }) : typeof listsApi.getState === "function" ? await listsApi.getState({ target: listsBlockTarget(blockId) }) : null;
3956
+ }) : isBodyStory && typeof listsApi.getState === "function" ? await listsApi.getState({ target: listsBlockTarget(blockId) }) : null;
3866
3957
  const result = raw && typeof raw === "object" ? raw : null;
3867
3958
  if (!result || result.success !== true || typeof result.isListItem !== "boolean") return null;
3868
3959
  return result.isListItem;
@@ -3870,13 +3961,42 @@ function createSuperDocUI(options) {
3870
3961
  return null;
3871
3962
  }
3872
3963
  };
3873
- const resolveParagraphIndentationForBlockAsync = async (doc, blockId) => {
3874
- if (!doc || typeof doc.getNodeById !== "function") return null;
3964
+ const resolveListSeedForBlock = (doc, blockId, story) => {
3965
+ const unavailable = {
3966
+ resolved: false,
3967
+ seed: null
3968
+ };
3969
+ const listsApi = doc?.lists;
3970
+ if (!listsApi) return unavailable;
3971
+ const isBodyStory = story.storyType === "body";
3972
+ const readState = typeof listsApi.getStateInStory === "function" ? () => listsApi.getStateInStory({
3973
+ target: listsBlockTarget(blockId),
3974
+ story
3975
+ }) : isBodyStory && typeof listsApi.getState === "function" ? () => listsApi.getState({ target: listsBlockTarget(blockId) }) : null;
3976
+ if (!readState) return unavailable;
3977
+ const normalize = (raw) => {
3978
+ const result = raw && typeof raw === "object" ? raw : null;
3979
+ if (!result || result.success !== true || typeof result.isListItem !== "boolean") return unavailable;
3980
+ return {
3981
+ resolved: true,
3982
+ seed: result.isListItem === true && (result.seed === "bullet" || result.seed === "ordered") ? result.seed : null
3983
+ };
3984
+ };
3985
+ try {
3986
+ const raw = readState();
3987
+ return isPromiseLike(raw) ? Promise.resolve(raw).then(normalize, () => unavailable) : normalize(raw);
3988
+ } catch {
3989
+ return unavailable;
3990
+ }
3991
+ };
3992
+ const resolveParagraphIndentationForBlockAsync = async (doc, blockId, story) => {
3993
+ if (!doc) return null;
3875
3994
  try {
3876
- const raw = await doc.getNodeById({
3995
+ const isBodyStory = !story || story.storyType === "body";
3996
+ const raw = story && typeof doc.getNode === "function" ? await doc.getNode(paragraphTarget(blockId, story)) : isBodyStory && typeof doc.getNodeById === "function" ? await doc.getNodeById({
3877
3997
  nodeId: blockId,
3878
3998
  nodeType: "paragraph"
3879
- });
3999
+ }) : null;
3880
4000
  return readParagraphIndentationFromResult(raw && typeof raw === "object" ? raw : null);
3881
4001
  } catch {
3882
4002
  return null;
@@ -3901,9 +4021,50 @@ function createSuperDocUI(options) {
3901
4021
  const listOp = descriptor.docRoute ? resolveDocOperation(doc, descriptor.docRoute) : null;
3902
4022
  const paragraphSetIndent = resolveDocOperation(doc, "format.paragraph.setIndentation");
3903
4023
  const paragraphClearIndent = resolveDocOperation(doc, "format.paragraph.clearIndentation");
3904
- const paragraphOpAvailable = descriptor.list?.mode === "indent" ? paragraphSetIndent != null : paragraphSetIndent != null || paragraphClearIndent != null;
3905
- for (const blockId of blockIds) {
3906
- const isListItem = readListMembershipForBlock(doc, blockId);
4024
+ const mode = descriptor.list?.mode;
4025
+ const paragraphOpAvailable = mode === "indent" ? paragraphSetIndent != null : paragraphSetIndent != null || paragraphClearIndent != null;
4026
+ const story = selectionStory(selection$1);
4027
+ let listMembership = null;
4028
+ if (story.storyType !== "body") {
4029
+ listMembership = [];
4030
+ for (const blockId of blockIds) {
4031
+ const { value: isListItem, status } = readListMembershipSnapshotForBlock(doc, blockId, story);
4032
+ if (isListItem === null) {
4033
+ if (status !== "ready" && (listOp != null || paragraphOpAvailable)) return normalizeCommandState({
4034
+ enabled: true,
4035
+ active,
4036
+ supported: true,
4037
+ value
4038
+ }, "builtin");
4039
+ return normalizeCommandState({
4040
+ enabled: false,
4041
+ active,
4042
+ supported: false,
4043
+ value,
4044
+ reason: unavailableRouteReason(doc)
4045
+ }, "builtin");
4046
+ }
4047
+ listMembership.push(isListItem);
4048
+ }
4049
+ }
4050
+ if (listMembership?.some((isListItem) => isListItem === true)) {
4051
+ const rangeRoute = mode === "indent" ? "lists.indentRange" : "lists.outdentRange";
4052
+ const canUseRange = readDocumentMode() !== "suggesting" && listMembership.every((isListItem) => isListItem === true) && resolveDocOperation(doc, rangeRoute) != null;
4053
+ const canUseStoryOutdent = mode === "outdent" && resolveDocOperation(doc, "lists.outdentInStory") != null;
4054
+ if (!canUseRange && !canUseStoryOutdent) return normalizeCommandState({
4055
+ enabled: false,
4056
+ active,
4057
+ supported: false,
4058
+ value,
4059
+ reason: unavailableRouteReason(doc)
4060
+ }, "builtin");
4061
+ }
4062
+ for (const [index, blockId] of blockIds.entries()) {
4063
+ const membership = listMembership ? {
4064
+ value: listMembership[index] ?? null,
4065
+ status: "ready"
4066
+ } : readListMembershipSnapshotForBlock(doc, blockId, story);
4067
+ const isListItem = membership.value;
3907
4068
  if (isListItem === true) {
3908
4069
  if (listOp != null) return normalizeCommandState({
3909
4070
  enabled: true,
@@ -3913,18 +4074,30 @@ function createSuperDocUI(options) {
3913
4074
  }, "builtin");
3914
4075
  continue;
3915
4076
  }
3916
- if (isListItem === false && paragraphOpAvailable) return normalizeCommandState({
3917
- enabled: true,
3918
- active,
3919
- supported: true,
3920
- value
3921
- }, "builtin");
3922
- if (isListItem === null && (listOp != null || paragraphOpAvailable)) return normalizeCommandState({
3923
- enabled: true,
3924
- active,
3925
- supported: true,
3926
- value
3927
- }, "builtin");
4077
+ if (isListItem === false && paragraphOpAvailable) {
4078
+ if (story.storyType !== "body" && typeof doc?.getNode !== "function") continue;
4079
+ return normalizeCommandState({
4080
+ enabled: true,
4081
+ active,
4082
+ supported: true,
4083
+ value
4084
+ }, "builtin");
4085
+ }
4086
+ if (isListItem === null) {
4087
+ if (membership.status !== "ready" && (listOp != null || paragraphOpAvailable)) return normalizeCommandState({
4088
+ enabled: true,
4089
+ active,
4090
+ supported: true,
4091
+ value
4092
+ }, "builtin");
4093
+ return normalizeCommandState({
4094
+ enabled: false,
4095
+ active,
4096
+ supported: false,
4097
+ value,
4098
+ reason: unavailableRouteReason(doc)
4099
+ }, "builtin");
4100
+ }
3928
4101
  }
3929
4102
  return normalizeCommandState({
3930
4103
  enabled: false,
@@ -3934,6 +4107,86 @@ function createSuperDocUI(options) {
3934
4107
  reason: unavailableRouteReason(doc)
3935
4108
  }, "builtin");
3936
4109
  };
4110
+ const computeListToggleCommandState = (descriptor, doc, readonly, selection$1, active, value) => {
4111
+ if (descriptor.mutates && readonly) return normalizeCommandState({
4112
+ enabled: false,
4113
+ active,
4114
+ supported: true,
4115
+ value,
4116
+ reason: SUPERDOC_UI_REASONS.documentReadonly
4117
+ }, "builtin");
4118
+ const blockIds = selectionBlockIds(selection$1);
4119
+ if (blockIds.length === 0) return normalizeCommandState({
4120
+ enabled: false,
4121
+ active,
4122
+ supported: true,
4123
+ value,
4124
+ reason: SUPERDOC_UI_REASONS.selectionRequired
4125
+ }, "builtin");
4126
+ const story = selectionStory(selection$1);
4127
+ const listsApi = doc?.lists;
4128
+ if (!(typeof listsApi?.getStateInStory === "function" || story.storyType === "body" && typeof listsApi?.getState === "function")) return normalizeCommandState({
4129
+ enabled: false,
4130
+ active,
4131
+ supported: false,
4132
+ value,
4133
+ reason: SUPERDOC_UI_REASONS.operationUnavailable
4134
+ }, "builtin");
4135
+ if (!canProbeEverySelectedBlock(blockIds)) return normalizeCommandState(story.storyType === "body" && resolveDocOperation(doc, "lists.remove") != null ? {
4136
+ enabled: true,
4137
+ active,
4138
+ supported: true,
4139
+ value
4140
+ } : {
4141
+ enabled: false,
4142
+ active,
4143
+ supported: false,
4144
+ value,
4145
+ reason: SUPERDOC_UI_REASONS.operationUnavailable
4146
+ }, "builtin");
4147
+ const snapshots = blockIds.map((blockId) => readListStateSnapshotForBlock(doc, blockId, story));
4148
+ if (snapshots.some(({ status }) => status !== "ready")) return normalizeCommandState({
4149
+ enabled: true,
4150
+ active,
4151
+ supported: true,
4152
+ value
4153
+ }, "builtin");
4154
+ if (snapshots.some(({ value: state$1 }) => state$1 === null)) return normalizeCommandState({
4155
+ enabled: false,
4156
+ active,
4157
+ supported: false,
4158
+ value,
4159
+ reason: SUPERDOC_UI_REASONS.operationUnavailable
4160
+ }, "builtin");
4161
+ const seed = descriptor.list?.seed;
4162
+ const shouldRemove = snapshots.every(({ value: state$1 }) => state$1?.isListItem === true && state$1.seed === seed);
4163
+ if (story.storyType === "body") return normalizeCommandState(!shouldRemove || resolveDocOperation(doc, "lists.remove") != null ? {
4164
+ enabled: true,
4165
+ active,
4166
+ supported: true,
4167
+ value
4168
+ } : {
4169
+ enabled: false,
4170
+ active,
4171
+ supported: false,
4172
+ value,
4173
+ reason: SUPERDOC_UI_REASONS.operationUnavailable
4174
+ }, "builtin");
4175
+ const removeInStory = resolveDocOperation(doc, "lists.removeInStory");
4176
+ if (shouldRemove && removeInStory) return normalizeCommandState({
4177
+ enabled: true,
4178
+ active,
4179
+ supported: true,
4180
+ value
4181
+ }, "builtin");
4182
+ return normalizeCommandState({
4183
+ enabled: false,
4184
+ active,
4185
+ supported: false,
4186
+ value,
4187
+ reason: SUPERDOC_UI_REASONS.operationUnavailable
4188
+ }, "builtin");
4189
+ };
3937
4190
  const readActiveLinkHref = (doc, selection$1) => {
3938
4191
  const href = (resolveCurrentHyperlink(doc, selection$1)?.properties)?.href;
3939
4192
  return typeof href === "string" ? href : null;
@@ -4048,8 +4301,8 @@ function createSuperDocUI(options) {
4048
4301
  status: combineStatus(full.status, quickResult.status)
4049
4302
  };
4050
4303
  };
4051
- const readBlockStyleRef = (doc, blockId) => {
4052
- const { value: result, status } = readNodeById(doc, blockId);
4304
+ const readBlockStyleRef = (doc, blockId, story) => {
4305
+ const { value: result, status } = readNodeById(doc, blockId, story);
4053
4306
  if (!result) return {
4054
4307
  ok: false,
4055
4308
  styleRef: null,
@@ -4128,8 +4381,9 @@ function createSuperDocUI(options) {
4128
4381
  let blockReadFailures = 0;
4129
4382
  let defaultReadFailures = 0;
4130
4383
  let readStatus = "ready";
4384
+ const story = selectionStory(selection$1);
4131
4385
  for (const blockId of blockIds) {
4132
- const read = readBlockStyleRef(doc, blockId);
4386
+ const read = readBlockStyleRef(doc, blockId, story);
4133
4387
  readStatus = combineStatus(readStatus, read.status);
4134
4388
  if (!read.ok) {
4135
4389
  blockReadFailures += 1;
@@ -4273,9 +4527,9 @@ function createSuperDocUI(options) {
4273
4527
  currentHostSelectionSource = next;
4274
4528
  if (!next) return;
4275
4529
  try {
4276
- const unsubscribe = next.subscribe(() => {
4530
+ const unsubscribe = next.subscribe((snapshot) => {
4277
4531
  selectionEpoch += 1;
4278
- seedCaretSelectionFromHost();
4532
+ seedCaretSelectionFromHost(snapshot);
4279
4533
  recompute();
4280
4534
  });
4281
4535
  if (typeof unsubscribe === "function") detachHostSelection = unsubscribe;
@@ -4428,6 +4682,39 @@ function createSuperDocUI(options) {
4428
4682
  function readTrackDecisionTargetId(target) {
4429
4683
  return target && typeof target === "object" && typeof target.id === "string" ? String(target.id) : null;
4430
4684
  }
4685
+ function trackDecisionRangeFromSelection(selection$1) {
4686
+ if (selection$1.empty) return null;
4687
+ const explicit = selection$1.selectionTarget;
4688
+ const start = explicit?.start;
4689
+ const end = explicit?.end;
4690
+ if (explicit?.kind !== "selection" || start?.kind !== "text" || end?.kind !== "text" || typeof start.blockId !== "string" || start.blockId !== end.blockId || !Number.isInteger(start.offset) || !Number.isInteger(end.offset) || start.offset < 0 || end.offset < 0 || start.offset === end.offset) return null;
4691
+ const rangeStart = Math.min(start.offset, end.offset);
4692
+ const rangeEnd = Math.max(start.offset, end.offset);
4693
+ const story = selectionStoryLocator(selection$1);
4694
+ return {
4695
+ kind: "text",
4696
+ ...story ? { story } : {},
4697
+ segments: [{
4698
+ blockId: start.blockId,
4699
+ range: {
4700
+ start: rangeStart,
4701
+ end: rangeEnd
4702
+ }
4703
+ }]
4704
+ };
4705
+ }
4706
+ function activeChangePartialDecisionRoute() {
4707
+ const activeId = state.selection.activeChangeIds[0];
4708
+ if (!activeId) return "unavailable";
4709
+ const items = readAllStoryTrackChanges();
4710
+ if (!items) return "unavailable";
4711
+ const story = selectionStoryLocator(state.selection);
4712
+ const item = items.find((candidate) => entityRowMatchesRequest(candidate, activeId, story));
4713
+ if (!item) return "unavailable";
4714
+ const type = trackChangesItemPayload(item).type;
4715
+ const grouping = trackChangesItemPayload(item).grouping;
4716
+ return (type === "insertion" || type === "deletion" || type === "insert" || type === "delete") && grouping === "standalone" ? "range" : "id";
4717
+ }
4431
4718
  function resolveTrackDecisionTarget(command, payload) {
4432
4719
  if (command.scope === "all") return {
4433
4720
  target: { scope: "all" },
@@ -4465,6 +4752,20 @@ function createSuperDocUI(options) {
4465
4752
  } : null;
4466
4753
  }
4467
4754
  function executeTrackDecisionCommand(command, payload) {
4755
+ if (command.scope === "id" && payload == null) {
4756
+ if (state.selection.activeChangeIds.length > 1) return executeTrackDecisionTargets(command.kind, state.selection.activeChangeIds);
4757
+ if (state.selection.activeChangeIds.length === 1 && !state.selection.empty && state.selection.selectionTarget) {
4758
+ const route = activeChangePartialDecisionRoute();
4759
+ if (route === "unavailable") return false;
4760
+ if (route === "range") {
4761
+ const range = trackDecisionRangeFromSelection(state.selection);
4762
+ return range ? executeTrackDecisionTarget(command.kind, {
4763
+ kind: "range",
4764
+ range
4765
+ }, null) : false;
4766
+ }
4767
+ }
4768
+ }
4468
4769
  const resolved = resolveTrackDecisionTarget(command, payload);
4469
4770
  if (!resolved) return false;
4470
4771
  return executeTrackDecisionTarget(command.kind, resolved.target, resolved.changeId);
@@ -4653,8 +4954,8 @@ function createSuperDocUI(options) {
4653
4954
  offsetSpace: "selection"
4654
4955
  });
4655
4956
  };
4656
- const buildBlockParagraphInput = (spec, blockId, payload) => {
4657
- const target = paragraphTarget(blockId);
4957
+ const buildBlockParagraphInput = (spec, blockId, payload, story) => {
4958
+ const target = paragraphTarget(blockId, story);
4658
4959
  switch (spec.kind) {
4659
4960
  case "alignment": {
4660
4961
  const alignment = payload;
@@ -4755,13 +5056,19 @@ function createSuperDocUI(options) {
4755
5056
  }
4756
5057
  }
4757
5058
  const resetOp = resolveDocOperation(doc, "format.paragraph.resetDirectFormatting");
4758
- const listsRemoveOp = resolveDocOperation(doc, "lists.remove");
4759
5059
  const setStyleRefOp = resolveDocOperation(doc, "styles.paragraph.setStyleRef");
5060
+ const story = selectionStoryLocator(state.selection);
5061
+ const isBodyStory = !story || story.storyType === "body";
5062
+ const listsRemoveOp = resolveDocOperation(doc, isBodyStory ? "lists.remove" : "lists.removeInStory");
4760
5063
  for (const blockId of selectionBlockIds(state.selection)) {
4761
- const target = paragraphTarget(blockId);
5064
+ const target = paragraphTarget(blockId, story);
4762
5065
  if (resetOp) run(resetOp, { target });
4763
5066
  if (listsRemoveOp) try {
4764
- const r = settleOperationResult(listsRemoveOp({ target }));
5067
+ const listTarget = listsBlockTarget(blockId);
5068
+ const r = settleOperationResult(listsRemoveOp(isBodyStory ? { target: listTarget } : {
5069
+ target: listTarget,
5070
+ story
5071
+ }));
4765
5072
  settledResults.push(r.settled.then((s) => commandResultSucceeded(s) ? s : true));
4766
5073
  } catch {}
4767
5074
  if (setStyleRefOp) run(setStyleRefOp, {
@@ -4772,7 +5079,7 @@ function createSuperDocUI(options) {
4772
5079
  if (!rangeTarget && readDocumentMode() !== "suggesting") {
4773
5080
  const setMarkRunPropsOp = resolveDocOperation(doc, "format.paragraph.setMarkRunProps");
4774
5081
  if (setMarkRunPropsOp) for (const blockId of selectionBlockIds(state.selection)) run(setMarkRunPropsOp, {
4775
- target: paragraphTarget(blockId),
5082
+ target: paragraphTarget(blockId, story),
4776
5083
  markRunProps: {
4777
5084
  bold: false,
4778
5085
  italic: false,
@@ -4790,7 +5097,8 @@ function createSuperDocUI(options) {
4790
5097
  const executeBlockParagraph = (descriptor, op, payload) => {
4791
5098
  const blockIds = selectionBlockIds(state.selection);
4792
5099
  if (blockIds.length === 0) return false;
4793
- return applyPerBlock(descriptor.docRoute, op, blockIds, (blockId) => buildBlockParagraphInput(descriptor.blockParagraph, blockId, payload));
5100
+ const story = selectionStoryLocator(state.selection);
5101
+ return applyPerBlock(descriptor.docRoute, op, blockIds, (blockId) => buildBlockParagraphInput(descriptor.blockParagraph, blockId, payload, story));
4794
5102
  };
4795
5103
  const executeHybridIndentCommand = (descriptor, listOp, blockIds) => {
4796
5104
  const doc = getDoc();
@@ -4798,6 +5106,7 @@ function createSuperDocUI(options) {
4798
5106
  const paragraphClearIndent = resolveDocOperation(doc, "format.paragraph.clearIndentation");
4799
5107
  const mode = descriptor.list?.mode;
4800
5108
  const story = selectionStory(state.selection);
5109
+ const targetStory = selectionStoryLocator(state.selection);
4801
5110
  const planBlock = async (blockId) => {
4802
5111
  const isListItem = await resolveListMembershipForBlockAsync(doc, blockId, story);
4803
5112
  if (isListItem === true) return {
@@ -4807,7 +5116,8 @@ function createSuperDocUI(options) {
4807
5116
  input: { target: listItemTarget(blockId) }
4808
5117
  };
4809
5118
  if (isListItem === null) return null;
4810
- const current = await resolveParagraphIndentationForBlockAsync(doc, blockId);
5119
+ if (targetStory && targetStory.storyType !== "body" && typeof doc?.getNode !== "function") return null;
5120
+ const current = await resolveParagraphIndentationForBlockAsync(doc, blockId, targetStory);
4811
5121
  const preserved = {};
4812
5122
  if (current?.right != null) preserved.right = current.right;
4813
5123
  if (current?.firstLine != null) preserved.firstLine = current.firstLine;
@@ -4819,7 +5129,7 @@ function createSuperDocUI(options) {
4819
5129
  routeToCall: "format.paragraph.setIndentation",
4820
5130
  opToCall: paragraphSetIndent,
4821
5131
  input: {
4822
- target: paragraphTarget(blockId),
5132
+ target: paragraphTarget(blockId, targetStory),
4823
5133
  left: Math.max(0, current?.left ?? 0) + PARAGRAPH_INDENT_STEP_TWIPS,
4824
5134
  ...preserved
4825
5135
  }
@@ -4832,14 +5142,14 @@ function createSuperDocUI(options) {
4832
5142
  kind: "paragraph",
4833
5143
  routeToCall: "format.paragraph.clearIndentation",
4834
5144
  opToCall: paragraphClearIndent,
4835
- input: { target: paragraphTarget(blockId) }
5145
+ input: { target: paragraphTarget(blockId, targetStory) }
4836
5146
  };
4837
5147
  if (paragraphSetIndent) return {
4838
5148
  kind: "paragraph",
4839
5149
  routeToCall: "format.paragraph.setIndentation",
4840
5150
  opToCall: paragraphSetIndent,
4841
5151
  input: {
4842
- target: paragraphTarget(blockId),
5152
+ target: paragraphTarget(blockId, targetStory),
4843
5153
  left: 0
4844
5154
  }
4845
5155
  };
@@ -4850,7 +5160,7 @@ function createSuperDocUI(options) {
4850
5160
  routeToCall: "format.paragraph.setIndentation",
4851
5161
  opToCall: paragraphSetIndent,
4852
5162
  input: {
4853
- target: paragraphTarget(blockId),
5163
+ target: paragraphTarget(blockId, targetStory),
4854
5164
  ...preserved
4855
5165
  }
4856
5166
  };
@@ -4870,9 +5180,16 @@ function createSuperDocUI(options) {
4870
5180
  return false;
4871
5181
  }
4872
5182
  }
5183
+ const nonBodyListPlans = story.storyType !== "body" && plans.some((plan) => plan?.kind === "list");
5184
+ const outdentInStory = nonBodyListPlans && mode === "outdent" ? resolveDocOperation(doc, "lists.outdentInStory") : null;
5185
+ if (nonBodyListPlans && !outdentInStory) return false;
4873
5186
  const settledResults = plans.map((plan) => {
4874
5187
  if (!plan) return Promise.resolve(false);
4875
5188
  try {
5189
+ if (plan.kind === "list" && outdentInStory) return settleOperationResult(callEditorMutation("lists.outdentInStory", outdentInStory, {
5190
+ target: plan.input.target,
5191
+ story
5192
+ })).settled;
4876
5193
  return settleOperationResult(callEditorMutation(plan.routeToCall, plan.opToCall, plan.input)).settled;
4877
5194
  } catch {
4878
5195
  return Promise.resolve(false);
@@ -4887,17 +5204,62 @@ function createSuperDocUI(options) {
4887
5204
  const doc = getDoc();
4888
5205
  const blockIds = selectionBlockIds(state.selection);
4889
5206
  if (blockIds.length === 0) return false;
5207
+ const story = selectionStory(state.selection);
5208
+ const isBodyStory = story.storyType === "body";
4890
5209
  if (spec.mode === "indent" || spec.mode === "outdent") return executeHybridIndentCommand(descriptor, op, blockIds);
4891
5210
  const seed = spec.seed;
4892
- if (readListSeed(doc, state.selection) === seed) {
4893
- const remove = resolveDocOperation(doc, "lists.remove");
4894
- if (!remove) return false;
4895
- return applyPerBlock("lists.remove", remove, blockIds, (blockId) => ({ target: listsBlockTarget(blockId) }));
5211
+ const executeResolved = (states) => {
5212
+ if (states.some((listState) => !listState.resolved)) return {
5213
+ immediate: false,
5214
+ settled: null
5215
+ };
5216
+ const shouldRemove = states.every((listState) => listState.seed === seed);
5217
+ if (!shouldRemove && !isBodyStory) return {
5218
+ immediate: false,
5219
+ settled: null
5220
+ };
5221
+ const route = shouldRemove ? isBodyStory ? "lists.remove" : "lists.removeInStory" : descriptor.docRoute;
5222
+ const operation = shouldRemove ? resolveDocOperation(doc, route) : op;
5223
+ if (!operation) return {
5224
+ immediate: false,
5225
+ settled: null
5226
+ };
5227
+ const immediateResults = [];
5228
+ const settledResults = [];
5229
+ blockIds.forEach((blockId, index) => {
5230
+ if (!shouldRemove && states[index]?.seed === seed) return;
5231
+ const input = shouldRemove ? {
5232
+ target: listsBlockTarget(blockId),
5233
+ ...!isBodyStory ? { story } : {}
5234
+ } : {
5235
+ target: listsBlockTarget(blockId),
5236
+ seed
5237
+ };
5238
+ try {
5239
+ const result$1 = settleOperationResult(callEditorMutation(route, operation, input));
5240
+ immediateResults.push(result$1.immediate);
5241
+ settledResults.push(result$1.settled);
5242
+ } catch {
5243
+ immediateResults.push(false);
5244
+ settledResults.push(Promise.resolve(false));
5245
+ }
5246
+ });
5247
+ return {
5248
+ immediate: combineCommandResults(immediateResults),
5249
+ settled: settledResults.length ? Promise.all(settledResults).then((results) => combineCommandResults(results)) : null
5250
+ };
5251
+ };
5252
+ const stateReads = blockIds.map((blockId) => resolveListSeedForBlock(doc, blockId, story));
5253
+ if (stateReads.some(isPromiseLike)) {
5254
+ pendingCommandSettlement = Promise.all(stateReads).then(async (states) => {
5255
+ const result$1 = executeResolved(states);
5256
+ return result$1.settled ? await result$1.settled : result$1.immediate;
5257
+ });
5258
+ return true;
4896
5259
  }
4897
- return applyPerBlock(descriptor.docRoute, op, blockIds, (blockId) => readListSeedForBlock(doc, blockId) === seed ? null : {
4898
- target: listsBlockTarget(blockId),
4899
- seed
4900
- });
5260
+ const result = executeResolved(stateReads);
5261
+ pendingCommandSettlement = result.settled;
5262
+ return result.immediate;
4901
5263
  };
4902
5264
  function readLinkPayloadRecord(payload) {
4903
5265
  return payload && typeof payload === "object" ? payload : {};
@@ -5104,14 +5466,29 @@ function createSuperDocUI(options) {
5104
5466
  const src = record.src ?? record.value;
5105
5467
  if (typeof src !== "string" || src.trim() === "") return false;
5106
5468
  const caret = collapsedTextAddressFromSelection(state.selection);
5469
+ const story = selectionStory(state.selection);
5470
+ const nonBodyStory = story.storyType === "body" ? null : story;
5471
+ let at;
5472
+ if (caret && typeof caret.blockId === "string") at = {
5473
+ kind: "inParagraph",
5474
+ target: paragraphTarget(caret.blockId, nonBodyStory),
5475
+ offset: caret.range?.start ?? 0
5476
+ };
5477
+ else {
5478
+ at = createLocationAt(state.selection, "target");
5479
+ if (nonBodyStory && at.kind === "after") at = {
5480
+ ...at,
5481
+ target: {
5482
+ ...at.target,
5483
+ story: nonBodyStory
5484
+ }
5485
+ };
5486
+ }
5107
5487
  const input$1 = {
5108
5488
  src,
5109
- at: caret && typeof caret.blockId === "string" ? {
5110
- kind: "inParagraph",
5111
- target: paragraphTarget(caret.blockId),
5112
- offset: caret.range?.start ?? 0
5113
- } : createLocationAt(state.selection, "target")
5489
+ at
5114
5490
  };
5491
+ if (nonBodyStory) input$1.in = at.kind === "inParagraph" ? resolveActiveHeaderFooterSlot(nonBodyStory) ?? nonBodyStory : nonBodyStory;
5115
5492
  if (typeof record.alt === "string") input$1.alt = record.alt;
5116
5493
  if (typeof record.title === "string") input$1.title = record.title;
5117
5494
  if (record.size && typeof record.size === "object") input$1.size = record.size;
@@ -6123,24 +6500,46 @@ function createSuperDocUI(options) {
6123
6500
  }, id, story);
6124
6501
  };
6125
6502
  const executeTrackDecisionTarget = (kind, target, changeId, story) => {
6503
+ try {
6504
+ return settleCommandExecution(callTrackDecisionTarget(kind, target, changeId, story));
6505
+ } catch {
6506
+ return false;
6507
+ }
6508
+ };
6509
+ const callTrackDecisionTarget = (kind, target, changeId, story) => {
6126
6510
  if (reviewMutationsAreReadOnly()) return false;
6127
6511
  const tcApi = getDoc()?.trackChanges;
6512
+ const isAllTarget = target.scope === "all";
6513
+ const isRangeTarget = target.kind === "range";
6128
6514
  if (bulkTrackDecisionBlockedReason({
6129
6515
  kind,
6130
- scope: changeId ? "id" : "all"
6516
+ scope: isAllTarget ? "all" : "id"
6131
6517
  }, tcApi)) return false;
6132
- const legacyName = changeId ? kind : `${kind}All`;
6133
- const op = !story && tcApi && typeof tcApi[legacyName] === "function" ? tcApi[legacyName] : null;
6518
+ const legacyName = isAllTarget ? `${kind}All` : kind;
6519
+ const op = !isRangeTarget && !story && tcApi && typeof tcApi[legacyName] === "function" ? tcApi[legacyName] : null;
6134
6520
  const decide = tcApi && typeof tcApi.decide === "function" ? tcApi.decide : null;
6135
6521
  if (!op && !decide) return false;
6136
- try {
6137
- return settleCommandExecution(op ? op.call(tcApi, changeId ?? {}) : decide.call(tcApi, {
6138
- decision: kind,
6139
- target
6140
- }));
6522
+ return op ? op.call(tcApi, changeId ?? {}) : decide.call(tcApi, {
6523
+ decision: kind,
6524
+ target
6525
+ });
6526
+ };
6527
+ const executeTrackDecisionTargets = (kind, changeIds) => {
6528
+ const immediateResults = [];
6529
+ const settledResults = [];
6530
+ for (const changeId of changeIds) try {
6531
+ const result = settleOperationResult(callTrackDecisionTarget(kind, {
6532
+ kind: "id",
6533
+ id: changeId
6534
+ }, changeId));
6535
+ immediateResults.push(result.immediate);
6536
+ settledResults.push(result.settled);
6141
6537
  } catch {
6142
- return false;
6538
+ immediateResults.push(false);
6539
+ settledResults.push(Promise.resolve(false));
6143
6540
  }
6541
+ pendingCommandSettlement = Promise.all(settledResults).then((results) => combineCommandResults(results));
6542
+ return settleCommandExecution(combineCommandResults(immediateResults));
6144
6543
  };
6145
6544
  const contentControlsSub = sliceHandle((s) => s.contentControls);
6146
6545
  const findContentControl = (id) => {