import { SuperDocUIReason } from './reasons.js'; /** * The original v2-native command ids. These are the historically stable * `superdoc/ui` built-ins; the broader catalog below adds v1 headless-toolbar * coverage. Kept as a named export because it is part of the public `superdoc/ui` * surface (`verify-public-facade-emit.cjs`, consumer typechecks). */ export declare const BUILT_IN_COMMAND_IDS: { readonly bold: "bold"; readonly italic: "italic"; readonly underline: "underline"; readonly strikethrough: "strikethrough"; readonly fontFamily: "font-family"; readonly fontSize: "font-size"; readonly setFontFamily: "setFontFamily"; readonly setFontSize: "setFontSize"; readonly undo: "undo"; readonly redo: "redo"; readonly acceptChange: "acceptChange"; readonly rejectChange: "rejectChange"; readonly acceptAllChanges: "acceptAllChanges"; readonly rejectAllChanges: "rejectAllChanges"; readonly bulletList: "bullet-list"; readonly numberedList: "numbered-list"; }; /** Every command id built into SuperDoc's UI controller. */ export type BuiltInCommandId = (typeof COMMAND_CATALOG)[number]['id']; /** * How a catalog command is (or is not) backed by a public surface. * * - `routed` → backed by a public surface today. * - `deferred` → recognized v1 workflow the public Document API can back, * intentionally not wired (reports `command-deferred`). * No descriptor uses this in the functional-parity state. * - `context-gap` → a real v2 operation whose required document context * cannot be resolved from public custom-UI state (the * table cell-context family). Fails closed with a precise, * named reason (`table-context-unavailable`), NOT the * generic `command-deferred`. * - `unsupported` → no public v2 equivalent (product decision) or unknown id. */ export type CommandDisposition = 'routed' | 'deferred' | 'context-gap' | 'unsupported'; /** A tracked-change decision a command performs. */ export interface TrackDecisionSpec { kind: 'accept' | 'reject'; scope: 'id' | 'all'; } /** * How an inline-format command builds its public Document API input from the * live selection. * * The v2 public Document API (`activeEditor.doc.format.*`) requires an explicit * `target`/`ref` — it does NOT default to the current selection (see * `@superdoc/document-api` `format.ts` `validateTargetLocator`). The controller * therefore resolves the live selection to a `SelectionTarget` and passes * `{ target, value }`. A command with no resolvable range selection fails * closed with `range-selection-required` rather than calling the operation with * a missing target (which would throw `INVALID_INPUT`). */ export interface InlineFormatSpec { /** Inline run-property key (`bold`, `color`, `highlight`, `fontFamily`, `fontSize`, ...). */ key: string; /** * - `toggle`: boolean mark, value = `!active` so the command flips on/off. * - `value-string`: string value (color, font family) taken from the payload. * - `value-number`: numeric value (font size) taken from the payload. * - `clear`: clear direct inline formatting on the selection via `format.apply`. */ kind: 'toggle' | 'value-string' | 'value-number' | 'clear'; } /** * How a block-level paragraph command builds its `format.paragraph.*` / * `styles.paragraph.*` input from the current paragraph block(s). * * The selection's `blockId` is the same identifier the public block ops accept * as `nodeId` (both resolve to the OOXML `paraId` in the private v2 document-api * adapter write path). The controller * therefore synthesizes a `ParagraphTarget` (`{ kind:'block', * nodeType:'paragraph', nodeId }`) for each block the selection covers and * routes the operation per block. A command with no resolvable current block * fails closed with `selection-required`. */ export interface BlockParagraphSpec { /** * - `alignment`: `format.paragraph.setAlignment({ target, alignment })`. * - `spacing-line`: `format.paragraph.setSpacing({ target, line, lineRule:'auto' })`. * - `style`: `styles.paragraph.setStyle({ target, styleId })`. * - `direction`: `format.paragraph.setDirection({ target, direction })`. */ kind: 'alignment' | 'spacing-line' | 'style' | 'direction'; /** Fixed value for direction commands (`ltr` / `rtl`). */ fixedValue?: string; } /** * How a list command builds its `lists.*` input from the current block. Routes * through `lists.apply` (seed a list), `lists.remove` (toggle off when the * block is already that list kind), or the hybrid indent family. For * `indent` / `outdent`, actual list items use `lists.indent` / * `lists.outdent`, while plain paragraphs fall back to * `format.paragraph.setIndentation` / `clearIndentation` so the legacy toolbar * semantics stay truthful in custom UIs. */ export interface ListCommandSpec { mode: 'toggle-seed' | 'indent' | 'outdent'; /** List kind seeded by `lists.apply` for `toggle-seed`. */ seed?: 'bullet' | 'ordered'; } /** * How a link command routes through `hyperlinks.*`. With a range selection over * non-link text and an href → `hyperlinks.wrap`. With a collapsed target and * text/href → `hyperlinks.insert`. With an active link and a new href → * `hyperlinks.patch`. With an active link and `{ href: null }` → * `hyperlinks.remove` (unwrap). */ export interface LinkCommandSpec { kind: 'link'; } /** * How a create command routes through `create.*`. Insertion location is derived * from the current block (`{ kind:'after', nodeId }`) or defaults to * `documentEnd` when there is no selection. */ export interface CreateCommandSpec { kind: 'table' | 'image' | 'toc'; } /** * How a table cell-context command routes through a `tables.*` operation once * the current table context is resolved. * * The whole `table-*` family routes against the live table context projected by * the shared V2 table-context facade (`host.getTableContext()`, surfaced as * `ui.tables.getContext()`). The controller derives the `TableLocator` inputs * (`nodeId`, `rowIndex` / `columnIndex`, cell locator, merge range) from that * snapshot — never from private editor internals. A command with no resolvable * table context fails closed with `table-context-unavailable`. */ export interface TableCommandSpec { /** Which table operation this command performs against the current context. */ action: 'insert-row-before' | 'insert-row-after' | 'delete-row' | 'insert-column-before' | 'insert-column-after' | 'delete-column' | 'delete-table' | 'merge-cells' | 'split-cell' | 'remove-borders'; /** Document API operation member invoked on `activeEditor.doc.tables`. */ op: string; /** Whether this action needs a resolved cell node (split) rather than table + index. */ requiresCell?: boolean; } /** * Declarative descriptor for a built-in command id. Pure data plus pure * payload-normalizer functions — it holds no controller closures, so the * catalog stays a portable source of truth the controller interprets. */ export interface CommandDescriptor { /** Stable command id (matches the v1 headless-toolbar id where applicable). */ id: string; /** Command family, for documentation / grouping. */ family: string; /** Disposition: routed today, deferred, or unsupported. */ disposition: CommandDisposition; /** Whether running this command mutates the document (read-only guarded). */ mutates: boolean; /** * Canonical id this command is an alias of, when it routes the same public * behavior under a different (e.g. v1 kebab) id. */ aliasOf?: string; /** Other ids that alias this command (documentation). */ aliases?: readonly string[]; /** Dotted path on the public Document API facade (`activeEditor.doc`). */ docRoute?: string; /** Public SuperDoc-instance method this command calls. */ instanceRoute?: string; /** Fixed argument passed to `instanceRoute` instead of the payload. */ fixedArg?: unknown; /** Tracked-change decision this command performs. */ trackDecision?: TrackDecisionSpec; /** Selection mark whose presence marks this command active. */ activeMark?: string; /** * Inline-format routing spec. When present, the command routes through an * inline `format.*` operation against the live selection target rather than * forwarding the raw payload, and requires a non-empty range selection. */ inline?: InlineFormatSpec; /** * Block-level paragraph routing spec. When present, the command resolves the * current paragraph block(s) from the selection and routes a * `format.paragraph.*` / `styles.paragraph.*` operation per block. */ blockParagraph?: BlockParagraphSpec; /** List routing spec (`lists.apply` / `remove` / `indent` / `outdent`). */ list?: ListCommandSpec; /** Hyperlink routing spec (`hyperlinks.wrap` / `patch` / `remove`). */ link?: LinkCommandSpec; /** Create routing spec (`create.table` / `create.image` / `create.tableOfContents`). */ create?: CreateCommandSpec; /** Table cell-context routing spec (`tables.*` against the live table context). */ table?: TableCommandSpec; /** Controller state slice the command's `value` is read from. */ valueFrom?: 'zoom' | 'documentMode' | 'ruler' | 'formattingMarks' | 'measurementUnit'; /** Pure normalizer applied to the payload before routing. */ normalizePayload?: (payload: unknown) => unknown; /** Stable reason for deferred / unsupported commands. */ reason?: SuperDocUIReason; } /** Strip a trailing `pt` unit from v1-style font sizes (`"12pt"` → `"12"`). */ export declare function normalizeFontSizePayload(payload: unknown): unknown; /** * Normalize v1-style zoom payloads to the percentage `SuperDoc.setZoom` * expects (`100 === 100%`). Accepts `150`, `"150%"`, `"1.5"`, or `1.5`. * Percentages pass through as numbers; legacy fraction-style values in the * `0..5` range are converted to percentages (`1.5` → `150`). */ export declare function normalizeZoomPayload(payload: unknown): unknown; /** * Normalize a measurement-unit payload to the `'in'` / `'cm'` form * `SuperDoc.setMeasurementUnit` expects. Accepts the canonical `'in'` / `'cm'`, * a `{ value }` wrapper, or common long forms (`"inches"`, `"centimeters"`). * An unrecognized value passes through so the instance method can reject it. */ export declare function normalizeMeasurementUnitPayload(payload: unknown): unknown; /** * Normalize a v1-style color payload to the `#RRGGBB` form the inline * `format.color` / `format.highlight` operations expect. Accepts `"#RRGGBB"`, * `"RRGGBB"`, or `{ value }`. `null` (clear) passes through unchanged; an * unrecognized value is returned as-is so the operation can reject it. */ export declare function normalizeColorPayload(payload: unknown): unknown; /** Normalize a v1 alignment payload (`"left"`, `{ value }`, `"justified"`) to a `ParagraphAlignment`. */ export declare function normalizeAlignmentPayload(payload: unknown): unknown; /** * Normalize a v1 line-height payload to OOXML auto line spacing (240ths of a * line: 240 = single, 360 = 1.5×, 480 = double). A multiplier in the `0..10` * range is converted (`1.5` → `360`); a larger raw value passes through. */ export declare function normalizeLineHeightPayload(payload: unknown): unknown; /** Preserve semantic style intent; normalize legacy linked-style payloads to a concrete ID. */ export declare function normalizeStyleIdPayload(payload: unknown): unknown; /** Resolve a document-mode payload (`"viewing"` or `{ mode }`) to the mode string. */ export declare function normalizeDocumentModePayload(payload: unknown): unknown; /** * The built-in command catalog. Order is documentation-only. Every v1 * headless-toolbar id appears here with an explicit disposition; the 14 * canonical v2 ids route today, the kebab track-change selection ids and the * zoom / document-mode ids are newly routed in this unit, and the remaining v1 * workflows are deferred or marked product-unsupported with a stable reason. */ export declare const COMMAND_CATALOG: readonly [{ readonly id: "bold"; readonly family: "inline marks"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "format.bold"; readonly activeMark: "bold"; readonly inline: { readonly key: "bold"; readonly kind: "toggle"; }; }, { readonly id: "italic"; readonly family: "inline marks"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "format.italic"; readonly activeMark: "italic"; readonly inline: { readonly key: "italic"; readonly kind: "toggle"; }; }, { readonly id: "underline"; readonly family: "inline marks"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "format.underline"; readonly activeMark: "underline"; readonly inline: { readonly key: "underline"; readonly kind: "toggle"; }; }, { readonly id: "strikethrough"; readonly family: "inline marks"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "format.strikethrough"; readonly activeMark: "strikethrough"; readonly inline: { readonly key: "strike"; readonly kind: "toggle"; }; }, { readonly id: "font-family"; readonly family: "fonts"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "format.fontFamily"; readonly aliases: readonly ["setFontFamily"]; readonly inline: { readonly key: "fontFamily"; readonly kind: "value-string"; }; }, { readonly id: "font-size"; readonly family: "fonts"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "format.fontSize"; readonly aliases: readonly ["setFontSize"]; readonly normalizePayload: typeof normalizeFontSizePayload; readonly inline: { readonly key: "fontSize"; readonly kind: "value-number"; }; }, { readonly id: "setFontFamily"; readonly family: "fonts"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "format.fontFamily"; readonly aliasOf: "font-family"; readonly inline: { readonly key: "fontFamily"; readonly kind: "value-string"; }; }, { readonly id: "setFontSize"; readonly family: "fonts"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "format.fontSize"; readonly aliasOf: "font-size"; readonly normalizePayload: typeof normalizeFontSizePayload; readonly inline: { readonly key: "fontSize"; readonly kind: "value-number"; }; }, { readonly id: "undo"; readonly family: "history"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "history.undo"; }, { readonly id: "redo"; readonly family: "history"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "history.redo"; }, { readonly id: "acceptChange"; readonly family: "tracked changes"; readonly disposition: "routed"; readonly mutates: true; readonly trackDecision: { readonly kind: "accept"; readonly scope: "id"; }; readonly aliases: readonly ["track-changes-accept-selection"]; }, { readonly id: "rejectChange"; readonly family: "tracked changes"; readonly disposition: "routed"; readonly mutates: true; readonly trackDecision: { readonly kind: "reject"; readonly scope: "id"; }; readonly aliases: readonly ["track-changes-reject-selection"]; }, { readonly id: "acceptAllChanges"; readonly family: "tracked changes (bulk)"; readonly disposition: "routed"; readonly mutates: true; readonly trackDecision: { readonly kind: "accept"; readonly scope: "all"; }; }, { readonly id: "rejectAllChanges"; readonly family: "tracked changes (bulk)"; readonly disposition: "routed"; readonly mutates: true; readonly trackDecision: { readonly kind: "reject"; readonly scope: "all"; }; }, { readonly id: "track-changes-accept-selection"; readonly family: "tracked changes"; readonly disposition: "routed"; readonly mutates: true; readonly trackDecision: { readonly kind: "accept"; readonly scope: "id"; }; readonly aliasOf: "acceptChange"; }, { readonly id: "track-changes-reject-selection"; readonly family: "tracked changes"; readonly disposition: "routed"; readonly mutates: true; readonly trackDecision: { readonly kind: "reject"; readonly scope: "id"; }; readonly aliasOf: "rejectChange"; }, { readonly id: "zoom"; readonly family: "zoom"; readonly disposition: "routed"; readonly mutates: false; readonly instanceRoute: "setZoom"; readonly valueFrom: "zoom"; readonly normalizePayload: typeof normalizeZoomPayload; }, { readonly id: "zoom-fit-width"; readonly family: "zoom"; readonly disposition: "routed"; readonly mutates: false; readonly instanceRoute: "setZoomMode"; readonly fixedArg: "fit-width"; readonly valueFrom: "zoom"; }, { readonly id: "document-mode"; readonly family: "document"; readonly disposition: "routed"; readonly mutates: false; readonly instanceRoute: "setDocumentMode"; readonly valueFrom: "documentMode"; readonly normalizePayload: typeof normalizeDocumentModePayload; }, { readonly id: "measurement-unit"; readonly family: "measurement"; readonly disposition: "routed"; readonly mutates: false; readonly instanceRoute: "setMeasurementUnit"; readonly valueFrom: "measurementUnit"; readonly normalizePayload: typeof normalizeMeasurementUnitPayload; }, { readonly id: "text-color"; readonly family: "color"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "format.color"; readonly inline: { readonly key: "color"; readonly kind: "value-string"; }; readonly normalizePayload: typeof normalizeColorPayload; }, { readonly id: "highlight-color"; readonly family: "color"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "format.highlight"; readonly inline: { readonly key: "highlight"; readonly kind: "value-string"; }; readonly normalizePayload: typeof normalizeColorPayload; }, { readonly id: "link"; readonly family: "links"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "hyperlinks.wrap"; readonly activeMark: "link"; readonly link: { readonly kind: "link"; }; }, { readonly id: "text-align"; readonly family: "paragraph"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "format.paragraph.setAlignment"; readonly blockParagraph: { readonly kind: "alignment"; }; readonly normalizePayload: typeof normalizeAlignmentPayload; }, { readonly id: "line-height"; readonly family: "paragraph"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "format.paragraph.setSpacing"; readonly blockParagraph: { readonly kind: "spacing-line"; }; readonly normalizePayload: typeof normalizeLineHeightPayload; }, { readonly id: "linked-style"; readonly family: "paragraph"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "styles.paragraph.setStyle"; readonly blockParagraph: { readonly kind: "style"; }; readonly normalizePayload: typeof normalizeStyleIdPayload; }, { readonly id: "bullet-list"; readonly family: "lists"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "lists.apply"; readonly list: { readonly mode: "toggle-seed"; readonly seed: "bullet"; }; }, { readonly id: "numbered-list"; readonly family: "lists"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "lists.apply"; readonly list: { readonly mode: "toggle-seed"; readonly seed: "ordered"; }; }, { readonly id: "indent-increase"; readonly family: "lists/indent"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "lists.indent"; readonly list: { readonly mode: "indent"; }; }, { readonly id: "indent-decrease"; readonly family: "lists/indent"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "lists.outdent"; readonly list: { readonly mode: "outdent"; }; }, { readonly id: "direction-ltr"; readonly family: "direction"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "format.paragraph.setDirection"; readonly blockParagraph: { readonly kind: "direction"; readonly fixedValue: "ltr"; }; }, { readonly id: "direction-rtl"; readonly family: "direction"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "format.paragraph.setDirection"; readonly blockParagraph: { readonly kind: "direction"; readonly fixedValue: "rtl"; }; }, { readonly id: "clear-formatting"; readonly family: "formatting"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "format.apply"; readonly inline: { readonly key: "*"; readonly kind: "clear"; }; }, { readonly id: "image"; readonly family: "media"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "create.image"; readonly create: { readonly kind: "image"; }; }, { readonly id: "table-of-contents-insert"; readonly family: "TOC"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "create.tableOfContents"; readonly create: { readonly kind: "toc"; }; }, { readonly id: "table-insert"; readonly family: "tables"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "create.table"; readonly create: { readonly kind: "table"; }; }, { readonly id: "table-add-row-before"; readonly family: "tables"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "tables.insertRow"; readonly table: { readonly action: "insert-row-before"; readonly op: "insertRow"; }; readonly reason: "table-context-unavailable"; }, { readonly id: "table-add-row-after"; readonly family: "tables"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "tables.insertRow"; readonly table: { readonly action: "insert-row-after"; readonly op: "insertRow"; }; readonly reason: "table-context-unavailable"; }, { readonly id: "table-delete-row"; readonly family: "tables"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "tables.deleteRow"; readonly table: { readonly action: "delete-row"; readonly op: "deleteRow"; }; readonly reason: "table-context-unavailable"; }, { readonly id: "table-add-column-before"; readonly family: "tables"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "tables.insertColumn"; readonly table: { readonly action: "insert-column-before"; readonly op: "insertColumn"; }; readonly reason: "table-context-unavailable"; }, { readonly id: "table-add-column-after"; readonly family: "tables"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "tables.insertColumn"; readonly table: { readonly action: "insert-column-after"; readonly op: "insertColumn"; }; readonly reason: "table-context-unavailable"; }, { readonly id: "table-delete-column"; readonly family: "tables"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "tables.deleteColumn"; readonly table: { readonly action: "delete-column"; readonly op: "deleteColumn"; }; readonly reason: "table-context-unavailable"; }, { readonly id: "table-delete"; readonly family: "tables"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "tables.delete"; readonly table: { readonly action: "delete-table"; readonly op: "delete"; }; readonly reason: "table-context-unavailable"; }, { readonly id: "table-merge-cells"; readonly family: "tables"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "tables.mergeCells"; readonly table: { readonly action: "merge-cells"; readonly op: "mergeCells"; }; readonly reason: "table-context-unavailable"; }, { readonly id: "table-split-cell"; readonly family: "tables"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "tables.splitCell"; readonly table: { readonly action: "split-cell"; readonly op: "splitCell"; readonly requiresCell: true; }; readonly reason: "table-context-unavailable"; }, { readonly id: "table-remove-borders"; readonly family: "tables"; readonly disposition: "routed"; readonly mutates: true; readonly docRoute: "tables.setBorders"; readonly table: { readonly action: "remove-borders"; readonly op: "setBorders"; }; readonly reason: "table-context-unavailable"; }, { readonly id: "ruler"; readonly family: "UI chrome"; readonly disposition: "routed"; readonly mutates: false; readonly instanceRoute: "toggleRuler"; readonly valueFrom: "ruler"; }, { readonly id: "formatting-marks"; readonly family: "UI chrome"; readonly disposition: "routed"; readonly mutates: false; readonly instanceRoute: "toggleFormattingMarks"; readonly valueFrom: "formattingMarks"; }, { readonly id: "copy-format"; readonly family: "formatting"; readonly disposition: "routed"; readonly mutates: false; }, { readonly id: "table-fix"; readonly family: "tables"; readonly disposition: "unsupported"; readonly mutates: true; readonly reason: "command-unsupported"; }]; /** All built-in command ids known to the controller (routed + deferred + unsupported). */ export declare const ALL_BUILT_IN_COMMAND_IDS: readonly BuiltInCommandId[]; /** Resolve a command descriptor by id, or `null` when the id is unknown. */ export declare function getCommandDescriptor(id: string): CommandDescriptor | null;