@bendyline/squisq-editor-react 2.11.16 → 2.11.18

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 (30) hide show
  1. package/dist/{chunk-ZDWZRVAY.js → chunk-2XXBDECJ.js} +1 -1
  2. package/dist/{chunk-DNB32SU2.js → chunk-45NQUR62.js} +1105 -130
  3. package/dist/{chunk-34FRFDQ6.js → chunk-CGENKHWY.js} +58 -102
  4. package/dist/chunk-DNEVUOBZ.js +14 -0
  5. package/dist/chunk-DNJ6HOES.js +93 -0
  6. package/dist/{chunk-EN5BYZOE.js → chunk-DPAMJV6A.js} +3 -2
  7. package/dist/{chunk-5TYCTE33.js → chunk-GIXXD2O3.js} +2189 -1834
  8. package/dist/{chunk-ENDRPV6T.js → chunk-H7YTL6SL.js} +1 -1
  9. package/dist/chunk-LRZJD5YD.js +107 -0
  10. package/dist/{chunk-4D7QMRTZ.js → chunk-U6NVT3BY.js} +466 -540
  11. package/dist/{chunk-WDQFWD7Z.js → chunk-UBFNLWCN.js} +2 -2
  12. package/dist/{chunk-27WOOJXR.js → chunk-WKZLBGPX.js} +1 -1
  13. package/dist/dictationSession-PN3OT5LJ.js +521 -0
  14. package/dist/index.d.ts +36 -21
  15. package/dist/index.js +54 -22
  16. package/dist/json-editor/index.js +3 -3
  17. package/dist/recorder/index.d.ts +2 -2
  18. package/dist/recorder/index.js +17 -13
  19. package/dist/{recorder-C6ArYQBa.d.ts → recorder-CRsw0eC7.d.ts} +6 -5
  20. package/dist/review/index.js +3 -2
  21. package/dist/shell/index.d.ts +2 -1
  22. package/dist/shell/index.js +10 -7
  23. package/dist/{shell-X0EoQbbN.d.ts → shell-Cb0P_ln8.d.ts} +170 -5
  24. package/dist/speech/index.d.ts +126 -0
  25. package/dist/speech/index.js +10 -0
  26. package/dist/styles/index.css +162 -29
  27. package/dist/teleprompter/index.d.ts +13 -5
  28. package/dist/teleprompter/index.js +3 -2
  29. package/dist/{useNarrationStage-DopDr_P5.d.ts → useNarrationStage-vKtk6H-V.d.ts} +7 -1
  30. package/package.json +10 -6
@@ -7,9 +7,10 @@ import { ReactNode, RefObject, ReactElement } from 'react';
7
7
  import { Doc, MediaProvider, ViewportPreset, Theme } from '@bendyline/squisq/schemas';
8
8
  import { FenceRendererMap } from '@bendyline/squisq/fence';
9
9
  import { CodeBlockCopyHandler } from '@bendyline/squisq-react';
10
- import { MarkdownDocument } from '@bendyline/squisq/markdown';
10
+ import { MarkdownSourceEdit, MarkdownDocument } from '@bendyline/squisq/markdown';
11
11
  import { ContentContainer } from '@bendyline/squisq/storage';
12
12
  import { a as ProofingCapability, c as ProofingIgnoreStore } from './types-De4z-gGb.js';
13
+ import { DictationControl, SpeechInputCapability } from './speech/index.js';
13
14
  import { DocumentVersionManager, SaveVersionOptions, SaveVersionResult, PrunePolicy } from '@bendyline/squisq/versions';
14
15
  import { Editor } from '@tiptap/core';
15
16
  import { editor } from 'monaco-editor';
@@ -207,6 +208,102 @@ declare function offsetToLine(source: string, offset: number): number;
207
208
  /** Index of the slice whose range contains `offset`, or -1. */
208
209
  declare function sliceIndexAtOffset(slices: BlockSlice[], offset: number): number;
209
210
 
211
+ /**
212
+ * sourceEdits
213
+ *
214
+ * Pure helpers behind the editor's undoable source-edit actions
215
+ * (`applySourceEdits`, `insertBlockAfterCursor`) and usable by any host that
216
+ * plans edits against a markdown string:
217
+ *
218
+ * - validating and applying `{ start, end, text }` edits to a source string;
219
+ * - the smallest single edit that turns one source into another;
220
+ * - where a new block may go relative to a cursor, without splitting a
221
+ * container template (drawing, layout, diagram) or letting a new heading
222
+ * capture the paragraphs that follow it.
223
+ *
224
+ * Placement is decided on an abstract outline of top-level blocks so the Write
225
+ * view (ProseMirror nodes) and the Source view (markdown AST) share one rule.
226
+ */
227
+
228
+ /**
229
+ * Where `insertBlockAfterCursor` puts a block.
230
+ *
231
+ * - `afterBlock`: straight after the top-level block holding the cursor. Right
232
+ * for fences, tables and paragraphs.
233
+ * - `sectionEnd`: at the end of the cursor's section, just before the next
234
+ * heading. Right for heading-based blocks, which would otherwise capture
235
+ * the paragraphs after them.
236
+ */
237
+ type BlockPlacement = 'afterBlock' | 'sectionEnd';
238
+ /** The heading structure around an insertion point. */
239
+ interface BlockInsertionContext {
240
+ /** Depth of the section the insertion point belongs to; 0 before any heading. */
241
+ sectionDepth: number;
242
+ /** Depth of the first heading after the insertion point, or null at the end. */
243
+ nextHeadingDepth: number | null;
244
+ /**
245
+ * Deepest heading depth a heading-based block may use here. Normally 6;
246
+ * lower straight after a container template, whose children any deeper
247
+ * heading would join.
248
+ */
249
+ maxHeadingDepth: number;
250
+ }
251
+ /** One top-level block, as an outline sees it. */
252
+ interface OutlineBlock {
253
+ /** Heading depth, or null for any non-heading block. */
254
+ headingDepth: number | null;
255
+ /** True for a heading whose template consumes the headings under it. */
256
+ container: boolean;
257
+ }
258
+ /** An insertion point in an outline: insert before `blocks[index]`. */
259
+ interface OutlineInsertion extends BlockInsertionContext {
260
+ index: number;
261
+ }
262
+ /** Validate edits against `source` and sort them by position; null if any is unusable. */
263
+ declare function normalizeSourceEdits(source: string, edits: readonly MarkdownSourceEdit[]): MarkdownSourceEdit[] | null;
264
+ /** Apply edits to `source`. Throws on edits `normalizeSourceEdits` rejects. */
265
+ declare function applySourceEditsToText(source: string, edits: readonly MarkdownSourceEdit[]): string;
266
+ /** The smallest single edit turning `previous` into `next`, or null when they are equal. */
267
+ declare function minimalReplaceEdit(previous: string, next: string): MarkdownSourceEdit | null;
268
+ /**
269
+ * Decide where a block goes in an outline, given the block holding the cursor
270
+ * (`cursorIndex`, -1 when the cursor is before every block).
271
+ *
272
+ * A cursor inside a container template never puts a block among its children:
273
+ * `afterBlock` moves before the container and `sectionEnd` after its subtree.
274
+ */
275
+ declare function resolveOutlineInsertion(blocks: readonly OutlineBlock[], cursorIndex: number, placement: BlockPlacement): OutlineInsertion;
276
+ /**
277
+ * Heading depth for a heading-based block inserted at this point, so the
278
+ * heading that follows closes it instead of being captured, and it does not
279
+ * join a preceding container's children. `childLevels` reserves depth for the
280
+ * block's own child headings (1 for drawing and layout). Null when nothing fits.
281
+ */
282
+ declare function headingDepthForInsertion(context: BlockInsertionContext, options?: {
283
+ childLevels?: number;
284
+ }): number | null;
285
+ /** Top-level blocks of a markdown source with their character ranges. */
286
+ declare function sourceOutline(source: string): {
287
+ blocks: OutlineBlock[];
288
+ ranges: Array<{
289
+ start: number;
290
+ end: number;
291
+ }>;
292
+ };
293
+ /**
294
+ * Where a block goes in a markdown source for a cursor at `cursorOffset`.
295
+ * `offset` is the end of the block the new one follows (or the body start);
296
+ * pass it to {@link joinBlockAt}.
297
+ */
298
+ declare function blockInsertionPoint(source: string, cursorOffset: number, placement: BlockPlacement): BlockInsertionContext & {
299
+ offset: number;
300
+ };
301
+ /**
302
+ * The edit that puts `block` at `offset` with exactly one blank line on each
303
+ * side, absorbing whatever whitespace already surrounds that point.
304
+ */
305
+ declare function joinBlockAt(source: string, offset: number, block: string): MarkdownSourceEdit;
306
+
210
307
  /**
211
308
  * Configuration a Scene host (diagram / drawing / layout widget) supplies
212
309
  * so the shared inline text editor knows how to read and write the text of
@@ -288,6 +385,25 @@ interface DocumentLinkCandidate {
288
385
  */
289
386
  type DocumentLinkProvider = (query: string) => Promise<DocumentLinkCandidate[]>;
290
387
  type EditorView = 'raw' | 'wysiwyg' | 'preview';
388
+ /** Options for {@link EditorActions.applySourceEdits}. */
389
+ interface ApplySourceEditsOptions {
390
+ /**
391
+ * The source the edits were planned against. When the document no longer
392
+ * matches it, nothing is applied and the action returns false.
393
+ */
394
+ baseSource?: string;
395
+ }
396
+ /**
397
+ * Builds the markdown for {@link EditorActions.insertBlockAfterCursor} once the
398
+ * insertion point is known, so a heading-based block can pick a depth that
399
+ * fits there. Return null to cancel.
400
+ */
401
+ type BlockInsertionBuilder = (context: BlockInsertionContext) => string | null;
402
+ /** Options for {@link EditorActions.insertBlockAfterCursor}. */
403
+ interface InsertBlockOptions {
404
+ /** Defaults to `'afterBlock'`. Use `'sectionEnd'` for heading-based blocks. */
405
+ placement?: BlockPlacement;
406
+ }
291
407
  /** What is selected in the active editing surface. */
292
408
  interface EditorSelectionInfo {
293
409
  /** Which surface reported it. */
@@ -525,8 +641,34 @@ interface EditorActions {
525
641
  getBlockAtCursor: () => BlockSlice | null;
526
642
  /** Insert text at the current cursor position in the active editor */
527
643
  insertAtCursor: (text: string) => void;
528
- /** Replace all editor content with the given text */
644
+ /**
645
+ * Replace all editor content with the given text. Undoable as one step in
646
+ * Write and Source views (Document layout); elsewhere it replaces the
647
+ * source directly.
648
+ */
529
649
  replaceAll: (text: string) => void;
650
+ /**
651
+ * Apply `{ start, end, text }` edits to the full markdown source as ONE
652
+ * undoable step. Offsets refer to `markdownSource` (frontmatter included).
653
+ *
654
+ * Source view edits Monaco's model in place, so the cursor, scroll and undo
655
+ * history survive. Write view loads the result as a minimal change.
656
+ * Returns false, applying nothing, in Preview, outside the Document layout,
657
+ * when the edits overlap or fall outside the source, or when the source no
658
+ * longer matches `options.baseSource`.
659
+ */
660
+ applySourceEdits: (edits: readonly MarkdownSourceEdit[], options?: ApplySourceEditsOptions) => boolean;
661
+ /**
662
+ * Insert a markdown block near the cursor as ONE undoable step, leaving the
663
+ * selection where it is.
664
+ *
665
+ * `'afterBlock'` (default) puts it after the top-level block holding the
666
+ * cursor; `'sectionEnd'` puts it just before the next heading. Neither ever
667
+ * lands among a drawing's or layout's child headings. Pass a builder to
668
+ * choose the markdown once the surrounding heading depths are known.
669
+ * Returns false when the surface cannot be edited or the builder cancels.
670
+ */
671
+ insertBlockAfterCursor: (block: string | BlockInsertionBuilder, options?: InsertBlockOptions) => boolean;
530
672
  /**
531
673
  * Request the modal image editor open on the given relative media path.
532
674
  * The path must resolve through the active `mediaProvider`. No-op when
@@ -645,6 +787,13 @@ interface EditorContextValue extends EditorState, EditorActions {
645
787
  * for the session.
646
788
  */
647
789
  proofingIgnoreStore: ProofingIgnoreStore | null;
790
+ /**
791
+ * Imperative dictation control for the host-injected `speechInput`
792
+ * capability — e.g. to drive dictation from a native menu command in a
793
+ * component rendered inside the shell's slots. `null` when no capability
794
+ * was injected. See docs/speech-input.md.
795
+ */
796
+ dictation: DictationControl | null;
648
797
  /** The document's article id — part of the proofing document ref. */
649
798
  articleId: string;
650
799
  /**
@@ -760,6 +909,11 @@ interface EditorProviderProps {
760
909
  proofingGrammarEnabled?: boolean;
761
910
  /** Host-owned per-document storage for dismissed proofing findings. */
762
911
  proofingIgnoreStore?: ProofingIgnoreStore | null;
912
+ /**
913
+ * Speech-input (dictation) capability. Omit for no dictation; see
914
+ * `EditorShellProps.speechInput`.
915
+ */
916
+ speechInput?: SpeechInputCapability | null;
763
917
  /**
764
918
  * Async provider for sibling-document suggestions in the link dialog.
765
919
  * Omit to fall back to URL-only link insertion.
@@ -897,7 +1051,7 @@ interface ViewPreferences {
897
1051
  /** Document vs. block-at-a-time layout. */
898
1052
  layoutMode?: LayoutMode;
899
1053
  }
900
- declare function EditorProvider({ initialMarkdown, initialView, articleId, colorScheme: initialColorScheme, workspaceContainer, allowVersioning, versionBasename, versioningPrunePolicy, versioningAutoSaveIdleMs, onSaveVersion, mediaProvider, mediaEditRenders, imageDisplayMode, mentionProvider, proofing, proofingDefaultEnabled, proofingSpellingEnabled, proofingGrammarEnabled, proofingIgnoreStore, documentLinkProvider, fenceRenderers, onCopyCode, linkSchemes, allowRecording, allowNarrate, preserveSourceWrapping, fileName, saveCoverImageOutput, saveDashboardImageOutput, language, findMode: controlledFindMode, onFindModeChange, inlinePreview, showStatusBar, outline, blockTags, blockTagVisibility: initialBlockTagVisibility, themeInheritance, layoutMode, viewPreferences, onViewPreferencesChange, children, }: EditorProviderProps): react_jsx_runtime.JSX.Element;
1054
+ declare function EditorProvider({ initialMarkdown, initialView, articleId, colorScheme: initialColorScheme, workspaceContainer, allowVersioning, versionBasename, versioningPrunePolicy, versioningAutoSaveIdleMs, onSaveVersion, mediaProvider, mediaEditRenders, imageDisplayMode, mentionProvider, proofing, proofingDefaultEnabled, proofingSpellingEnabled, proofingGrammarEnabled, proofingIgnoreStore, speechInput, documentLinkProvider, fenceRenderers, onCopyCode, linkSchemes, allowRecording, allowNarrate, preserveSourceWrapping, fileName, saveCoverImageOutput, saveDashboardImageOutput, language, findMode: controlledFindMode, onFindModeChange, inlinePreview, showStatusBar, outline, blockTags, blockTagVisibility: initialBlockTagVisibility, themeInheritance, layoutMode, viewPreferences, onViewPreferencesChange, children, }: EditorProviderProps): react_jsx_runtime.JSX.Element;
901
1055
 
902
1056
  /**
903
1057
  * Host-supplied context sections rendered INSIDE the Monaco (raw/code)
@@ -1350,6 +1504,17 @@ interface EditorShellProps {
1350
1504
  * store and Ignore still works, lasting for the session only.
1351
1505
  */
1352
1506
  proofingIgnoreStore?: ProofingIgnoreStore | null;
1507
+ /**
1508
+ * Dictation capability — a `SpeechInputProvider` (or factory) from
1509
+ * `@bendyline/squisq-editor-react/speech`. Omit and the feature is off:
1510
+ * no microphone button, no capture code loaded. With it, the toolbar
1511
+ * gains a microphone in the Write and Source views that inserts
1512
+ * recognized phrases as literal text at the caret; hosts can also drive
1513
+ * it via `useEditorContext().dictation`. Instance = host-owned; factory =
1514
+ * created on mount, disposed on unmount. Ignored when `readOnly`. Host
1515
+ * guide: docs/speech-input.md.
1516
+ */
1517
+ speechInput?: SpeechInputCapability | null;
1353
1518
  /**
1354
1519
  * Optional async provider for sibling-document suggestions in the
1355
1520
  * link insert dialog. When supplied, the dialog gains a "Browse
@@ -1527,7 +1692,7 @@ interface EditorShellProps {
1527
1692
  * Complete markdown editor shell with toolbar, view switcher, and three
1528
1693
  * editing modes: Raw (Monaco), WYSIWYG (Tiptap), and Preview.
1529
1694
  */
1530
- declare function EditorShell({ initialMarkdown, initialView, hostMode, defaultViewportPreset, articleId, basePath, onChange, onLinkClick, showCodeCopyButton, onCopyCode, colorScheme, className, height, minHeight, maxHeight, mediaProvider, mediaEditRenders, workspaceContainer, allowVersioning, versionBasename, versioningPrunePolicy, versioningAutoSaveIdleMs, onSaveVersion, showFilesToggle, showFilesWhenNotEmpty, showFormattingControls, showInsertControls, allowBinaryDownloads, toolbarSlotLeft, toolbarSlotAfterActions, toolbarSlotRight, statusBarSlotRight, sidePanelSlot, reviewProviders, showPlayTab, allowPresentationWindow, allowPresentationFullscreen, allowPrint, submitOnEnter, calcEngineFactory, codeContext, fullWidth, uxFont, thinMargins, writeCanvasSettings, showStatusBar, imageDisplayMode, fileName, saveCoverImageOutput, saveDashboardImageOutput, language, findMode, onFindModeChange, mentionProvider, proofing, proofingDefaultEnabled, proofingSpellingEnabled, proofingGrammarEnabled, proofingIgnoreStore, documentLinkProvider, fenceRenderers, linkSchemes, allowRecording, allowNarrate, preserveSourceWrapping, placeholder, readOnly, imageSrc, imageAlt, imageMode, imageEditorContainer, onImageExport, inlinePreview, inlinePreviewWidth, outline, outlineWidth, blockTags, blockTagVisibility, themeInheritance, viewPreferences, onViewPreferencesChange, themeOverride, }: EditorShellProps): react_jsx_runtime.JSX.Element;
1695
+ declare function EditorShell({ initialMarkdown, initialView, hostMode, defaultViewportPreset, articleId, basePath, onChange, onLinkClick, showCodeCopyButton, onCopyCode, colorScheme, className, height, minHeight, maxHeight, mediaProvider, mediaEditRenders, workspaceContainer, allowVersioning, versionBasename, versioningPrunePolicy, versioningAutoSaveIdleMs, onSaveVersion, showFilesToggle, showFilesWhenNotEmpty, showFormattingControls, showInsertControls, allowBinaryDownloads, toolbarSlotLeft, toolbarSlotAfterActions, toolbarSlotRight, statusBarSlotRight, sidePanelSlot, reviewProviders, showPlayTab, allowPresentationWindow, allowPresentationFullscreen, allowPrint, submitOnEnter, calcEngineFactory, codeContext, fullWidth, uxFont, thinMargins, writeCanvasSettings, showStatusBar, imageDisplayMode, fileName, saveCoverImageOutput, saveDashboardImageOutput, language, findMode, onFindModeChange, mentionProvider, proofing, proofingDefaultEnabled, proofingSpellingEnabled, proofingGrammarEnabled, proofingIgnoreStore, speechInput, documentLinkProvider, fenceRenderers, linkSchemes, allowRecording, allowNarrate, preserveSourceWrapping, placeholder, readOnly, imageSrc, imageAlt, imageMode, imageEditorContainer, onImageExport, inlinePreview, inlinePreviewWidth, outline, outlineWidth, blockTags, blockTagVisibility, themeInheritance, viewPreferences, onViewPreferencesChange, themeOverride, }: EditorShellProps): react_jsx_runtime.JSX.Element;
1531
1696
 
1532
1697
  /**
1533
1698
  * Shared editor context menu.
@@ -1677,4 +1842,4 @@ interface PreviewPanelProps {
1677
1842
  */
1678
1843
  declare function PreviewPanel({ basePath, className, workspaceContainer, onLinkClick, showCodeCopyButton, onCopyCode, }: PreviewPanelProps): react_jsx_runtime.JSX.Element;
1679
1844
 
1680
- export { sliceIndexAtOffset as $, type MentionProvider as A, type BlockRange as B, type CodeContext as C, type DashboardImageSaveOutput as D, type EditorHostMode as E, type FormulaEditRecord as F, type PreviewPanelProps as G, type RawEditorProps as H, type ImageDisplayMode as I, WysiwygEditor as J, type WysiwygEditorProps as K, type LayoutMode as L, type MentionCandidate as M, XLSX_LOCKED_REASON as N, type XlsxFormulaSession as O, PreviewPanel as P, createXlsxFormulaSession as Q, RawEditor as R, type SceneTextChannel as S, type ThemeInheritance as T, getBlockSlices as U, type ViewPreferences as V, type WriteCanvasSettings as W, type XlsxSourceMeta as X, ingestSidecarBytes as Y, lineToOffset as Z, offsetToLine as _, type CalcEngineFactory as a, spliceBlock as a0, useEditorContext as a1, useEditorContextMenuItems as a2, type CsvSourceMeta as b, type BlockSlice as c, type BlockTagVisibility as d, type CodeContextSection as e, type CoverImageSaveOutput as f, type DocumentLinkCandidate as g, type DocumentLinkProvider as h, type EditorActions as i, type EditorColorScheme as j, type EditorContextMenuContext as k, type EditorContextMenuItem as l, type EditorContextMenuItemVisibility as m, EditorContextMenuProvider as n, type EditorContextMenuProviderProps as o, type EditorContextValue as p, type EditorMode as q, EditorProvider as r, type EditorProviderProps as s, type EditorSelectionInfo as t, EditorShell as u, type EditorShellProps as v, type EditorState as w, type EditorView as x, type FormulaSessionOptions as y, type IngestedSidecar as z };
1845
+ export { XLSX_LOCKED_REASON as $, type ApplySourceEditsOptions as A, type BlockInsertionBuilder as B, type CodeContext as C, type DashboardImageSaveOutput as D, type EditorHostMode as E, type FormulaEditRecord as F, type EditorView as G, type FormulaSessionOptions as H, type ImageDisplayMode as I, type IngestedSidecar as J, type InsertBlockOptions as K, type LayoutMode as L, type MentionCandidate as M, type MentionProvider as N, type OutlineBlock as O, type OutlineInsertion as P, PreviewPanel as Q, type PreviewPanelProps as R, type SceneTextChannel as S, RawEditor as T, type RawEditorProps as U, type ThemeInheritance as V, type ViewPreferences as W, type XlsxSourceMeta as X, type WriteCanvasSettings as Y, WysiwygEditor as Z, type WysiwygEditorProps as _, type CalcEngineFactory as a, type XlsxFormulaSession as a0, applySourceEditsToText as a1, blockInsertionPoint as a2, createXlsxFormulaSession as a3, getBlockSlices as a4, headingDepthForInsertion as a5, ingestSidecarBytes as a6, joinBlockAt as a7, lineToOffset as a8, minimalReplaceEdit as a9, normalizeSourceEdits as aa, offsetToLine as ab, resolveOutlineInsertion as ac, sliceIndexAtOffset as ad, sourceOutline as ae, spliceBlock as af, useEditorContext as ag, useEditorContextMenuItems as ah, type CsvSourceMeta as b, type BlockInsertionContext as c, type BlockPlacement as d, type BlockRange as e, type BlockSlice as f, type BlockTagVisibility as g, type CodeContextSection as h, type CoverImageSaveOutput as i, type DocumentLinkCandidate as j, type DocumentLinkProvider as k, type EditorActions as l, type EditorColorScheme as m, type EditorContextMenuContext as n, type EditorContextMenuItem as o, type EditorContextMenuItemVisibility as p, EditorContextMenuProvider as q, type EditorContextMenuProviderProps as r, type EditorContextValue as s, type EditorMode as t, EditorProvider as u, type EditorProviderProps as v, type EditorSelectionInfo as w, EditorShell as x, type EditorShellProps as y, type EditorState as z };
@@ -0,0 +1,126 @@
1
+ /**
2
+ * The speech-input (dictation) capability contract between an editor host and
3
+ * the shell.
4
+ *
5
+ * A host that can turn speech into text passes a {@link SpeechInputProvider}
6
+ * (or a factory) to `EditorShell`'s `speechInput` prop — the same
7
+ * capability-injection semantics as `proofing`: absent means the feature is
8
+ * off, no dictation code loads, and no microphone button renders.
9
+ *
10
+ * The editor owns everything up to the recognizer: microphone capture, phrase
11
+ * segmentation, WAV encoding, the toolbar button, the caret indicator and the
12
+ * literal-text insertion. The provider owns only recognition. Audio crosses
13
+ * the boundary as one self-contained mono 16-bit PCM WAV at 16 kHz per take,
14
+ * which every whisper.cpp build accepts and which keeps IPC payloads small.
15
+ * The surface is plain data end to end, so a provider can be a local engine
16
+ * behind IPC, a call to a service, or a test fake.
17
+ */
18
+ /** Sample rate (Hz) of every WAV take handed to {@link SpeechInputProvider.transcribe}. */
19
+ declare const SPEECH_INPUT_SAMPLE_RATE = 16000;
20
+ /** Coarse readiness of a provider, as the editor's button presents it. */
21
+ type SpeechInputReadinessState =
22
+ /** Dictation can start now. */
23
+ 'ready'
24
+ /**
25
+ * The host needs a one-time setup (typically a model download) first.
26
+ * Clicking the microphone calls {@link SpeechInputProvider.requestSetup}
27
+ * instead of starting.
28
+ */
29
+ | 'download-required'
30
+ /** Dictation can never work in this host/session. The button is hidden. */
31
+ | 'unavailable'
32
+ /**
33
+ * The host still needs a permission (e.g. the OS microphone grant).
34
+ * Starting proceeds — the microphone request is what prompts for it.
35
+ */
36
+ | 'permission-required';
37
+ interface SpeechInputReadiness {
38
+ readonly state: SpeechInputReadinessState;
39
+ /** Optional human-readable detail, shown in the button's tooltip/errors. */
40
+ readonly reason?: string;
41
+ }
42
+ interface SpeechInputTranscribeOptions {
43
+ /**
44
+ * The recent dictated text of this session (a word-aligned tail of at most
45
+ * 1,000 characters), for engines that accept a priming prompt — Whisper's
46
+ * `prompt`. Absent on the first take.
47
+ */
48
+ readonly prompt?: string;
49
+ /** BCP-47-ish language hint. The editor does not currently set it. */
50
+ readonly language?: string;
51
+ /**
52
+ * Aborted when the user cancels dictation or the editor unmounts. A
53
+ * provider should stop promptly; a rejection after abort is ignored.
54
+ */
55
+ readonly signal: AbortSignal;
56
+ }
57
+ interface SpeechInputTranscript {
58
+ /** Recognized text. Blank (or Whisper's `[BLANK_AUDIO]`) means silence. */
59
+ readonly text: string;
60
+ }
61
+ interface SpeechInputProvider {
62
+ /** Stable identifier, e.g. `whisper-local`. */
63
+ readonly id: string;
64
+ /** Human-readable name, e.g. `On-device speech recognition`. */
65
+ readonly label: string;
66
+ /**
67
+ * Current readiness. Called once when the editor mounts with the
68
+ * capability (to decide whether to render the microphone at all) and again
69
+ * each time the user starts dictation. Must be cheap.
70
+ */
71
+ status(): Promise<SpeechInputReadiness>;
72
+ /** Optional readiness push (e.g. a model finished downloading). Returns unsubscribe. */
73
+ onStatus?(listener: (readiness: SpeechInputReadiness) => void): () => void;
74
+ /** Called when the user starts dictation so the host can warm its engine; may reject. */
75
+ prepare?(signal: AbortSignal): Promise<void>;
76
+ /** Called when the user clicks the mic while readiness is 'download-required'; the host shows its own setup UI. */
77
+ requestSetup?(): void;
78
+ /** Transcribe one self-contained take: mono 16-bit PCM WAV at 16 kHz. */
79
+ transcribe(wav: ArrayBuffer, options: SpeechInputTranscribeOptions): Promise<SpeechInputTranscript>;
80
+ /** Release resources. Called by the editor only for a provider it created from a factory. */
81
+ dispose?(): void;
82
+ }
83
+ /**
84
+ * Deferred construction. The shell creates the provider when it mounts with
85
+ * the capability (it needs `status()` to decide whether to render the
86
+ * microphone) and disposes it on unmount or when the capability changes. A
87
+ * host that passes an instance owns its lifetime — which is what lets one warm
88
+ * engine survive shell remounts.
89
+ */
90
+ type SpeechInputProviderFactory = () => SpeechInputProvider;
91
+ /** What the `speechInput` shell prop accepts. */
92
+ type SpeechInputCapability = SpeechInputProvider | SpeechInputProviderFactory;
93
+ /**
94
+ * Resolve a capability to a provider instance. A factory is invoked (and the
95
+ * caller then owns — must dispose — what it returns); an instance is returned
96
+ * as-is and stays host-owned.
97
+ */
98
+ declare function resolveSpeechInputProvider(capability: SpeechInputCapability): SpeechInputProvider;
99
+ /**
100
+ * Whether a capability is a factory, i.e. whether the provider resolved from
101
+ * it is owned (and must be disposed) by the resolver's caller.
102
+ */
103
+ declare function isSpeechInputProviderFactory(capability: SpeechInputCapability): capability is SpeechInputProviderFactory;
104
+ /**
105
+ * Imperative dictation control on the editor context
106
+ * (`useEditorContext().dictation`), for a host component rendered inside the
107
+ * shell's slots — e.g. to drive dictation from a native menu command. `null`
108
+ * when the host injected no `speechInput` capability.
109
+ */
110
+ interface DictationControl {
111
+ /** True from the moment dictation starts until its last take is inserted. */
112
+ readonly active: boolean;
113
+ /**
114
+ * True when `toggle()` would do something: the provider is not
115
+ * `unavailable` and a text-editing view (Write or Source) is showing.
116
+ */
117
+ readonly available: boolean;
118
+ /**
119
+ * Start dictation when idle (or ask the host to set up when readiness is
120
+ * `download-required`); finish and stop when listening; cancel while the
121
+ * microphone is still starting.
122
+ */
123
+ toggle(): void;
124
+ }
125
+
126
+ export { type DictationControl, SPEECH_INPUT_SAMPLE_RATE, type SpeechInputCapability, type SpeechInputProvider, type SpeechInputProviderFactory, type SpeechInputReadiness, type SpeechInputReadinessState, type SpeechInputTranscribeOptions, type SpeechInputTranscript, isSpeechInputProviderFactory, resolveSpeechInputProvider };
@@ -0,0 +1,10 @@
1
+ import {
2
+ SPEECH_INPUT_SAMPLE_RATE,
3
+ isSpeechInputProviderFactory,
4
+ resolveSpeechInputProvider
5
+ } from "../chunk-DNEVUOBZ.js";
6
+ export {
7
+ SPEECH_INPUT_SAMPLE_RATE,
8
+ isSpeechInputProviderFactory,
9
+ resolveSpeechInputProvider
10
+ };
@@ -8145,6 +8145,131 @@
8145
8145
  text-overflow: ellipsis;
8146
8146
  white-space: nowrap;
8147
8147
  }
8148
+ .squisq-dictation {
8149
+ position: relative;
8150
+ display: inline-flex;
8151
+ align-items: center;
8152
+ flex-shrink: 0;
8153
+ }
8154
+ .squisq-dictation-button--active,
8155
+ .squisq-dictation-button--active:hover {
8156
+ background: var(--squisq-danger-soft);
8157
+ color: var(--squisq-danger-strong);
8158
+ }
8159
+ .squisq-dictation-button--listening {
8160
+ width: auto;
8161
+ gap: 6px;
8162
+ padding: 2px 8px 0;
8163
+ }
8164
+ .squisq-dictation-button[aria-busy=true] > i {
8165
+ animation: squisq-dictation-pulse 1.2s ease-in-out infinite;
8166
+ }
8167
+ .squisq-dictation-meter {
8168
+ display: inline-flex;
8169
+ align-items: center;
8170
+ gap: 1px;
8171
+ height: 16px;
8172
+ }
8173
+ .squisq-dictation-meter-bar {
8174
+ width: 2px;
8175
+ min-height: 2px;
8176
+ border-radius: 1px;
8177
+ background: currentColor;
8178
+ transition: height 0.05s linear;
8179
+ }
8180
+ .squisq-dictation-error {
8181
+ position: absolute;
8182
+ top: calc(100% + 6px);
8183
+ right: 0;
8184
+ z-index: 110;
8185
+ display: flex;
8186
+ align-items: flex-start;
8187
+ gap: 8px;
8188
+ width: max-content;
8189
+ max-width: min(320px, calc(100vw - 24px));
8190
+ padding: 8px 8px 8px 12px;
8191
+ border: 1px solid var(--squisq-danger-border);
8192
+ border-radius: 8px;
8193
+ background: var(--squisq-surface);
8194
+ box-shadow: var(--squisq-shadow-md);
8195
+ color: var(--squisq-danger-text);
8196
+ font-size: 12px;
8197
+ font-weight: 500;
8198
+ line-height: 1.4;
8199
+ white-space: normal;
8200
+ }
8201
+ .squisq-dictation-error-text {
8202
+ flex: 1;
8203
+ }
8204
+ .squisq-dictation-error-dismiss {
8205
+ flex-shrink: 0;
8206
+ padding: 0 2px;
8207
+ border: 0;
8208
+ background: transparent;
8209
+ color: var(--squisq-text-muted);
8210
+ cursor: pointer;
8211
+ font-size: 12px;
8212
+ line-height: 1.4;
8213
+ }
8214
+ .squisq-dictation-error-dismiss:hover {
8215
+ color: var(--squisq-text-strong);
8216
+ }
8217
+ .squisq-dictation-interim {
8218
+ display: inline-block;
8219
+ margin-left: 2px;
8220
+ padding: 0 6px;
8221
+ border-radius: 999px;
8222
+ background: var(--squisq-danger-soft);
8223
+ color: var(--squisq-text-muted);
8224
+ font-size: 0.78em;
8225
+ font-style: italic;
8226
+ font-weight: 400;
8227
+ line-height: 1.5;
8228
+ vertical-align: baseline;
8229
+ white-space: nowrap;
8230
+ user-select: none;
8231
+ pointer-events: none;
8232
+ }
8233
+ .squisq-dictation-interim::before {
8234
+ content: "";
8235
+ display: inline-block;
8236
+ width: 6px;
8237
+ height: 6px;
8238
+ margin-right: 5px;
8239
+ border-radius: 50%;
8240
+ background: var(--squisq-danger);
8241
+ vertical-align: 0.1em;
8242
+ animation: squisq-dictation-pulse 1.2s ease-in-out infinite;
8243
+ }
8244
+ .squisq-dictation-interim--source {
8245
+ display: inline;
8246
+ margin-left: 0.5ch;
8247
+ padding: 0;
8248
+ background: transparent;
8249
+ font-size: inherit;
8250
+ line-height: inherit;
8251
+ opacity: 0.8;
8252
+ }
8253
+ .squisq-dictation-interim--source::before {
8254
+ content: none;
8255
+ }
8256
+ @keyframes squisq-dictation-pulse {
8257
+ 0%, 100% {
8258
+ opacity: 1;
8259
+ }
8260
+ 50% {
8261
+ opacity: 0.35;
8262
+ }
8263
+ }
8264
+ @media (prefers-reduced-motion: reduce) {
8265
+ .squisq-dictation-interim::before,
8266
+ .squisq-dictation-button[aria-busy=true] > i {
8267
+ animation: none;
8268
+ }
8269
+ .squisq-dictation-meter-bar {
8270
+ transition: none;
8271
+ }
8272
+ }
8148
8273
 
8149
8274
  /* src/styles/template-picker.css */
8150
8275
  .squisq-mermaid-type-menu {
@@ -8710,35 +8835,6 @@
8710
8835
  height: 100%;
8711
8836
  display: block;
8712
8837
  }
8713
- .squisq-template-gallery-content-preview-fallback {
8714
- display: flex;
8715
- align-items: center;
8716
- justify-content: center;
8717
- width: 100%;
8718
- height: 100%;
8719
- }
8720
- .squisq-template-gallery-content-preview-fallback > svg {
8721
- width: 100%;
8722
- height: 100%;
8723
- display: block;
8724
- }
8725
- .squisq-template-gallery-content-preview-warning {
8726
- position: absolute;
8727
- left: 50%;
8728
- top: 50%;
8729
- max-width: calc(100% - 16px);
8730
- padding: 3px 6px;
8731
- transform: translate(-50%, -50%);
8732
- border: 1px solid var(--squisq-template-gallery-preview-warning-border);
8733
- border-radius: 4px;
8734
- background: var(--squisq-template-gallery-preview-warning-bg);
8735
- color: var(--squisq-template-gallery-preview-warning-text);
8736
- font-size: 10px;
8737
- font-weight: 600;
8738
- line-height: 1.2;
8739
- text-align: center;
8740
- pointer-events: none;
8741
- }
8742
8838
  .squisq-template-gallery-card-body {
8743
8839
  display: flex;
8744
8840
  flex-direction: column;
@@ -15543,6 +15639,43 @@
15543
15639
  .squisq-image-edit-modal__error {
15544
15640
  color: var(--squisq-danger);
15545
15641
  }
15642
+ .squisq-image-placeholder {
15643
+ display: flex;
15644
+ align-items: center;
15645
+ gap: 12px;
15646
+ box-sizing: border-box;
15647
+ width: 260px;
15648
+ max-width: 100%;
15649
+ min-height: 80px;
15650
+ padding: 16px;
15651
+ border: 1px dashed var(--squisq-editor-control-border);
15652
+ border-radius: 8px;
15653
+ background: var(--squisq-editor-control-bg);
15654
+ color: var(--squisq-text-muted);
15655
+ font: 500 13px/1.4 var(--squisq-ux-font, system-ui, sans-serif);
15656
+ user-select: none;
15657
+ }
15658
+ .squisq-image-placeholder svg {
15659
+ flex-shrink: 0;
15660
+ }
15661
+ .squisq-image-placeholder[data-error=true] {
15662
+ border-style: solid;
15663
+ }
15664
+ .squisq-image-placeholder__text {
15665
+ display: flex;
15666
+ flex-direction: column;
15667
+ min-width: 0;
15668
+ }
15669
+ .squisq-image-placeholder__label {
15670
+ overflow: hidden;
15671
+ text-overflow: ellipsis;
15672
+ white-space: nowrap;
15673
+ font-size: 12px;
15674
+ font-weight: 400;
15675
+ }
15676
+ .squisq-image-placeholder--thumbnail {
15677
+ width: 100px;
15678
+ }
15546
15679
 
15547
15680
  /* src/styles/media-edit.css */
15548
15681
  .squisq-media-edit-modal {
@@ -1,9 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { Doc, Theme, MediaProvider } from '@bendyline/squisq/schemas';
3
3
  import { ContentContainer } from '@bendyline/squisq/storage';
4
- import { o as TeleprompterRecordingDeps, g as NarrationStageHandle, T as TeleprompterController, c as FloatingWindowHandle } from '../useNarrationStage-DopDr_P5.js';
5
- export { C as CanvasSink, D as DEFAULT_TELEPROMPTER_PREFS, F as FloatOpenOptions, b as FloatTier, d as FloatingWindowManager, M as MicAnalysisHandle, e as MicAnalysisStatus, N as NarrationRecorderController, f as NarrationRecorderState, h as NarrationTake, P as PrompterTransport, J as TELEPROMPTER_PREF_LIMITS, n as TeleprompterPrefs, q as UseNarrationStageOptions, s as createFloatingWindowManager, t as detectFloatTiers, K as normalizeTeleprompterPrefs, z as useFloatingWindow, B as useMicAnalysis, E as useNarrationRecorder, G as useNarrationStage, H as useTeleprompter, I as vadConfigForSensitivity } from '../useNarrationStage-DopDr_P5.js';
6
- import { NarrationTimingJsonV3, NarrationScript, NarrationAlignment } from '@bendyline/squisq/narration';
4
+ import { o as TeleprompterRecordingDeps, g as NarrationStageHandle, T as TeleprompterController, c as FloatingWindowHandle } from '../useNarrationStage-vKtk6H-V.js';
5
+ export { C as CanvasSink, D as DEFAULT_TELEPROMPTER_PREFS, F as FloatOpenOptions, b as FloatTier, d as FloatingWindowManager, M as MicAnalysisHandle, e as MicAnalysisStatus, N as NarrationRecorderController, f as NarrationRecorderState, h as NarrationTake, P as PrompterTransport, J as TELEPROMPTER_PREF_LIMITS, n as TeleprompterPrefs, q as UseNarrationStageOptions, s as createFloatingWindowManager, t as detectFloatTiers, K as normalizeTeleprompterPrefs, z as useFloatingWindow, B as useMicAnalysis, E as useNarrationRecorder, G as useNarrationStage, H as useTeleprompter, I as vadConfigForSensitivity } from '../useNarrationStage-vKtk6H-V.js';
6
+ import { NarrationTimingJsonV3, NarrationScript, NarrationAlignment, NarrationTimingGenerator } from '@bendyline/squisq/narration';
7
7
  import { ReactNode } from 'react';
8
8
 
9
9
  interface TeleprompterViewProps {
@@ -88,10 +88,18 @@ interface NarrationSavePlanArgs {
88
88
  durationSec: number;
89
89
  audioExt: string;
90
90
  cameraExt: string | null;
91
- baseWpm: number;
91
+ /** Speaking rate the take was paced at; recorded as `generator.baseWpm`. */
92
+ baseWpm?: number;
92
93
  cameraOffsetSec?: number;
93
94
  /** User-chosen filename base for the audio file; default timestamped `narration-…`. */
94
95
  audioBasename?: string;
96
+ /**
97
+ * Provenance written to the sidecar's `generator`. Defaults to the
98
+ * teleprompter aligner (`squisq-teleprompter` / `dsp-align`); a host that
99
+ * produced the timings itself names itself here — e.g.
100
+ * `{ name: 'my-tts', method: 'tts' }` for generated narration.
101
+ */
102
+ generator?: NarrationTimingGenerator;
95
103
  }
96
104
  interface NarrationSavePlan {
97
105
  audioRelativeName: string;
@@ -305,4 +313,4 @@ declare function prompterVarsFromTheme(theme: Theme): Record<string, string>;
305
313
  declare function ensureTeleprompterStyles(doc: Document): void;
306
314
  declare const TELEPROMPTER_CSS = "\n.squisq-teleprompter-root {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 0;\n background: var(--squisq-bg, #f5f5f5);\n outline: none;\n}\n.squisq-teleprompter-stage {\n position: relative;\n flex: 1;\n min-height: 0;\n display: flex;\n}\n\n.squisq-teleprompter-surface {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n background: var(--squisq-prompter-bg, #101014);\n color: var(--squisq-prompter-text, #f5f5f2);\n font-family: var(--squisq-prompter-font, system-ui, sans-serif);\n user-select: none;\n}\n.squisq-teleprompter-flip {\n position: absolute;\n inset: 0;\n}\n.squisq-teleprompter-surface--mirrored .squisq-teleprompter-flip {\n transform: scaleX(-1);\n}\n.squisq-teleprompter-scroll {\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n will-change: transform;\n padding: 40vh 8% 60vh;\n box-sizing: border-box;\n}\n.squisq-teleprompter-line-guide {\n position: absolute;\n left: 0;\n right: 0;\n pointer-events: none;\n z-index: 2;\n}\n.squisq-teleprompter-line-guide::before,\n.squisq-teleprompter-line-guide::after {\n content: '';\n position: absolute;\n top: 50%;\n border: 9px solid transparent;\n transform: translateY(-50%);\n}\n.squisq-teleprompter-line-guide::before {\n left: 6px;\n border-left-color: var(--squisq-prompter-accent, #e8b64c);\n}\n.squisq-teleprompter-line-guide::after {\n right: 6px;\n border-right-color: var(--squisq-prompter-accent, #e8b64c);\n}\n.squisq-teleprompter-guide-band {\n position: absolute;\n left: 0;\n right: 0;\n background: color-mix(in srgb, var(--squisq-prompter-accent, #e8b64c) 9%, transparent);\n pointer-events: none;\n z-index: 1;\n}\n\n.squisq-teleprompter-block-marker {\n display: block;\n margin: 1.1em 0 0.35em;\n font-size: 0.38em;\n font-weight: 600;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--squisq-prompter-muted, #9a9aa0);\n border-top: 1px solid color-mix(in srgb, var(--squisq-prompter-muted, #9a9aa0) 35%, transparent);\n padding-top: 0.6em;\n}\n.squisq-teleprompter-para {\n margin: 0 0 0.55em;\n line-height: 1.4;\n font-weight: 500;\n}\n.squisq-teleprompter-word {\n opacity: 0.45;\n transition: color 0.1s linear, opacity 0.1s linear;\n}\n.squisq-teleprompter-word--read {\n opacity: 0.9;\n}\n.squisq-teleprompter-word--active {\n opacity: 1;\n font-weight: 800;\n color: var(--squisq-prompter-accent, #e8b64c);\n}\n\n.squisq-teleprompter-countdown {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 3;\n background: color-mix(in srgb, var(--squisq-prompter-bg, #101014) 72%, transparent);\n}\n.squisq-teleprompter-countdown-digit {\n font-size: 18vmin;\n font-weight: 800;\n color: var(--squisq-prompter-accent, #e8b64c);\n animation: squisq-prompter-pulse 1s ease-in-out infinite;\n}\n.squisq-teleprompter-recdot {\n position: absolute;\n top: 14px;\n right: 16px;\n z-index: 4;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: #e5484d;\n box-shadow: 0 0 0 3px color-mix(in srgb, #e5484d 30%, transparent);\n animation: squisq-prompter-pulse 1.4s ease-in-out infinite;\n}\n/* Counter-flip overlays so they stay readable in mirror mode. */\n.squisq-teleprompter-surface--mirrored .squisq-teleprompter-countdown,\n.squisq-teleprompter-surface--mirrored .squisq-teleprompter-recdot {\n transform: scaleX(-1);\n}\n\n.squisq-teleprompter-selfview {\n position: absolute;\n right: 14px;\n bottom: 14px;\n width: 160px;\n border-radius: 8px;\n border: 2px solid color-mix(in srgb, #e5484d 60%, transparent);\n z-index: 5;\n background: #000;\n}\n\n.squisq-teleprompter-review {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 10px;\n padding: 8px 12px;\n border-top: 1px solid var(--squisq-border, #e5e7eb);\n background: var(--squisq-surface, var(--squisq-bg, #fff));\n color: var(--squisq-text, #111827);\n font-size: 12.5px;\n}\n.squisq-teleprompter-review audio {\n height: 28px;\n max-width: 260px;\n}\n.squisq-teleprompter-review button {\n font: inherit;\n color: inherit;\n background: var(--squisq-input-bg, #fff);\n border: 1px solid var(--squisq-border, #d1d5db);\n border-radius: 6px;\n padding: 4px 10px;\n cursor: pointer;\n}\n\n.squisq-teleprompter-float-note {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n color: var(--squisq-text-muted, #4b5563);\n font-size: 14px;\n}\n\n.squisq-teleprompter-controls {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 10px;\n padding: 8px 12px;\n border-top: 1px solid var(--squisq-border, #e5e7eb);\n background: var(--squisq-surface, var(--squisq-bg, #fff));\n color: var(--squisq-text, #111827);\n font-size: 12.5px;\n}\n.squisq-teleprompter-controls .squisq-teleprompter-group {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n white-space: nowrap;\n}\n.squisq-teleprompter-controls button {\n font: inherit;\n color: inherit;\n background: var(--squisq-input-bg, #fff);\n border: 1px solid var(--squisq-border, #d1d5db);\n border-radius: 6px;\n padding: 4px 10px;\n cursor: pointer;\n}\n.squisq-teleprompter-controls button:hover {\n background: var(--squisq-surface-hover, #f3f4f6);\n}\n.squisq-teleprompter-controls button[aria-pressed='true'] {\n background: var(--squisq-text, #111827);\n color: var(--squisq-bg, #fff);\n border-color: var(--squisq-text, #111827);\n}\n.squisq-teleprompter-controls select,\n.squisq-teleprompter-controls input[type='range'] {\n font: inherit;\n color: inherit;\n background: var(--squisq-input-bg, #fff);\n border: 1px solid var(--squisq-border, #d1d5db);\n border-radius: 6px;\n max-width: 170px;\n}\n.squisq-teleprompter-controls input[type='range'] {\n border: none;\n background: transparent;\n width: 110px;\n}\n.squisq-teleprompter-meter {\n position: relative;\n width: 64px;\n height: 8px;\n border-radius: 4px;\n overflow: hidden;\n background: var(--squisq-border, #e5e7eb);\n}\n.squisq-teleprompter-meter-fill {\n position: absolute;\n inset: 0 auto 0 0;\n background: #9ca3af;\n transition: width 0.08s linear;\n}\n.squisq-teleprompter-meter--voice .squisq-teleprompter-meter-fill {\n background: #30a46c;\n}\n\n@keyframes squisq-prompter-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.45; }\n}\n@media (prefers-reduced-motion: reduce) {\n .squisq-teleprompter-countdown-digit,\n .squisq-teleprompter-recdot {\n animation: none;\n }\n .squisq-teleprompter-word {\n transition: none;\n }\n}\n";
307
315
 
308
- export { EYE_LINE_FRACTION, FloatingWindowHandle, type NarrationSavePlan, type NarrationSaveResult, NarrationStage, NarrationStageHandle, type NarrationStageProps, PCM_WORKLET_NAME, PCM_WORKLET_SOURCE, TELEPROMPTER_CSS, TeleprompterController, TeleprompterControls, type TeleprompterControlsProps, TeleprompterRecordingDeps, TeleprompterSurface, type TeleprompterSurfaceProps, TeleprompterView, type TeleprompterViewProps, type TokenLineMap, buildNarrationSavePlan, cameraVideoLine, ensureTeleprompterStyles, executeNarrationSave, insertNarrationPreamble, measureTokenLines, narrationAnnotationLine, prompterVarsFromTheme, registerPcmWorklet, stepScroll, targetOffsetFor };
316
+ export { EYE_LINE_FRACTION, type ExecuteNarrationSaveDeps, FloatingWindowHandle, type NarrationSavePlan, type NarrationSavePlanArgs, type NarrationSaveProgress, type NarrationSaveResult, NarrationStage, NarrationStageHandle, type NarrationStageProps, PCM_WORKLET_NAME, PCM_WORKLET_SOURCE, TELEPROMPTER_CSS, TeleprompterController, TeleprompterControls, type TeleprompterControlsProps, TeleprompterRecordingDeps, TeleprompterSurface, type TeleprompterSurfaceProps, TeleprompterView, type TeleprompterViewProps, type TokenLineMap, buildNarrationSavePlan, cameraVideoLine, ensureTeleprompterStyles, executeNarrationSave, insertNarrationPreamble, measureTokenLines, narrationAnnotationLine, prompterVarsFromTheme, registerPcmWorklet, stepScroll, targetOffsetFor };