superdoc 2.30.0-next.4 → 2.30.0-next.9

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 (48) hide show
  1. package/dist/chunks/{create-super-doc-ui-CbEDXtpm.es.js → create-super-doc-ui-Cq8trPVC.es.js} +494 -107
  2. package/dist/chunks/{create-super-doc-ui--kQBNTyt.cjs → create-super-doc-ui-FH5GSHph.cjs} +492 -105
  3. package/dist/collaboration-upgrade-engine.cjs +1 -1
  4. package/dist/collaboration-upgrade-engine.es.js +1 -1
  5. package/dist/document-api/src/comments/comment-create-attribution.d.ts +4 -0
  6. package/dist/document-api/src/comments/comments.types.d.ts +4 -0
  7. package/dist/document-api/src/contract/command-catalog.d.ts +1 -1
  8. package/dist/document-api/src/contract/operation-definitions.d.ts +25 -7
  9. package/dist/document-api/src/contract/operation-registry.d.ts +2 -2
  10. package/dist/document-api/src/contract/schemas.d.ts +7 -1
  11. package/dist/document-api/src/index.d.ts +7 -1
  12. package/dist/document-api/src/paragraphs/paragraphs.d.ts +8 -3
  13. package/dist/document-api/src/paragraphs/paragraphs.types.d.ts +49 -5
  14. package/dist/document-api/src/tables/data/table-preset-appearance.generated.d.ts +2 -0
  15. package/dist/document-api/src/tables/table-style-presets.d.ts +73 -0
  16. package/dist/document-api/src/tables/tables.d.ts +4 -1
  17. package/dist/document-api/src/types/sd-props.d.ts +7 -0
  18. package/dist/document-api/src/types/table-operations.types.d.ts +12 -15
  19. package/dist/layout-engine/contracts/src/index.d.ts +1 -0
  20. package/dist/layout-engine/contracts/src/table-cell-vector-overlay.d.ts +2 -0
  21. package/dist/layout-engine/painters/dom/src/index.d.ts +2 -0
  22. package/dist/public/ui-react.cjs +1 -1
  23. package/dist/public/ui-react.es.js +1 -1
  24. package/dist/public/ui-vue.cjs +1 -1
  25. package/dist/public/ui-vue.es.js +1 -1
  26. package/dist/public/ui.cjs +1 -1
  27. package/dist/public/ui.es.js +1 -1
  28. package/dist/style.css +1 -1
  29. package/dist/style.layered.css +1 -1
  30. package/dist/superdoc/src/composables/use-comment-sidebar.d.ts +6 -0
  31. package/dist/superdoc/src/core/config/normalize-interaction-config.d.ts +10 -1
  32. package/dist/superdoc/src/core/editor-runtime/types.d.ts +4 -0
  33. package/dist/superdoc/src/core/types/index.d.ts +39 -0
  34. package/dist/superdoc/src/public/index.d.cts +4 -0
  35. package/dist/superdoc/src/public/index.d.ts +1 -1
  36. package/dist/superdoc/src/public/ui/format-painter-paragraph.d.ts +7 -0
  37. package/dist/superdoc/src/public/ui/format-painter-state.d.ts +5 -9
  38. package/dist/superdoc/src/public/ui/inline-values.d.ts +1 -1
  39. package/dist/superdoc/src/public/ui/reasons.d.ts +1 -0
  40. package/dist/superdoc/src/public/ui/selection-targets.d.ts +2 -0
  41. package/dist/superdoc/src/stores/comments-store.d.ts +21 -9
  42. package/dist/superdoc/src/stores/helpers/review-presentation.d.ts +1 -0
  43. package/dist/superdoc.cjs +239 -60
  44. package/dist/superdoc.es.js +239 -60
  45. package/dist-cdn/style.layered.css +1 -1
  46. package/dist-cdn/superdoc.min.css +1 -1
  47. package/dist-cdn/superdoc.min.js +38 -38
  48. package/package.json +2 -2
@@ -1410,6 +1410,9 @@ new Set(INLINE_PROPERTY_REGISTRY.map((entry) => entry.key));
1410
1410
  var INLINE_PROPERTY_BY_KEY = Object.fromEntries(INLINE_PROPERTY_REGISTRY.map((entry) => [entry.key, entry]));
1411
1411
  INLINE_PROPERTY_REGISTRY.filter((entry) => entry.storage === "mark").map((entry) => entry.key), INLINE_PROPERTY_REGISTRY.filter((entry) => entry.storage === "runAttribute").map((entry) => entry.key);
1412
1412
  //#endregion
1413
+ //#region ../document-api/src/paragraphs/paragraphs.types.ts
1414
+ var PARAGRAPH_BATCH_MAX_TARGETS = 1e3;
1415
+ //#endregion
1413
1416
  //#region src/helpers/tracked-change-comment-lane.ts
1414
1417
  var TRACKED_CHANGE_COMMENT_LANE_ID_PREFIX = "tc-comment:";
1415
1418
  function isSyntheticTrackedChangeCommentLaneItem(item) {
@@ -2972,6 +2975,213 @@ function fontsValue(value) {
2972
2975
  return Object.keys(result).length > 0 ? result : null;
2973
2976
  }
2974
2977
  //#endregion
2978
+ //#region src/public/ui/format-painter-paragraph.ts
2979
+ var BORDER_SIDES = /* @__PURE__ */ new Set([
2980
+ "top",
2981
+ "bottom",
2982
+ "left",
2983
+ "right",
2984
+ "between",
2985
+ "bar"
2986
+ ]);
2987
+ function isBorderSide(side) {
2988
+ return BORDER_SIDES.has(side);
2989
+ }
2990
+ var TAB_ALIGNMENTS = /* @__PURE__ */ new Set([
2991
+ "left",
2992
+ "center",
2993
+ "right",
2994
+ "decimal",
2995
+ "bar",
2996
+ "clear"
2997
+ ]);
2998
+ var TAB_LEADERS = /* @__PURE__ */ new Set([
2999
+ "none",
3000
+ "dot",
3001
+ "hyphen",
3002
+ "underscore",
3003
+ "heavy",
3004
+ "middleDot"
3005
+ ]);
3006
+ function colorValue(value) {
3007
+ if (value == null) return void 0;
3008
+ if (typeof value === "string") return /^(?:#?[\da-f]{6}|auto)$/i.test(value) ? value.replace(/^#/, "") : void 0;
3009
+ if (typeof value !== "object" || !("model" in value)) return void 0;
3010
+ const color = value;
3011
+ if (color.model === "auto") return "auto";
3012
+ return color.model === "rgb" && "value" in color && typeof color.value === "string" && /^[\da-f]{6}$/i.test(color.value) ? color.value : void 0;
3013
+ }
3014
+ function hasOnlyKeys(value, keys) {
3015
+ return Object.keys(value).every((key) => keys.includes(key));
3016
+ }
3017
+ function captureParagraphPaintOperations(props, supports = () => true) {
3018
+ const operations = [];
3019
+ if (props.bidi == null) operations.push({
3020
+ operationId: "format.paragraph.clearDirection",
3021
+ input: {}
3022
+ });
3023
+ else operations.push({
3024
+ operationId: "format.paragraph.setDirection",
3025
+ input: {
3026
+ direction: props.bidi ? "rtl" : "ltr",
3027
+ alignmentPolicy: "preserve"
3028
+ }
3029
+ });
3030
+ const alignment = props.alignment;
3031
+ if (alignment == null) operations.push({
3032
+ operationId: "format.paragraph.clearAlignment",
3033
+ input: {}
3034
+ });
3035
+ else if (alignment === "left" || alignment === "right" || alignment === "center" || alignment === "justify") operations.push({
3036
+ operationId: "format.paragraph.setAlignment",
3037
+ input: { alignment }
3038
+ });
3039
+ const spacing = props.spacing;
3040
+ if (!spacing || hasOnlyKeys(spacing, [
3041
+ "before",
3042
+ "after",
3043
+ "line",
3044
+ "lineRule"
3045
+ ])) {
3046
+ operations.push({
3047
+ operationId: "format.paragraph.clearSpacing",
3048
+ input: {}
3049
+ });
3050
+ if (spacing && [
3051
+ "before",
3052
+ "after",
3053
+ "line",
3054
+ "lineRule"
3055
+ ].some((key) => spacing[key] != null)) {
3056
+ const lineRule = spacing.lineRule === "multiple" ? "auto" : spacing.lineRule ?? "auto";
3057
+ operations.push({
3058
+ operationId: "format.paragraph.setSpacing",
3059
+ input: {
3060
+ ...spacing.before != null ? { before: Math.round(Number(spacing.before) * 20) } : {},
3061
+ ...spacing.after != null ? { after: Math.round(Number(spacing.after) * 20) } : {},
3062
+ ...spacing.line != null ? { line: Math.round(Number(spacing.line) * (lineRule === "auto" ? 240 : 20)) } : {},
3063
+ ...spacing.line != null || spacing.lineRule != null ? { lineRule } : {}
3064
+ }
3065
+ });
3066
+ }
3067
+ }
3068
+ const indent = props.indent;
3069
+ if (!indent || hasOnlyKeys(indent, [
3070
+ "left",
3071
+ "right",
3072
+ "firstLine",
3073
+ "hanging"
3074
+ ]) && !(indent.firstLine != null && indent.hanging != null)) {
3075
+ operations.push({
3076
+ operationId: "format.paragraph.clearIndentation",
3077
+ input: {}
3078
+ });
3079
+ if (indent && Object.keys(indent).length) operations.push({
3080
+ operationId: "format.paragraph.setIndentation",
3081
+ input: Object.fromEntries(Object.entries(indent).filter(([, value]) => value != null).map(([key, value]) => [key, Math.round(Number(value) * 20)]))
3082
+ });
3083
+ }
3084
+ operations.push({
3085
+ operationId: "format.paragraph.setKeepOptions",
3086
+ input: {
3087
+ keepNext: props.keepWithNext ?? null,
3088
+ keepLines: props.keepLines ?? null,
3089
+ widowControl: props.widowControl ?? null
3090
+ }
3091
+ });
3092
+ operations.push({
3093
+ operationId: "format.paragraph.setFlowOptions",
3094
+ input: {
3095
+ contextualSpacing: props.contextualSpacing ?? null,
3096
+ pageBreakBefore: props.pageBreakBefore ?? null
3097
+ }
3098
+ });
3099
+ operations.push({
3100
+ operationId: "format.paragraph.setOutlineLevel",
3101
+ input: { outlineLevel: props.outlineLevel ?? null }
3102
+ });
3103
+ const borders = props.borders;
3104
+ if (!borders || Object.entries(borders).every(([side, border]) => BORDER_SIDES.has(side) && hasOnlyKeys(border, [
3105
+ "style",
3106
+ "color",
3107
+ "width",
3108
+ "space"
3109
+ ]) && typeof border.style === "string" && (border.color == null || colorValue(border.color) !== void 0))) {
3110
+ operations.push({
3111
+ operationId: "format.paragraph.clearBorder",
3112
+ input: { side: "all" }
3113
+ });
3114
+ for (const side of Object.keys(borders ?? {})) {
3115
+ if (!isBorderSide(side)) continue;
3116
+ const border = borders?.[side];
3117
+ if (!border || typeof border.style !== "string") continue;
3118
+ operations.push({
3119
+ operationId: "format.paragraph.setBorder",
3120
+ input: {
3121
+ side,
3122
+ style: border.style,
3123
+ ...border.color != null ? { color: colorValue(border.color) } : {},
3124
+ ...border.width != null ? { size: Math.round(Number(border.width) * 8) } : {},
3125
+ ...border.space != null ? { space: border.space } : {}
3126
+ }
3127
+ });
3128
+ }
3129
+ }
3130
+ const shading = props.shading;
3131
+ if (!shading || hasOnlyKeys(shading, [
3132
+ "fill",
3133
+ "color",
3134
+ "pattern"
3135
+ ]) && ["fill", "color"].every((key) => shading[key] == null || colorValue(shading[key]) !== void 0)) {
3136
+ operations.push({
3137
+ operationId: "format.paragraph.clearShading",
3138
+ input: {}
3139
+ });
3140
+ if (shading && Object.keys(shading).length) operations.push({
3141
+ operationId: "format.paragraph.setShading",
3142
+ input: {
3143
+ ...shading.fill != null ? { fill: colorValue(shading.fill) } : {},
3144
+ ...shading.color != null ? { color: colorValue(shading.color) } : {},
3145
+ ...shading.pattern != null ? { pattern: shading.pattern } : {}
3146
+ }
3147
+ });
3148
+ }
3149
+ const tabs = props.tabs;
3150
+ if (!tabs || tabs.every((tab) => hasOnlyKeys(tab, [
3151
+ "kind",
3152
+ "position",
3153
+ "alignment",
3154
+ "leader"
3155
+ ]) && (tab.kind === "clear" || TAB_ALIGNMENTS.has(String(tab.alignment ?? "left"))) && (tab.leader == null || TAB_LEADERS.has(String(tab.leader))))) {
3156
+ operations.push({
3157
+ operationId: "format.paragraph.clearAllTabStops",
3158
+ input: {}
3159
+ });
3160
+ for (const tab of tabs ?? []) {
3161
+ const alignment = tab.kind === "clear" ? "clear" : tab.alignment ?? "left";
3162
+ const leader = tab.leader;
3163
+ if (alignment === "num" || leader === "line") continue;
3164
+ operations.push({
3165
+ operationId: "format.paragraph.setTabStop",
3166
+ input: {
3167
+ position: Math.round(Number(tab.position) * 20),
3168
+ alignment,
3169
+ ...leader != null ? { leader } : {}
3170
+ }
3171
+ });
3172
+ }
3173
+ }
3174
+ const replacementGroups = [
3175
+ ["clearSpacing", "setSpacing"],
3176
+ ["clearIndentation", "setIndentation"],
3177
+ ["clearBorder", "setBorder"],
3178
+ ["clearShading", "setShading"],
3179
+ ["clearAllTabStops", "setTabStop"]
3180
+ ].map((group) => group.map((name) => `format.paragraph.${name}`));
3181
+ const unavailable = new Set(replacementGroups.filter((group) => operations.some((entry) => group.includes(entry.operationId) && !supports(entry.operationId))).flat());
3182
+ return operations.filter((entry) => supports(entry.operationId) && !unavailable.has(entry.operationId));
3183
+ }
3184
+ //#endregion
2975
3185
  //#region src/public/ui/format-painter-helpers.ts
2976
3186
  /**
2977
3187
  * Maps SDRunProps field names to InlineRunPatch field names.
@@ -3333,6 +3543,7 @@ var SUPERDOC_UI_REASONS = {
3333
3543
  * are never gated by this reason.
3334
3544
  */
3335
3545
  contentControlLocked: "content-control-locked",
3546
+ deletedTextFormattingUnsupported: "deleted-text-formatting-unsupported",
3336
3547
  /**
3337
3548
  * The current user is not allowed to run this command under the product
3338
3549
  * permissionResolver / role matrix (SD-3845). Distinct from document
@@ -4351,6 +4562,10 @@ function getParagraphInlineDirection(attrs) {
4351
4562
  if (ppRtl === false) return "ltr";
4352
4563
  }
4353
4564
  //#endregion
4565
+ //#region ../layout-engine/contracts/src/table-cell-vector-overlay.ts
4566
+ var svgNumber = String.raw`[-+]?(?:\d*\.?\d+)(?:e[-+]?\d+)?`;
4567
+ new RegExp(String.raw`^M\s+${svgNumber}\s+${svgNumber}\s+L\s+${svgNumber}\s+${svgNumber}\s*$`, "iu");
4568
+ //#endregion
4354
4569
  //#region ../layout-engine/contracts/src/drawing-taxonomy.ts
4355
4570
  /**
4356
4571
  * Frozen canonical diagnostic codes for drawing rendering.
@@ -4650,7 +4865,7 @@ function readEffectiveInlineUniformityStates(raw) {
4650
4865
  states[key] = require__plugin_vue_export_helper.isPlainObject(entry) && entry.state === "uniform" && typeof entry.value === "string" ? {
4651
4866
  state: "uniform",
4652
4867
  value: entry.value
4653
- } : { state: require__plugin_vue_export_helper.isPlainObject(entry) && entry.state === "mixed" ? "mixed" : "unresolvable" };
4868
+ } : { state: require__plugin_vue_export_helper.isPlainObject(entry) && (entry.state === "mixed" || entry.state === "absent") ? entry.state : "unresolvable" };
4654
4869
  }
4655
4870
  return states;
4656
4871
  }
@@ -5751,6 +5966,19 @@ function resolveInlineSelectionTarget(selection) {
5751
5966
  if (fallback && fallback.start.kind === "text" && fallback.end.kind === "text" && fallback.start.blockId !== fallback.end.blockId) return null;
5752
5967
  return fallback;
5753
5968
  }
5969
+ function selectionHasOnlyDeletedText(selection) {
5970
+ const target = selection.target;
5971
+ const selected = selection.selectionTarget;
5972
+ return !selection.empty && selected?.kind === "selection" && selected.coordinateSpace === "tracked" && target?.kind === "text" && target.segments.length > 0 && target.segments.every((segment) => segment.range.start === segment.range.end);
5973
+ }
5974
+ function selectionTouchesDeletedText(selection) {
5975
+ const target = selection.target;
5976
+ if (selection.empty || target?.kind !== "text" || !Array.isArray(target.segments) || target.segments.length === 0) return false;
5977
+ const visibleLength = target.segments.reduce((length, segment) => length + Math.abs(segment.range.end - segment.range.start), 0);
5978
+ const selected = selection.selectionTarget;
5979
+ const trackedSegments = selection.trackedTarget?.segments;
5980
+ return ((Array.isArray(trackedSegments) ? trackedSegments.reduce((length, segment) => length + Math.abs(segment.range.end - segment.range.start), 0) : void 0) ?? (selected?.coordinateSpace === "tracked" && selected.start.kind === "text" && selected.end.kind === "text" && selected.start.blockId === selected.end.blockId ? Math.abs(selected.end.offset - selected.start.offset) : 0)) > visibleLength;
5981
+ }
5754
5982
  //#endregion
5755
5983
  //#region src/public/ui/tracked-change-ids.ts
5756
5984
  function identityText(value) {
@@ -8945,6 +9173,11 @@ function createSuperDocUI(options) {
8945
9173
  const scope = listSelectionScope(selection);
8946
9174
  return [...pendingListRequests].some((request) => !request.cancelled && request.editor === getEditor() && request.doc === getEditor()?.doc && request.mode === readDocumentMode() && request.kind === kind && (request.scope == null || scope == null || request.scope === scope));
8947
9175
  };
9176
+ const inlineFormattingRefused = (descriptor, selection) => {
9177
+ if (!descriptor.inline) return false;
9178
+ if (selectionHasOnlyDeletedText(selection)) return true;
9179
+ return descriptor.inline.kind === "clear" && selectionTouchesDeletedText(selection);
9180
+ };
8948
9181
  const computeCommandState = (id, doc, selection, projectedInlineValues, lockModesById = /* @__PURE__ */ new Map()) => {
8949
9182
  if (customCommands.has(id)) return normalizeCommandState({
8950
9183
  supported: true,
@@ -9021,6 +9254,12 @@ function createSuperDocUI(options) {
9021
9254
  supported: false,
9022
9255
  reason: descriptor.reason ?? SUPERDOC_UI_REASONS.commandUnsupported
9023
9256
  }, descriptor.disposition === "unsupported" ? "unsupported" : "builtin");
9257
+ if (inlineFormattingRefused(descriptor, selection)) return normalizeCommandState({
9258
+ enabled: false,
9259
+ active: false,
9260
+ supported: true,
9261
+ reason: readonly ? SUPERDOC_UI_REASONS.documentReadonly : SUPERDOC_UI_REASONS.deletedTextFormattingUnsupported
9262
+ }, "builtin");
9024
9263
  if (descriptor.instanceRoute) return computeInstanceCommandState(descriptor);
9025
9264
  if (descriptor.table) return computeTableCommandState(descriptor, doc, readonly);
9026
9265
  if (descriptor.inline?.kind === "clear") {
@@ -11460,27 +11699,26 @@ function createSuperDocUI(options) {
11460
11699
  setPendingInlineFormatOnHost(method, value);
11461
11700
  return true;
11462
11701
  };
11463
- const executeClearFormattingCommand = () => {
11464
- const doc = getDoc();
11465
- const { lockModesById } = computeActiveContentControlLockModes(doc?.contentControls, state.selection);
11466
- if (contentControlLockReason(lockModesById)) return false;
11702
+ const acceptUnchangedReset = (result) => require__plugin_vue_export_helper.isPlainObject(result) && result.success === false && require__plugin_vue_export_helper.isPlainObject(result.failure) && result.failure.code === "NO_OP" ? true : result;
11703
+ const applyClearFormattingCommand = (doc) => {
11467
11704
  const immediateResults = [];
11468
11705
  const settledResults = [];
11469
- const record = (result) => {
11706
+ const record = (result, unchangedResetIsSuccess = false) => {
11470
11707
  const settled = settleOperationResult(result);
11471
- immediateResults.push(settled.immediate);
11472
- settledResults.push(settled.settled);
11708
+ immediateResults.push(unchangedResetIsSuccess ? acceptUnchangedReset(settled.immediate) : settled.immediate);
11709
+ settledResults.push(unchangedResetIsSuccess ? settled.settled.then(acceptUnchangedReset) : settled.settled);
11473
11710
  };
11474
- const run = (op, input, options) => {
11711
+ const run = (op, input, unchangedResetIsSuccess = false) => {
11475
11712
  try {
11476
- record(options ? op(input, options) : op(input));
11713
+ record(op(input), unchangedResetIsSuccess);
11477
11714
  } catch {
11478
11715
  immediateResults.push(false);
11479
11716
  settledResults.push(Promise.resolve(false));
11480
11717
  }
11481
11718
  };
11482
11719
  const defaultStyleId = getStyleCatalog().cache?.full.defaults.paragraphStyleId ?? DEFAULT_PARAGRAPH_STYLE_ID;
11483
- const rangeTarget = resolveInlineCommandTarget(state.selection);
11720
+ const selectedTarget = resolveInlineCommandTarget(state.selection);
11721
+ const rangeTarget = selectedTarget?.coordinateSpace === "tracked" && !currentCellSelectionTarget() && !selectionTouchesDeletedText(state.selection) ? selectionTargetFromTextTarget(state.selection.target) ?? selectedTarget : selectedTarget;
11484
11722
  if (rangeTarget) {
11485
11723
  const inlineOp = resolveDocOperation(doc, "format.apply");
11486
11724
  if (inlineOp) try {
@@ -11500,7 +11738,7 @@ function createSuperDocUI(options) {
11500
11738
  const listsRemoveOp = resolveDocOperation(doc, isBodyStory ? "lists.remove" : "lists.removeInStory");
11501
11739
  for (const blockId of selectionBlockIds(state.selection)) {
11502
11740
  const target = paragraphTarget(blockId, story);
11503
- if (resetOp) run(resetOp, { target });
11741
+ if (resetOp) run(resetOp, { target }, true);
11504
11742
  if (listsRemoveOp) try {
11505
11743
  const listTarget = listsBlockTarget(blockId);
11506
11744
  const r = settleOperationResult(listsRemoveOp(isBodyStory ? { target: listTarget } : {
@@ -11532,6 +11770,47 @@ function createSuperDocUI(options) {
11532
11770
  pendingCommandSettlement = settledResults.length ? Promise.all(settledResults).then((results) => combineCommandResults(results)) : null;
11533
11771
  return combineCommandResults(immediateResults.length ? immediateResults : [false]);
11534
11772
  };
11773
+ const executeClearFormattingCommand = () => {
11774
+ const doc = getDoc();
11775
+ const { lockModesById } = computeActiveContentControlLockModes(doc?.contentControls, state.selection);
11776
+ if (contentControlLockReason(lockModesById)) return false;
11777
+ const editor = getEditor();
11778
+ const sourceDoc = editor?.doc;
11779
+ const reset = resolveDocOperation(doc, "format.paragraph.resetDirectFormatting");
11780
+ const blockIds = selectionBlockIds(state.selection);
11781
+ if (editor?.editorVersion !== 2 || !reset || blockIds.length === 0) return applyClearFormattingCommand(doc);
11782
+ const sourceToken = contentToken(), mode = readDocumentMode(), selectionToken = selectionKey(state.selection);
11783
+ const story = selectionStoryLocator(state.selection);
11784
+ const normalize = (result) => acceptUnchangedReset(commandResultFromOperationResult(result));
11785
+ const checks = blockIds.map((blockId) => {
11786
+ try {
11787
+ const result = reset({ target: paragraphTarget(blockId, story) }, { dryRun: true });
11788
+ return isPromiseLike(result) ? Promise.resolve(result).then(normalize, () => false) : normalize(result);
11789
+ } catch {
11790
+ return false;
11791
+ }
11792
+ });
11793
+ const isCurrent = () => !disposed && getEditor() === editor && editor?.doc === sourceDoc && contentToken() === sourceToken && readDocumentMode() === mode && selectionKey(state.selection) === selectionToken;
11794
+ if (!checks.some(isPromiseLike)) {
11795
+ const result = combineCommandResults(checks.map(commandResultFromOperationResult));
11796
+ if (!commandResultSucceeded(result)) return result;
11797
+ return isCurrent() ? applyClearFormattingCommand(doc) : false;
11798
+ }
11799
+ pendingCommandSettlement = Promise.all(checks).then((results) => {
11800
+ const result = combineCommandResults(results);
11801
+ if (!commandResultSucceeded(result)) return result;
11802
+ if (!isCurrent()) return false;
11803
+ const previousSettlement = pendingCommandSettlement;
11804
+ pendingCommandSettlement = null;
11805
+ try {
11806
+ const immediate = applyClearFormattingCommand(doc);
11807
+ return pendingCommandSettlement ?? immediate;
11808
+ } finally {
11809
+ pendingCommandSettlement = previousSettlement;
11810
+ }
11811
+ });
11812
+ return true;
11813
+ };
11535
11814
  /** Route a block-level paragraph command (`format.paragraph.*` / `styles.paragraph.*`). */
11536
11815
  const executeBlockParagraph = (descriptor, op, payload) => {
11537
11816
  const blockIds = selectionBlockIds(state.selection);
@@ -12220,6 +12499,7 @@ function createSuperDocUI(options) {
12220
12499
  if (id === "copy-format") return documentIsReadOnly() ? false : settleCommandExecution(executeCopyFormat());
12221
12500
  const descriptor = getCommandDescriptor(id);
12222
12501
  if (!descriptor || descriptor.disposition !== "routed") return false;
12502
+ if (inlineFormattingRefused(descriptor, preparedSelection ?? state.selection)) return false;
12223
12503
  const normalized = descriptor.normalizePayload ? descriptor.normalizePayload(payload) : payload;
12224
12504
  if (descriptor.instanceRoute) {
12225
12505
  if (!getEditor()) return false;
@@ -12513,11 +12793,20 @@ function createSuperDocUI(options) {
12513
12793
  }
12514
12794
  let paragraphSnapshot = null;
12515
12795
  const paragraphSegments = formatPainterParagraphSegments(segments, sourceWholeCells);
12516
- if (paragraphSegments.length === 1) {
12796
+ if (paragraphSegments.length > 0) {
12517
12797
  const segment = paragraphSegments[0];
12518
12798
  const node = (await readBlockNode(paragraphTarget(segment.blockId, sourceStory)))?.["node"] ?? null;
12519
12799
  if (node && getParagraphLikeData(node)) paragraphSnapshot = await extractParagraphSnapshot(node, segment.blockId, doc, sourceStory);
12520
12800
  }
12801
+ if (paragraphSnapshot?.styleId && effectiveInput) {
12802
+ const authored = await readEffectiveInlineUniformityNow({
12803
+ ...effectiveInput,
12804
+ keys: ["fontFamily", "fontSize"],
12805
+ includeCaret: true,
12806
+ includeDefaultFloor: false
12807
+ });
12808
+ paragraphSnapshot.inheritedFontKeys = ["fontFamily", "fontSize"].filter((key) => authored?.[key]?.state === "absent");
12809
+ }
12521
12810
  return {
12522
12811
  story: sourceStory ?? null,
12523
12812
  inline: inlinePatch,
@@ -12570,15 +12859,7 @@ function createSuperDocUI(options) {
12570
12859
  const para = getParagraphLikeData(node);
12571
12860
  const props = para["props"] ?? {};
12572
12861
  const styleId = para["styleRef"] ?? null;
12573
- const alignment = props["alignment"] ?? null;
12574
- const spacing = props["spacing"] ?? null;
12575
- const indent = props["indent"] ?? null;
12576
- const indentation = indent ? {
12577
- ...indent["left"] != null ? { left: indent["left"] } : {},
12578
- ...indent["right"] != null ? { right: indent["right"] } : {},
12579
- ...indent["firstLine"] != null ? { firstLine: indent["firstLine"] } : {},
12580
- ...indent["hanging"] != null ? { hanging: indent["hanging"] } : {}
12581
- } : null;
12862
+ const operations = captureParagraphPaintOperations(props, (operationId) => Boolean(resolveDocOperation(doc, operationId)));
12582
12863
  const markRunProps = props["markRunProps"] ?? null;
12583
12864
  const rawNumId = props["numbering"]?.["numId"];
12584
12865
  const numId = typeof rawNumId === "string" ? parseInt(rawNumId, 10) : typeof rawNumId === "number" ? rawNumId : NaN;
@@ -12596,9 +12877,7 @@ function createSuperDocUI(options) {
12596
12877
  } catch {}
12597
12878
  return {
12598
12879
  styleId,
12599
- alignment,
12600
- spacing,
12601
- indentation,
12880
+ operations,
12602
12881
  markRunProps,
12603
12882
  numbering,
12604
12883
  listStyle
@@ -12680,11 +12959,13 @@ function createSuperDocUI(options) {
12680
12959
  const segments = Array.isArray(selTarget?.segments) ? selTarget.segments : [];
12681
12960
  const targetStory = selTarget?.story;
12682
12961
  const inline = { ...snap.inline };
12962
+ const contiguousTextLengths = /* @__PURE__ */ new Map();
12683
12963
  if (!selectionIsEmpty) {
12684
12964
  for (const segment of formatPainterTextSegments(segments)) {
12685
12965
  const node = (await readBlockNode(paragraphTarget(segment.blockId, targetStory)))?.node;
12686
12966
  const para = node ? getParagraphLikeData(node) : null;
12687
12967
  const runs = Array.isArray(para?.inlines) ? para.inlines : [];
12968
+ if (runs.every((run) => typeof run.run?.text === "string")) contiguousTextLengths.set(segment.blockId, runs.reduce((length, run) => length + run.run.text.length, 0));
12688
12969
  for (const props of sliceRunProps(runs, segment.range)) for (const key of Object.keys(sdRunPropsToInlineRunPatch(props))) {
12689
12970
  const fontAlias = key === "rFonts" ? "fontFamily" : key === "fontFamily" ? "rFonts" : null;
12690
12971
  const capturedFontAlias = fontAlias && (fontAlias in inline || snap.mixedInlineKeys.includes(fontAlias));
@@ -12693,99 +12974,205 @@ function createSuperDocUI(options) {
12693
12974
  }
12694
12975
  if (disposed || painter.snapshot !== snap || getEditor() !== ownerEditor || ownerEditor?.doc !== ownerDoc || readDocumentMode() !== ownerMode) return;
12695
12976
  }
12696
- if (!selectionIsEmpty && Object.keys(inline).length > 0) {
12697
- const inlineOp = resolveDocOperation(doc, "format.apply");
12698
- if (inlineOp && selectionTarget) try {
12699
- if (!commandResultSucceeded(commandResultFromOperationResult(await Promise.resolve(callInlineFormatMutation("format.apply", inlineOp, {
12977
+ const entries = [];
12978
+ const options = editorMutationOptionsForRoute("format.apply");
12979
+ if (options?.success === false) return;
12980
+ const inlineOptions = {
12981
+ ...options,
12982
+ ...tableCellSelection || paintedTarget || selectionTarget?.coordinateSpace === "tracked" ? { offsetSpace: "selection" } : selection.selectionTarget ? { offsetSpace: "kernel" } : {},
12983
+ ...tableCellSelection ? { tableCellSelection } : {}
12984
+ };
12985
+ const add = (operationId, input, entryOptions = options) => {
12986
+ entries.push({
12987
+ operationId,
12988
+ input,
12989
+ ...entryOptions ? { options: entryOptions } : {}
12990
+ });
12991
+ };
12992
+ if (!selectionIsEmpty && Object.keys(inline).length > 0 && selectionTarget && resolveDocOperation(doc, "format.apply")) {
12993
+ const inheritedFontKeys = snap.paragraph?.inheritedFontKeys ?? [];
12994
+ const styledBlocks = new Set(formatPainterParagraphSegments(segments, Boolean(tableCellSelection)).map((segment) => segment.blockId));
12995
+ if (inheritedFontKeys.length > 0 && styledBlocks.size > 0 && !tableCellSelection) {
12996
+ const textSegments = formatPainterTextSegments(segments);
12997
+ const original = selectionTarget;
12998
+ const start = original.start;
12999
+ const end = original.end;
13000
+ const first = segments[0];
13001
+ const last = segments.at(-1);
13002
+ if (original.kind === "selection" && start?.kind === "text" && end?.kind === "text" && start.blockId === first?.blockId && start.offset === first?.range.start && end.blockId === last?.blockId && end.offset === last?.range.end && JSON.stringify(original.story ?? null) === JSON.stringify(targetStory ?? null) && new Set(segments.map((segment) => segment.blockId)).size === segments.length && segments.every((segment, index) => segment.range.start >= 0 && segment.range.end >= segment.range.start && (index === 0 || segment.range.start === 0) && (index === segments.length - 1 || contiguousTextLengths.get(segment.blockId) === segment.range.end))) {
13003
+ const styledInline = { ...inline };
13004
+ for (const key of inheritedFontKeys) styledInline[key] = null;
13005
+ const partial = textSegments.at(-1);
13006
+ if (partial && !styledBlocks.has(partial.blockId)) {
13007
+ const markedEnd = textSegments.slice(0, -1).at(-1);
13008
+ if (markedEnd) add("format.apply", {
13009
+ target: {
13010
+ ...original,
13011
+ end: {
13012
+ ...end,
13013
+ blockId: markedEnd.blockId,
13014
+ offset: markedEnd.range.end
13015
+ }
13016
+ },
13017
+ inline: styledInline
13018
+ }, inlineOptions);
13019
+ add("format.apply", {
13020
+ target: {
13021
+ ...original,
13022
+ start: {
13023
+ ...start,
13024
+ blockId: partial.blockId,
13025
+ offset: partial.range.start
13026
+ }
13027
+ },
13028
+ inline
13029
+ }, inlineOptions);
13030
+ } else add("format.apply", {
13031
+ target: selectionTarget,
13032
+ inline: styledInline
13033
+ }, inlineOptions);
13034
+ } else for (const segment of textSegments) {
13035
+ const segmentInline = { ...inline };
13036
+ if (styledBlocks.has(segment.blockId)) for (const key of inheritedFontKeys) segmentInline[key] = null;
13037
+ add("format.apply", {
13038
+ target: {
13039
+ kind: "selection",
13040
+ ...targetStory ? { story: targetStory } : {},
13041
+ start: {
13042
+ kind: "text",
13043
+ blockId: segment.blockId,
13044
+ offset: segment.range.start
13045
+ },
13046
+ end: {
13047
+ kind: "text",
13048
+ blockId: segment.blockId,
13049
+ offset: segment.range.end
13050
+ }
13051
+ },
13052
+ inline: segmentInline
13053
+ }, {
13054
+ ...options,
13055
+ offsetSpace: "kernel"
13056
+ });
13057
+ }
13058
+ } else {
13059
+ if (tableCellSelection && styledBlocks.size > 0) for (const key of inheritedFontKeys) inline[key] = null;
13060
+ add("format.apply", {
12700
13061
  target: selectionTarget,
12701
13062
  inline
12702
- }, selection, tableCellSelection))))) {
12703
- recompute();
12704
- return;
12705
- }
12706
- } catch {
12707
- recompute();
12708
- return;
13063
+ }, inlineOptions);
12709
13064
  }
12710
13065
  }
12711
- if (snap.paragraph) for (const segment of formatPainterParagraphSegments(segments, Boolean(tableCellSelection))) {
12712
- const blockAddress = paragraphTarget(segment.blockId, targetStory);
12713
- const nodeResult = await readBlockNode(blockAddress);
12714
- const node = nodeResult?.["node"] ?? null;
12715
- const nodeAddress = nodeResult?.["address"] ?? null;
12716
- if (!node) continue;
12717
- const target = {
12718
- kind: "block",
12719
- nodeType: nodeAddress?.["nodeType"] ?? node["kind"] ?? "paragraph",
12720
- nodeId: segment.blockId,
12721
- ...targetStory && typeof targetStory === "object" ? { story: targetStory } : {}
12722
- };
12723
- const { styleId, alignment, spacing, indentation, markRunProps, numbering, listStyle } = snap.paragraph;
12724
- const targetProps = getParagraphLikeData(node)?.props;
12725
- if (styleId) await resolveDocOperation(doc, "styles.paragraph.setStyle")?.({
12726
- target,
12727
- styleId
12728
- });
12729
- else if (getParagraphLikeData(node)?.styleRef) await resolveDocOperation(doc, "styles.paragraph.clearStyle")?.({ target });
12730
- if (alignment) await resolveDocOperation(doc, "format.paragraph.setAlignment")?.({
12731
- target,
12732
- alignment
12733
- });
12734
- else if (targetProps?.alignment != null) await resolveDocOperation(doc, "format.paragraph.clearAlignment")?.({ target });
12735
- if (targetProps?.spacing != null) await resolveDocOperation(doc, "format.paragraph.clearSpacing")?.({ target });
12736
- if (targetProps?.indent != null) await resolveDocOperation(doc, "format.paragraph.clearIndentation")?.({ target });
12737
- if (spacing) {
12738
- const sp = spacing;
12739
- const rawLr = sp["lineRule"] ?? "auto";
12740
- const lr = (/* @__PURE__ */ new Set([
12741
- "auto",
12742
- "exact",
12743
- "atLeast"
12744
- ])).has(rawLr) ? rawLr : "auto";
12745
- const rawSpacing = {
12746
- ...sp["before"] != null ? { before: Math.round(sp["before"] * 20) } : {},
12747
- ...sp["after"] != null ? { after: Math.round(sp["after"] * 20) } : {},
12748
- ...sp["line"] != null ? {
12749
- line: Math.round(lr === "auto" ? sp["line"] * 240 : sp["line"] * 20),
12750
- lineRule: lr
12751
- } : {}
12752
- };
12753
- await resolveDocOperation(doc, "format.paragraph.setSpacing")?.({
12754
- target,
12755
- ...rawSpacing
12756
- });
12757
- }
12758
- if (indentation) {
12759
- const rawIndentation = {
12760
- ...indentation.left != null ? { left: pointsToTwips(indentation.left) } : {},
12761
- ...indentation.right != null ? { right: pointsToTwips(indentation.right) } : {},
12762
- ...indentation.firstLine != null ? { firstLine: pointsToTwips(indentation.firstLine) } : {},
12763
- ...indentation.hanging != null ? { hanging: pointsToTwips(indentation.hanging) } : {}
13066
+ if (snap.paragraph) {
13067
+ const batchSupported = (await resolveDocOperation(doc, "capabilities.get")?.())?.operations?.["format.paragraph.applyBatch"]?.available === true && Boolean(resolveDocOperation(doc, "format.paragraph.applyBatch"));
13068
+ const batchTargets = [];
13069
+ const { styleId, operations, markRunProps, numbering, listStyle } = snap.paragraph;
13070
+ const batchOperations = [
13071
+ ...styleId ? [{
13072
+ operationId: "styles.paragraph.setStyleRef",
13073
+ input: { styleId }
13074
+ }] : [{
13075
+ operationId: "styles.paragraph.clearStyle",
13076
+ input: {}
13077
+ }],
13078
+ ...operations,
13079
+ ...markRunProps ? [{
13080
+ operationId: "format.paragraph.setMarkRunProps",
13081
+ input: { markRunProps }
13082
+ }] : []
13083
+ ];
13084
+ for (const segment of formatPainterParagraphSegments(segments, Boolean(tableCellSelection))) {
13085
+ const blockAddress = paragraphTarget(segment.blockId, targetStory);
13086
+ const nodeResult = await readBlockNode(blockAddress);
13087
+ const node = nodeResult?.node;
13088
+ const para = node ? getParagraphLikeData(node) : null;
13089
+ if (!para) continue;
13090
+ const target = {
13091
+ ...blockAddress,
13092
+ nodeType: (nodeResult?.address)?.nodeType ?? node?.kind ?? "paragraph"
12764
13093
  };
12765
- await resolveDocOperation(doc, "format.paragraph.setIndentation")?.({
13094
+ if (batchSupported) batchTargets.push(target);
13095
+ else {
13096
+ if (styleId) add("styles.paragraph.setStyleRef", {
13097
+ target,
13098
+ styleId
13099
+ });
13100
+ else if (para.styleRef) add("styles.paragraph.clearStyle", { target });
13101
+ for (const operation of operations) add(operation.operationId, {
13102
+ target,
13103
+ ...operation.input
13104
+ });
13105
+ if (markRunProps) add("format.paragraph.setMarkRunProps", {
13106
+ target,
13107
+ markRunProps
13108
+ });
13109
+ }
13110
+ if (numbering) add("format.paragraph.setNumbering", {
12766
13111
  target,
12767
- ...rawIndentation
13112
+ ...numbering
12768
13113
  });
12769
- }
12770
- if (markRunProps) await resolveDocOperation(doc, "format.paragraph.setMarkRunProps")?.({
12771
- target,
12772
- markRunProps
12773
- });
12774
- if (numbering) await resolveDocOperation(doc, "format.paragraph.setNumbering")?.({
12775
- target,
12776
- ...numbering
12777
- });
12778
- if (listStyle) {
12779
- if (((await readBlockNode(blockAddress))?.["address"])?.["nodeType"] === "listItem") await resolveDocOperation(doc, "lists.applyStyle")?.({
13114
+ if (listStyle && target.nodeType === "listItem") add("lists.applyStyle", {
12780
13115
  target: listItemTarget(segment.blockId, targetStory),
12781
13116
  style: listStyle
12782
13117
  });
12783
- else if (numbering) await resolveDocOperation(doc, "format.paragraph.setNumbering")?.({
12784
- target,
12785
- ...numbering
13118
+ }
13119
+ if (batchSupported) {
13120
+ if (batchTargets.length * batchOperations.length > 32e3) {
13121
+ recompute();
13122
+ return;
13123
+ }
13124
+ let payloadBytes = 0;
13125
+ for (let index = 0; index < batchTargets.length; index += PARAGRAPH_BATCH_MAX_TARGETS) {
13126
+ const input = {
13127
+ targets: batchTargets.slice(index, index + PARAGRAPH_BATCH_MAX_TARGETS),
13128
+ operations: batchOperations
13129
+ };
13130
+ payloadBytes += new TextEncoder().encode(JSON.stringify(input)).length;
13131
+ if (payloadBytes > 1048576) {
13132
+ recompute();
13133
+ return;
13134
+ }
13135
+ add("format.paragraph.applyBatch", input);
13136
+ }
13137
+ }
13138
+ }
13139
+ if (disposed || painter.snapshot !== snap || getEditor() !== ownerEditor || ownerEditor?.doc !== ownerDoc || readDocumentMode() !== ownerMode || readDocumentMode() === "viewing") return;
13140
+ const currentLocks = computeActiveContentControlLockModes(doc.contentControls, targetSelection);
13141
+ if (contentControlLockReason(currentLocks.lockModesById)) return;
13142
+ try {
13143
+ const execute = resolveDocOperation(doc, "plan.execute");
13144
+ if (entries.length === 1 && entries[0].operationId === "format.apply") {
13145
+ const apply = resolveDocOperation(doc, "format.apply");
13146
+ if (!apply) return;
13147
+ if (!commandResultSucceeded(commandResultFromOperationResult(await apply(entries[0].input, entries[0].options)))) {
13148
+ recompute();
13149
+ return;
13150
+ }
13151
+ } else if (execute) {
13152
+ const result = await execute({
13153
+ atomic: true,
13154
+ entries
12786
13155
  });
13156
+ if (!result || result.failure || result.success === false) {
13157
+ recompute();
13158
+ return;
13159
+ }
13160
+ } else {
13161
+ if (ownerEditor?.editorVersion === 2) return;
13162
+ for (const entry of entries) {
13163
+ const operation = resolveDocOperation(doc, entry.operationId);
13164
+ if (!operation) continue;
13165
+ if (!commandResultSucceeded(commandResultFromOperationResult(entry.options ? await operation(entry.input, entry.options) : await operation(entry.input)))) {
13166
+ recompute();
13167
+ return;
13168
+ }
13169
+ }
12787
13170
  }
13171
+ } catch {
13172
+ recompute();
13173
+ return;
12788
13174
  }
13175
+ if (disposed || painter.snapshot !== snap || getEditor() !== ownerEditor || ownerEditor?.doc !== ownerDoc || readDocumentMode() !== ownerMode) return;
12789
13176
  if (painter.mode === "armed") exitFormatPainter();
12790
13177
  recompute();
12791
13178
  };