superdoc 2.4.0-next.12 → 2.4.0-next.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1769,11 +1769,11 @@ img[data-v-c95b2073] {
1769
1769
  pointer-events: auto;
1770
1770
  }
1771
1771
 
1772
- .superdoc[data-v-62293b70] {
1772
+ .superdoc[data-v-25570fd2] {
1773
1773
  display: flex;
1774
1774
  position: relative;
1775
1775
  }
1776
- .sd-visually-hidden[data-v-62293b70] {
1776
+ .sd-visually-hidden[data-v-25570fd2] {
1777
1777
  position: absolute;
1778
1778
  width: 1px;
1779
1779
  height: 1px;
@@ -1784,30 +1784,30 @@ img[data-v-c95b2073] {
1784
1784
  white-space: nowrap;
1785
1785
  border: 0;
1786
1786
  }
1787
- .right-sidebar[data-v-62293b70] {
1787
+ .right-sidebar[data-v-25570fd2] {
1788
1788
  min-width: 320px;
1789
1789
  height: 100%;
1790
1790
  }
1791
- .floating-comments[data-v-62293b70] {
1791
+ .floating-comments[data-v-25570fd2] {
1792
1792
  min-width: 300px;
1793
1793
  width: 300px;
1794
1794
  height: 100%;
1795
1795
  overflow: visible;
1796
1796
  }
1797
- .superdoc__layers[data-v-62293b70] {
1797
+ .superdoc__layers[data-v-25570fd2] {
1798
1798
  height: 100%;
1799
1799
  position: relative;
1800
1800
  box-sizing: border-box;
1801
1801
  }
1802
- .superdoc__document[data-v-62293b70] {
1802
+ .superdoc__document[data-v-25570fd2] {
1803
1803
  width: 100%;
1804
1804
  position: relative;
1805
1805
  }
1806
- .superdoc__sub-document[data-v-62293b70] {
1806
+ .superdoc__sub-document[data-v-25570fd2] {
1807
1807
  width: 100%;
1808
1808
  position: relative;
1809
1809
  }
1810
- .superdoc__selection-layer[data-v-62293b70] {
1810
+ .superdoc__selection-layer[data-v-25570fd2] {
1811
1811
  position: absolute;
1812
1812
  min-width: 100%;
1813
1813
  min-height: 100%;
@@ -1818,13 +1818,13 @@ img[data-v-c95b2073] {
1818
1818
  /* SD-3497: PDF whiteboard overlay sits above the rendered PDF canvas but below
1819
1819
  the PDF comment anchors (z-index 6 in PdfCommentsLayer) so anchors stay
1820
1820
  clickable, and below the selection layer (z-index 10). */
1821
- .superdoc__whiteboard-layer[data-v-62293b70] {
1821
+ .superdoc__whiteboard-layer[data-v-25570fd2] {
1822
1822
  z-index: 4;
1823
1823
  }
1824
- .superdoc__temp-selection[data-v-62293b70] {
1824
+ .superdoc__temp-selection[data-v-25570fd2] {
1825
1825
  position: absolute;
1826
1826
  }
1827
- .superdoc__right-sidebar[data-v-62293b70] {
1827
+ .superdoc__right-sidebar[data-v-25570fd2] {
1828
1828
  width: 320px;
1829
1829
  min-width: 320px;
1830
1830
  padding: 0 10px;
@@ -1832,7 +1832,7 @@ img[data-v-c95b2073] {
1832
1832
  position: relative;
1833
1833
  z-index: 2;
1834
1834
  }
1835
- .superdoc__compact-comment-popover[data-v-62293b70] {
1835
+ .superdoc__compact-comment-popover[data-v-25570fd2] {
1836
1836
  position: absolute;
1837
1837
  top: 12px;
1838
1838
  right: 12px;
@@ -1841,14 +1841,14 @@ img[data-v-c95b2073] {
1841
1841
  }
1842
1842
 
1843
1843
  /* Tools styles */
1844
- .tools[data-v-62293b70] {
1844
+ .tools[data-v-25570fd2] {
1845
1845
  position: absolute;
1846
1846
  z-index: 3;
1847
1847
  display: flex;
1848
1848
  flex-direction: column;
1849
1849
  gap: var(--sd-ui-tools-gap, 6px);
1850
1850
  }
1851
- .tools-item[data-v-62293b70] {
1851
+ .tools-item[data-v-25570fd2] {
1852
1852
  display: flex;
1853
1853
  align-items: center;
1854
1854
  justify-content: center;
@@ -1859,10 +1859,10 @@ img[data-v-c95b2073] {
1859
1859
  cursor: pointer;
1860
1860
  position: relative;
1861
1861
  }
1862
- .tools-item i[data-v-62293b70] {
1862
+ .tools-item i[data-v-25570fd2] {
1863
1863
  cursor: pointer;
1864
1864
  }
1865
- .superdoc__tools-icon[data-v-62293b70] {
1865
+ .superdoc__tools-icon[data-v-25570fd2] {
1866
1866
  width: var(--sd-ui-tools-icon-size, 20px);
1867
1867
  height: var(--sd-ui-tools-icon-size, 20px);
1868
1868
  flex-shrink: 0;
@@ -1877,22 +1877,22 @@ img[data-v-c95b2073] {
1877
1877
 
1878
1878
  /* 834px is iPad screen size in portrait orientation */
1879
1879
  @media (max-width: 834px) {
1880
- .superdoc .superdoc__layers[data-v-62293b70] {
1880
+ .superdoc .superdoc__layers[data-v-25570fd2] {
1881
1881
  margin: 0;
1882
1882
  border: 0 !important;
1883
1883
  box-shadow: none;
1884
1884
  }
1885
- .superdoc__sub-document[data-v-62293b70] {
1885
+ .superdoc__sub-document[data-v-25570fd2] {
1886
1886
  max-width: 100%;
1887
1887
  }
1888
- .superdoc__right-sidebar[data-v-62293b70] {
1888
+ .superdoc__right-sidebar[data-v-25570fd2] {
1889
1889
  padding: 10px;
1890
1890
  position: relative;
1891
1891
  }
1892
1892
  }
1893
1893
 
1894
1894
  /* AI Writer styles */
1895
- .ai-writer-container[data-v-62293b70] {
1895
+ .ai-writer-container[data-v-25570fd2] {
1896
1896
  position: fixed;
1897
1897
  z-index: 1000;
1898
1898
  background: white;
@@ -1908,10 +1908,10 @@ img[data-v-c95b2073] {
1908
1908
  transform: translateY(-50%);
1909
1909
  z-index: 50;
1910
1910
  } */
1911
- .ai-tool > svg[data-v-62293b70] {
1911
+ .ai-tool > svg[data-v-25570fd2] {
1912
1912
  fill: transparent;
1913
1913
  }
1914
- .ai-tool[data-v-62293b70]::before {
1914
+ .ai-tool[data-v-25570fd2]::before {
1915
1915
  content: '';
1916
1916
  position: absolute;
1917
1917
  width: 20px;
@@ -1932,7 +1932,7 @@ img[data-v-c95b2073] {
1932
1932
  filter: brightness(1.2);
1933
1933
  transition: filter 0.2s ease;
1934
1934
  }
1935
- .ai-tool[data-v-62293b70]:hover::before {
1935
+ .ai-tool[data-v-25570fd2]:hover::before {
1936
1936
  filter: brightness(1.3);
1937
1937
  }
1938
1938
 
@@ -30,6 +30,9 @@ interface SuperDocFormattingMarksPayload {
30
30
  interface SuperDocDocumentModeChangePayload {
31
31
  documentMode: DocumentMode;
32
32
  }
33
+ interface SuperDocCommentsListChangePayload {
34
+ isRendered: boolean;
35
+ }
33
36
  interface SuperDocPaginationPayload {
34
37
  totalPages: number;
35
38
  superdoc: SuperDoc;
@@ -51,6 +54,7 @@ interface SuperDocEventMap {
51
54
  editorDestroy: [];
52
55
  'pdf:document-ready': [];
53
56
  'sidebar-toggle': [boolean];
57
+ 'comments-list-change': [SuperDocCommentsListChangePayload];
54
58
  /** Requests the shell open its find/replace surface (e.g. the toolbar search button). */
55
59
  'search:open': [];
56
60
  zoomChange: [SuperDocZoomPayload];
@@ -25,8 +25,16 @@ export type { BrowserDocumentApi } from '../../public/browser-document-api.js';
25
25
  export interface ToolbarDropdownOption {
26
26
  /** Row text, and the default command argument when the row is chosen. */
27
27
  label?: string;
28
- /** Stable row identity, used for selection state. */
29
- key?: string;
28
+ /**
29
+ * Stable row identity, used for selection state and handed to the command.
30
+ *
31
+ * Numbers included, because the built-in zoom dropdown uses them
32
+ * (`key: 0.5`, `key: 1`) and the runtime passes the value through
33
+ * unchanged. This is what a `command` callback reads as `context.option`,
34
+ * so a string-only declaration here made a numeric key unusable at the far
35
+ * end even once the config side accepted it.
36
+ */
37
+ key?: string | number;
30
38
  /** Attributes spread onto the rendered row. */
31
39
  props?: Record<string, unknown>;
32
40
  [key: string]: unknown;
@@ -56,37 +64,350 @@ export interface ToolbarCustomButtonContext extends CustomCommandContext {
56
64
  argument?: unknown;
57
65
  }
58
66
  /**
59
- * A custom entry appended to the built-in toolbar's default item set.
60
- *
61
- * Intentionally loose. `name` and `command` are the two members the runtime
62
- * contract already fixes, and the rest stays open because the public
63
- * toolbar-item shape has not been decided yet (#1098).
67
+ * What a custom toolbar entry does when activated.
64
68
  *
65
- * Tightening it further needs that contract first. `useToolbarItem` accepting
66
- * a `type` only proves construction does not throw, not that the entry renders
67
- * as a top-level toolbar item -- `options` has no render branch at all,
68
- * `dropdown` needs rows and a trigger, and `overflow` draws only when the
69
- * separately-built overflow list is non-empty, which `customButtons` cannot
70
- * populate. Encoding those rules here would publish a contract on the strength
71
- * of constructor behavior, and they need rendered-behavior tests rather than
72
- * direct `useToolbarItem` construction to establish.
73
- */
74
- export interface ToolbarCustomButton {
75
- /** Unique item name. Also derives the registered command id. */
69
+ * A function is registered as a custom command and invoked with
70
+ * {@link ToolbarCustomButtonContext}; a string is read as a canonical V2
71
+ * command id and routed through the shared controller. An unknown id is
72
+ * accepted at compile time and then reported through the toolbar's
73
+ * `exception` event as "Command not handled" -- so the string form stays
74
+ * unnarrowed, but a typo is diagnosed at runtime rather than ignored.
75
+ */
76
+ export type ToolbarCustomButtonCommand = string | ((context: ToolbarCustomButtonContext) => unknown);
77
+ /** Members every custom toolbar entry carries, whatever its type. */
78
+ interface ToolbarCustomEntryBase {
79
+ /**
80
+ * Unique item name, which also derives the registered command id and the
81
+ * rendered `data-item` attribute.
82
+ *
83
+ * Uniqueness is enforced at construction rather than here: a name that
84
+ * repeats, or that matches a built-in item, used to render a second control
85
+ * under the same `data-item` with neither responding.
86
+ */
76
87
  name: string;
88
+ /** Which toolbar group the entry joins. Defaults to `center`. */
89
+ group?: 'left' | 'center' | 'right' | (string & {});
90
+ /** Hover text. */
91
+ tooltip?: string;
92
+ /** Render the entry as unavailable. Honored: a disabled entry does not run. */
93
+ disabled?: boolean;
94
+ /** Extra DOM hooks. Both are read straight onto the rendered control. */
95
+ attributes?: {
96
+ /**
97
+ * Appended to the item's class list. Any value Vue's `class` binding
98
+ * takes: a string, an array, a condition map, or a nesting of those.
99
+ *
100
+ * AIDEV-NOTE: unconstrained for the same reason as `dropdownStyles`, and
101
+ * verified rather than assumed. This lands inside an array binding --
102
+ * `ToolbarButton` renders `:class="['sd-toolbar-item',
103
+ * attributes.className]"` -- so Vue resolves it, and `string` rejected
104
+ * `['compact', { active: isActive }]`, the ordinary way to write a
105
+ * conditional class. There is also nothing to import or mirror:
106
+ * `@vue/shared` types the input as `normalizeClass(value: unknown)`, so
107
+ * Vue does not name this shape either, and any union written here would
108
+ * be narrower than what Vue accepts. A browser test asserts the array and
109
+ * the condition map both resolve, including that a false branch is
110
+ * dropped rather than stringified.
111
+ */
112
+ className?: unknown;
113
+ /** Sets `aria-label`, which is otherwise absent on a custom entry. */
114
+ ariaLabel?: string;
115
+ };
116
+ /** Render the control at reduced width. */
117
+ isNarrow?: boolean;
118
+ /** Render the control at increased width. */
119
+ isWide?: boolean;
120
+ /**
121
+ * `active` and `activeIcon` are rejected by name because both are dead.
122
+ * `useToolbarItem` hard-codes the initial active state to `false` and
123
+ * discards the option, and nothing in the toolbar reads `activeIcon`. Set
124
+ * the state from the command instead, which does work.
125
+ */
126
+ active?: never;
127
+ activeIcon?: never;
128
+ /**
129
+ * Open on purpose. `useToolbarItem` accepts 37 fields and forwards them, and
130
+ * closing this list around the ones I could enumerate rejected seven working
131
+ * configurations in review -- `label`, `hasCaret`, `dropdownValueKey`, row
132
+ * `icon`, `attributes`, `splitButton`, `argument` -- each of which renders or
133
+ * is forwarded by code the enumeration missed.
134
+ *
135
+ * So the guarantees here are structural rather than exhaustive: which `type`
136
+ * values render at all, that a button and a dropdown each have something
137
+ * visible, that a dropdown has rows, and that the two dead fields above are
138
+ * refused. A misspelled rare field still compiles, which is the same
139
+ * trade-off `CommentsConfig` makes for the same reason -- the runtime passes
140
+ * the whole bag through, so a closed type would be wrong more often than a
141
+ * typo is.
142
+ */
143
+ [key: string]: unknown;
144
+ }
145
+ /**
146
+ * A custom button.
147
+ *
148
+ * `icon` is required, which is stricter than the runtime check and deliberately
149
+ * so. Construction accepts `icon` **or** `defaultLabel`, but only the icon
150
+ * reaches the DOM: a button carrying `defaultLabel` alone builds, mounts, and
151
+ * draws nothing, leaving an empty control in the toolbar. Requiring `icon`
152
+ * keeps this type to shapes that produce something a user can see.
153
+ *
154
+ * `label` is not accepted at all. It is the *live* label a built-in item
155
+ * rewrites as state changes, it does not satisfy the affordance check, and a
156
+ * button carrying only `label` is rejected at construction.
157
+ *
158
+ * AIDEV-NOTE: `defaultLabel` renders nothing for a custom button. The check in
159
+ * `use-toolbar-item.js` treats it as an affordance and `ToolbarButton.vue`
160
+ * never draws it. Widening this type to accept `defaultLabel` alone requires
161
+ * fixing that render first, or it re-admits invisible buttons (#1098).
162
+ */
163
+ export interface ToolbarCustomButtonItem extends ToolbarCustomEntryBase {
164
+ type: 'button';
165
+ /** Inline SVG or markup. The only thing a custom button actually renders. */
166
+ icon: string;
167
+ /**
168
+ * Static label kept for the affordance check and for parity with the legacy
169
+ * spelling. It does not render today; pair it with `icon`, never alone.
170
+ */
171
+ defaultLabel?: string;
172
+ /**
173
+ * Visible text drawn beside the icon. Unlike `defaultLabel` this really is
174
+ * rendered, but it does not satisfy the affordance check on its own, so it
175
+ * accompanies `icon` rather than replacing it.
176
+ */
177
+ label?: string;
77
178
  /**
78
- * What the entry does when activated. A function is registered as a custom
79
- * command and invoked with {@link ToolbarCustomButtonContext}; a string is
80
- * read as a canonical V2 command id and routed through the shared controller
81
- * (`built-in-toolbar.js:948`).
179
+ * What the button does. Optional because omitting it renders a control that
180
+ * does nothing rather than failing, which is legal today; an entry meant to
181
+ * be actionable should always carry one.
182
+ */
183
+ command?: ToolbarCustomButtonCommand;
184
+ }
185
+ /**
186
+ * One selectable row in a custom dropdown.
187
+ *
188
+ * Both members are required: `key` is what reaches the command through
189
+ * {@link ToolbarCustomButtonContext.option}, and `label` is the only text the
190
+ * row renders, so a row missing either draws blank or selects as `undefined`.
191
+ */
192
+ interface ToolbarCustomDropdownOptionBase {
193
+ /**
194
+ * Drawn beside the row's label, or a function returning it.
195
+ *
196
+ * Not string-only: `OptionIcon` returns whatever this resolves to straight
197
+ * from a render function, so a Vue VNode works as well as markup.
82
198
  *
83
- * Optional because a separator has nothing to do. An actionable entry that
84
- * omits it silently no-ops on click -- a real defect, but one this type
85
- * cannot flag without the variant split that #1098 owns.
199
+ * `object` rather than `Record<string, unknown>`, which was the first
200
+ * attempt and admitted only inferred object literals -- a value already
201
+ * typed as Vue's `VNode` has no string index signature and so failed to
202
+ * assign. `object` accepts both without importing Vue's types into the
203
+ * public surface.
204
+ */
205
+ icon?: string | object | ((option: ToolbarCustomDropdownOption) => unknown);
206
+ /** Render the row as unavailable. */
207
+ disabled?: boolean;
208
+ /** Added to the row's class list. */
209
+ class?: unknown;
210
+ /**
211
+ * Spread onto the rendered row as attributes, and its `class` is merged
212
+ * with the one above. Declared here as well as on `ToolbarDropdownOption`,
213
+ * which it mirrors.
214
+ */
215
+ props?: Record<string, unknown>;
216
+ /**
217
+ * Open, like the `ToolbarDropdownOption` it mirrors, because
218
+ * `dropdownValueKey` names a member to read dynamically: a row can carry
219
+ * `{ label, key, value }` and send `value` to the command. Closing this
220
+ * would make that shape uncompilable while it still works.
86
221
  */
87
- command?: string | ((context: ToolbarCustomButtonContext) => unknown);
88
222
  [key: string]: unknown;
89
223
  }
224
+ /**
225
+ * One row in a custom dropdown.
226
+ *
227
+ * A selectable row needs both `label` and `key`: `label` is the only text it
228
+ * renders, and `key` is what reaches the command through
229
+ * {@link ToolbarCustomButtonContext.option}.
230
+ *
231
+ * A `type: 'render'` row is the exception and is why this is a union rather
232
+ * than one interface. `ToolbarDropdown` routes those to its `RenderOption`
233
+ * branch and never reads `label` for them, so requiring it would reject rows
234
+ * the runtime supports -- which the first version of this type did.
235
+ */
236
+ export type ToolbarCustomDropdownOption = (ToolbarCustomDropdownOptionBase & {
237
+ /**
238
+ * Application metadata. A row carrying `{ type: 'action', label, key }`
239
+ * stays selectable and reaches the command verbatim, so this member is
240
+ * open.
241
+ *
242
+ * AIDEV-NOTE: that openness means `{ type: 'render', label, key }` with
243
+ * no renderer still matches this branch, so the render branch's
244
+ * required `render` does not catch it (#1098). `Exclude<string,
245
+ * 'render'>` does not help -- subtracting a literal from the wide
246
+ * `string` type leaves `string` -- and neither does a branded
247
+ * intersection, because a plain string literal remains assignable to
248
+ * both. Closing this needs a literal union of the metadata values the
249
+ * product supports, which is a contract decision rather than a
250
+ * transcription. The runtime treats such a row as render-only and draws
251
+ * a blank, inert row.
252
+ */
253
+ type?: string;
254
+ /** Text rendered for the row. */
255
+ label: string;
256
+ /**
257
+ * Value handed to the command when this row is chosen, and the row's
258
+ * Vue key. Numbers are allowed because the built-in zoom dropdown uses
259
+ * them (`key: 0.5`, `key: 1`), and `ButtonGroup` passes the value
260
+ * through to `selectedValue` unchanged.
261
+ */
262
+ key: string | number;
263
+ }) | (ToolbarCustomDropdownOptionBase & {
264
+ /** Rendered through `RenderOption` rather than as a selectable row. */
265
+ type: 'render';
266
+ /**
267
+ * Required, because `RenderOption` returns `null` unless this is
268
+ * callable. A render row is also excluded from selection, so one
269
+ * without a renderer is a permanently blank row that cannot be clicked.
270
+ */
271
+ render: () => unknown;
272
+ label?: string;
273
+ key?: string | number;
274
+ });
275
+ /**
276
+ * A custom dropdown.
277
+ *
278
+ * `options` is required and must be non-empty: a dropdown with no rows, or an
279
+ * empty array, renders no trigger at all, so the entry silently disappears
280
+ * rather than drawing something inert.
281
+ *
282
+ * The trigger rule is looser than a button's -- `label` works here as well as
283
+ * `icon` -- because the dropdown draws its own trigger rather than going
284
+ * through the button affordance check.
285
+ */
286
+ interface ToolbarCustomDropdownBase extends ToolbarCustomEntryBase {
287
+ type: 'dropdown';
288
+ /**
289
+ * Static fallback trigger text. Present for parity with the legacy spelling
290
+ * and does not render, which is why it does not satisfy the trigger
291
+ * requirement below.
292
+ */
293
+ defaultLabel?: string;
294
+ /**
295
+ * The rows the dropdown offers. Empty is accepted, and is not a mistake on
296
+ * its own: `#updateHighlightColors` assigns `nestedOptions` after the item
297
+ * is built, so a dropdown can construct empty and fill in later. An empty
298
+ * one renders no menu rather than breaking -- `ButtonGroup` guards the
299
+ * branch on `nestedOptions.value.length`.
300
+ *
301
+ * AIDEV-NOTE: this was a nonempty tuple until #1188 review. The tuple did
302
+ * reject `options: []`, and it also rejected every array TypeScript cannot
303
+ * see the length of -- `rows.map(...)`, a `Row[]` variable, a function
304
+ * return. Those are how dropdown rows are normally built; `lineHeight` in
305
+ * `default-items.js` builds its own rows with `.map()`. The obvious escape,
306
+ * `readonly T[] & { 0: T }`, rejects all four (measured, not assumed): an
307
+ * array type carries no index-0 property for the intersection to satisfy.
308
+ * So please do not reintroduce a tuple here -- and note there is no runtime
309
+ * check to fall back on either, deliberately, because an empty dropdown is
310
+ * a legitimate intermediate state.
311
+ *
312
+ * `readonly` so an `as const` array is accepted: the runtime only iterates
313
+ * this and copies the elements into `nestedOptions`, never mutating the
314
+ * consumer's array.
315
+ */
316
+ options: readonly ToolbarCustomDropdownOption[];
317
+ /** Draw the dropdown caret beside the trigger. Rendered by `ToolbarButton`. */
318
+ hasCaret?: boolean;
319
+ /**
320
+ * Which member of the selected row becomes the command's `argument`.
321
+ *
322
+ * Defaults to `label`, not `key`: `ButtonGroup.handleSelect` reads
323
+ * `option[dropdownValueKey]` and falls back to `option.label` when this is
324
+ * unset. So a dropdown whose display text differs from its value has to set
325
+ * `'key'` explicitly, or the command receives the text a user sees rather
326
+ * than the value it stands for. Any member name works, not just `key`: a row
327
+ * carrying `{ label, key, value }` can send `value`. `context.option` always
328
+ * carries the whole row either way.
329
+ */
330
+ dropdownValueKey?: string;
331
+ /**
332
+ * Inline styles for the dropdown's own element -- its trigger and wrapper --
333
+ * forwarded unchanged to Vue's `:style` binding.
334
+ *
335
+ * Not the open panel: `ButtonGroup` styles that separately through
336
+ * `menu-props`, which `customButtons` cannot reach. Widths set here size the
337
+ * control in the toolbar, not the menu it opens.
338
+ *
339
+ * AIDEV-NOTE: deliberately unconstrained. Restating Vue's `StyleValue` here
340
+ * was attempted three times and was wrong each time -- string-valued
341
+ * objects rejected `{ padding: 0 }`, object-only rejected
342
+ * `'min-width: 200px'`, and a hand-written union rejected a value already
343
+ * typed as `CSSProperties`, whose index signature a structural restatement
344
+ * does not match. The shape set is Vue's to define and moves with Vue, so
345
+ * mirroring it by hand keeps rejecting working configuration. Importing
346
+ * `StyleValue` would pull a Vue type into the public surface, which this
347
+ * package avoids elsewhere.
348
+ */
349
+ dropdownStyles?: unknown;
350
+ /**
351
+ * `key` of the row to show as selected before the user picks one. Matches
352
+ * the row `key` type, numbers included.
353
+ */
354
+ selectedValue?: string | number;
355
+ /** Invoked with the chosen row on {@link ToolbarCustomButtonContext.option}. */
356
+ command?: ToolbarCustomButtonCommand;
357
+ }
358
+ /**
359
+ * A dropdown needs a trigger a user can see, and it has three ways to draw
360
+ * one. `ToolbarButton` renders `icon` and `label` in its non-split branch and,
361
+ * beside them, `.sd-dropdown-caret` on `v-if="hasCaret"` alone -- so a compact
362
+ * caret-only dropdown is a real control, not an oversight. It renders with a
363
+ * measurable width and opens its rows; only `defaultLabel` draws nothing.
364
+ *
365
+ * Splitting the variant is what makes at-least-one enforceable rather than
366
+ * advisory. Pair a caret-only trigger with `attributes.ariaLabel`, since
367
+ * there is no text for a screen reader to announce.
368
+ */
369
+ export type ToolbarCustomDropdownItem = (ToolbarCustomDropdownBase & {
370
+ icon: string;
371
+ label?: string;
372
+ hasCaret?: boolean;
373
+ }) | (ToolbarCustomDropdownBase & {
374
+ label: string;
375
+ icon?: string;
376
+ hasCaret?: boolean;
377
+ }) | (ToolbarCustomDropdownBase & {
378
+ hasCaret: true;
379
+ icon?: string;
380
+ label?: string;
381
+ });
382
+ /** A visual divider. Renders on its own and has nothing to run. */
383
+ export interface ToolbarCustomSeparatorItem extends ToolbarCustomEntryBase {
384
+ type: 'separator';
385
+ }
386
+ /**
387
+ * A custom entry appended to the built-in toolbar's default item set.
388
+ *
389
+ * What this guarantees is structural, not exhaustive. Each variant fixes the
390
+ * shape of the entry -- which `type` values render at all, that a button and a
391
+ * dropdown each carry something visible, that a dropdown has rows -- while the
392
+ * field list itself stays open, because `useToolbarItem` accepts 37 fields and
393
+ * forwards them. Closing that list rejected eight working configurations
394
+ * during review, and a rejected working config is a worse failure than the
395
+ * autocomplete it buys.
396
+ *
397
+ * Two of the five `useToolbarItem` types are absent because they render
398
+ * nothing: `options` constructs without throwing and `ButtonGroup` has no
399
+ * branch for it, and `overflow` draws only from the separately-built overflow
400
+ * list, which `customButtons` cannot populate.
401
+ *
402
+ * Two fields are refused by name on {@link ToolbarCustomEntryBase} for the
403
+ * same reason: `active` is discarded (`useToolbarItem` hard-codes the initial
404
+ * state to `false`) and `activeIcon` has no toolbar reader at all.
405
+ *
406
+ * Derived from a rendered-behavior survey rather than from the constructor
407
+ * (#1098): construction succeeding proves only that nothing threw, which for
408
+ * this surface was never the same question as whether a control appeared.
409
+ */
410
+ export type ToolbarCustomButton = ToolbarCustomButtonItem | ToolbarCustomDropdownItem | ToolbarCustomSeparatorItem;
90
411
  export type V2AuthoringSelectionCollapse = 'start' | 'end' | null;
91
412
  export type V2AuthoringResult = {
92
413
  ok: true;
@@ -2555,18 +2876,36 @@ export interface SuperDocExceptionEditorPayload {
2555
2876
  code?: string;
2556
2877
  documentId?: string | null;
2557
2878
  }
2879
+ /**
2880
+ * Exception payload raised by the built-in toolbar.
2881
+ *
2882
+ * Emitted for a command that failed and for a custom entry the toolbar could
2883
+ * not build, in which case `itemName` is the entry that was skipped and the
2884
+ * message names the field that would fix it. Reaches the host as well as the
2885
+ * toolbar, because entries are built inside the toolbar constructor and
2886
+ * nothing can have subscribed to the toolbar yet.
2887
+ */
2888
+ export interface SuperDocExceptionToolbarPayload {
2889
+ error: Error;
2890
+ /** The value originally thrown, before it was normalized to an `Error`. */
2891
+ originalError: unknown;
2892
+ /** The toolbar item involved, or `null` when the entry had no usable name. */
2893
+ itemName: string | null;
2894
+ editor?: Editor | null;
2895
+ }
2558
2896
  /**
2559
2897
  * Union of all `exception` event payloads SuperDoc emits at runtime.
2560
- * Consumers can narrow with `'stage' in payload` (store init) or
2561
- * `'code' in payload` (editor lifecycle).
2898
+ * Consumers can narrow with `'stage' in payload` (store init),
2899
+ * `'code' in payload` (editor lifecycle), or `'itemName' in payload`
2900
+ * (built-in toolbar).
2562
2901
  *
2563
- * The union exists today because three independent emit sites
2564
- * (`initializeDocuments`, the restore path, and the editor lifecycle)
2565
- * pre-date a shared error contract. Normalizing them to a single
2566
- * payload shape is a separate follow-up; consumers can narrow with
2902
+ * The union exists today because four independent emit sites
2903
+ * (`initializeDocuments`, the restore path, the editor lifecycle, and the
2904
+ * built-in toolbar) pre-date a shared error contract. Normalizing them to a
2905
+ * single payload shape is a separate follow-up; consumers can narrow with
2567
2906
  * the `in` checks above in the meantime.
2568
2907
  */
2569
- export type SuperDocExceptionPayload = SuperDocExceptionStorePayload | SuperDocExceptionRestorePayload | SuperDocExceptionEditorPayload;
2908
+ export type SuperDocExceptionPayload = SuperDocExceptionStorePayload | SuperDocExceptionRestorePayload | SuperDocExceptionEditorPayload | SuperDocExceptionToolbarPayload;
2570
2909
  /**
2571
2910
  * Zoom mode. `manual` holds whatever value was last set; `fit-width`
2572
2911
  * continuously recomputes the zoom that fits the page width into the
@@ -2754,12 +3093,16 @@ export interface UIConfig {
2754
3093
  */
2755
3094
  fonts?: ToolbarFontOption[];
2756
3095
  /**
2757
- * Custom toolbar buttons appended to the default item set.
3096
+ * Custom toolbar entries appended to the default item set. See
3097
+ * `ToolbarCustomButton` for which shapes render.
2758
3098
  *
2759
- * `name` and the `command` callback are typed; the rest of the
2760
- * `ToolbarItem` shape stays open because it is not yet public (#1098).
3099
+ * `readonly` so an `as const` array is accepted. Without it the array
3100
+ * built in a separate variable had no way through: widening turns each
3101
+ * `type` into `string`, `as const` is the documented answer to that,
3102
+ * and a mutable field then rejected the result. The toolbar only reads
3103
+ * this.
2761
3104
  */
2762
- customButtons?: ToolbarCustomButton[];
3105
+ customButtons?: readonly ToolbarCustomButton[];
2763
3106
  /**
2764
3107
  * Show the formatting marks (pilcrow) button in the toolbar. Off by
2765
3108
  * default. Distinct from `layoutEngineOptions.showFormattingMarks`, which
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Coalesce automatic review-catalog reconciliation behind a genuine typing
3
+ * quiet period. Every typing signal restarts the trailing timer; there is no
4
+ * maximum-wait escape hatch because that would deliberately run the expensive
5
+ * catalog reads in the middle of a sustained input burst.
6
+ */
7
+ export function createV2TypingReviewHydrationScheduler({ hydrate, idleMs, setTimer, clearTimer, }: {
8
+ hydrate: any;
9
+ idleMs?: number | undefined;
10
+ setTimer?: ((callback: any, delay: any) => NodeJS.Timeout) | undefined;
11
+ clearTimer?: ((timer: any) => void) | undefined;
12
+ }): {
13
+ schedule: () => void;
14
+ clear: () => void;
15
+ isPending: () => boolean;
16
+ };
@@ -1,20 +1,20 @@
1
1
  declare const _default: import('vue').DefineComponent<{}, {
2
2
  $emit: (event: "buttonClick" | "textSubmit" | "mainClick", ...args: any[]) => void;
3
3
  active: boolean;
4
- iconColor: string;
5
4
  isNarrow: boolean;
6
5
  isWide: boolean;
7
- toolbarItem: Record<string, any>;
8
6
  defaultLabel: string;
7
+ iconColor: string;
8
+ toolbarItem: Record<string, any>;
9
9
  isOverflowItem: boolean;
10
10
  allowEnterPropagation: boolean;
11
11
  $props: {
12
12
  readonly active?: boolean | undefined;
13
- readonly iconColor?: string | undefined;
14
13
  readonly isNarrow?: boolean | undefined;
15
14
  readonly isWide?: boolean | undefined;
16
- readonly toolbarItem?: Record<string, any> | undefined;
17
15
  readonly defaultLabel?: string | undefined;
16
+ readonly iconColor?: string | undefined;
17
+ readonly toolbarItem?: Record<string, any> | undefined;
18
18
  readonly isOverflowItem?: boolean | undefined;
19
19
  readonly allowEnterPropagation?: boolean | undefined;
20
20
  };