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