pptx-vanilla-viewer 0.1.3 → 0.3.0

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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { n, o, m, P, a, e, f, X, p, q, r, s, t, u, v, w, x, y, z, A, B, D, E, F, G, H, J, K, L, i, j, l, k, g, h } from './presentation-BfnrtJV1.js';
1
+ import { n, o, m, P, a, e, f, X, p, q, r, s, t, u, v, w, x, y, z, A, B, D, E, F, G, H, J, K, L, i, j, l, k, g, h, a0 } from './presentation-BfnrtJV1.js';
2
2
  export { a as PptxElement, P as PptxSlide } from './presentation-BfnrtJV1.js';
3
3
  import { ViewerTheme } from './theme/index.js';
4
4
  export { ViewerTheme, ViewerThemeColors, defaultCssVars, defaultRadius, defaultThemeColors, themeToCssVars, vermilionDarkTheme, vermilionLightTheme } from './theme/index.js';
@@ -1368,9 +1368,403 @@ interface CanvasSize {
1368
1368
  width: number;
1369
1369
  height: number;
1370
1370
  }
1371
+ /** Collaboration role within a session. */
1372
+ type CollaborationRole = 'owner' | 'collaborator' | 'viewer';
1373
+ /**
1374
+ * Collaboration transport.
1375
+ *
1376
+ * - `'websocket'` (default): y-websocket against `serverUrl`.
1377
+ * - `'webrtc'`: y-webrtc peer-to-peer; needs no document server. Peers meet
1378
+ * through the `signaling` servers (WebRTC signaling only, no document data)
1379
+ * and same-browser tabs additionally sync via BroadcastChannel even without
1380
+ * any signaling server, which makes this mode usable from static hosting.
1381
+ */
1382
+ type CollaborationTransport = 'websocket' | 'webrtc';
1383
+ /**
1384
+ * Real-time collaboration configuration.
1385
+ *
1386
+ * The same shape is accepted by the React, Vue, and Angular bindings.
1387
+ */
1388
+ interface CollaborationConfig {
1389
+ /** Unique identifier for the collaboration room (alphanumeric, hyphens, underscores). */
1390
+ roomId: string;
1391
+ /**
1392
+ * WebSocket server URL for the Yjs provider (e.g. "wss://collab.example.com").
1393
+ * Ignored (may be empty) when `transport` is `'webrtc'`.
1394
+ */
1395
+ serverUrl: string;
1396
+ /** Transport to use. Defaults to `'websocket'`. */
1397
+ transport?: CollaborationTransport;
1398
+ /**
1399
+ * WebRTC signaling server URLs (only used when `transport` is `'webrtc'`).
1400
+ * Defaults to y-webrtc's built-in public signaling list. Same-browser tabs
1401
+ * sync via BroadcastChannel regardless of signaling availability.
1402
+ */
1403
+ signaling?: string[];
1404
+ /** Display name for the local user. */
1405
+ userName: string;
1406
+ /** Avatar URL for the local user (optional). */
1407
+ userAvatar?: string;
1408
+ /** Hex colour for the local user's cursor/presence indicator. */
1409
+ userColor?: string;
1410
+ /** Optional authentication token sent with the WebSocket handshake. */
1411
+ authToken?: string;
1412
+ /** Role in the session; defaults to `'collaborator'`. */
1413
+ role?: CollaborationRole;
1414
+ /**
1415
+ * Elected-writer write-back callback (Area 3 of the C3 hardening plan).
1416
+ *
1417
+ * When the local user has `role: 'owner'`, the binding debounces changes and
1418
+ * serializes the current Y.Doc state to a PPTX byte array, then calls this
1419
+ * callback so the host can persist the snapshot. Only one writer (the owner)
1420
+ * does this; other collaborators never trigger write-back, eliminating the
1421
+ * last-save-wins problem.
1422
+ */
1423
+ onWriteBack?: (bytes: Uint8Array) => void;
1424
+ /**
1425
+ * Debounce delay (ms) between the last Y.Doc change and the write-back
1426
+ * invocation. Defaults to 5000 ms. Set to 0 to write back on every change
1427
+ * (not recommended for large documents).
1428
+ */
1429
+ writeBackDebounceMs?: number;
1430
+ }
1371
1431
  /** A neutral CSS map (framework `CSSProperties` are structurally compatible). */
1372
1432
  type CssStyleMap = Record<string, string | number>;
1373
1433
 
1434
+ /**
1435
+ * Pure geometry helpers for the align / distribute editor operations.
1436
+ *
1437
+ * These functions operate over a list of slide elements (anything carrying the
1438
+ * `{ id, x, y, width, height }` bounding-box fields of {@link PptxElement}) and
1439
+ * return a `Map` keyed by element `id` describing the *new* position(s) for the
1440
+ * elements that need to move. Elements that already sit on the target edge (or
1441
+ * the two outer-most elements during distribution) are still included with
1442
+ * their unchanged coordinate so callers can apply the whole map uniformly — the
1443
+ * map only ever contains the axis that the operation touches.
1444
+ *
1445
+ * The helpers are deliberately framework-agnostic: no DOM, no Vue reactivity,
1446
+ * no side effects. The host wires them into the editor by feeding the current
1447
+ * selection in and applying the returned `Map` via its element-transform
1448
+ * operation (one batched history entry per call).
1449
+ */
1450
+ /** Edge / centre that {@link alignElements} can snap a selection to. */
1451
+ type AlignEdge = 'left' | 'centerH' | 'right' | 'top' | 'middle' | 'bottom';
1452
+
1453
+ /**
1454
+ * "Change Case" text mutation (PowerPoint's Aa dropdown: Sentence case, lower,
1455
+ * UPPER, Capitalize Each Word, tOGGLE cASE). Unlike `textCaps` (a purely
1456
+ * visual `text-transform`-style render hint), these modes rewrite the actual
1457
+ * characters, matching PowerPoint's own behaviour. Framework-agnostic; no
1458
+ * framework imports.
1459
+ */
1460
+
1461
+ type ChangeCaseMode = 'sentence' | 'lower' | 'upper' | 'capitalize' | 'toggle';
1462
+
1463
+ /**
1464
+ * collaboration-presence.ts: Pure, framework-agnostic logic for the real-time
1465
+ * collaboration subsystem (Yjs-backed presence + remote cursors).
1466
+ *
1467
+ * Everything here is a plain function with no framework / Yjs dependency, so it
1468
+ * can be unit-tested in isolation and shared across the React, Vue, and Angular
1469
+ * bindings. The bindings own the stateful provider lifecycle (creating the
1470
+ * Y.Doc / WebsocketProvider, wiring awareness listeners) and call into these
1471
+ * helpers to validate config, sanitise inbound awareness data, and project it
1472
+ * into render-ready view-models.
1473
+ *
1474
+ * Responsibilities:
1475
+ * - Validate the room id and detect mixed-content (ws:// from https) up front.
1476
+ * - Sanitise inbound awareness data (XSS, bounds, colour, avatar, room id).
1477
+ * - Map awareness state into a `RemoteCursor` view-model for rendering.
1478
+ * - Derive the presence list (remote users only, stale entries dropped).
1479
+ * - Deterministic per-user colour assignment + cursor label formatting.
1480
+ *
1481
+ * This mirrors the React `sanitize.ts` / `usePresenceTracking.ts` helpers and
1482
+ * the Angular `collaboration-helpers.ts` (which predates this shared copy).
1483
+ */
1484
+
1485
+ /** Connection lifecycle states for the Yjs WebSocket provider. */
1486
+ type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
1487
+ /** In-memory clipboard payload stored when an element is copied or cut. */
1488
+ interface ElementClipboardPayload {
1489
+ element: a;
1490
+ isTemplate: boolean;
1491
+ }
1492
+
1493
+ /**
1494
+ * shape-preset-catalog.ts: the Insert > Shape picker catalogue shared by every
1495
+ * binding's toolbar/inspector.
1496
+ *
1497
+ * Pure data: each entry carries the preset geometry `type` (OOXML `a:prstGeom`
1498
+ * value the editor can insert), an English fallback `label`, the shared-i18n
1499
+ * `i18nKey`, and a framework-neutral icon descriptor ({@link ShapePresetGlyph}
1500
+ * name + optional utility-class modifier for rotation/skew). Each binding maps
1501
+ * the glyph name onto its own icon component/SVG.
1502
+ *
1503
+ * Order matters: bindings surface the first 12 entries as the quick "top
1504
+ * shapes" row, so new presets should be appended, not inserted.
1505
+ *
1506
+ * @module render/shape-preset-catalog
1507
+ */
1508
+ /** Shape preset geometry types offered by the insert picker. */
1509
+ type ShapePresetType = 'rect' | 'roundRect' | 'ellipse' | 'cylinder' | 'rtArrow' | 'leftArrow' | 'upArrow' | 'downArrow' | 'triangle' | 'rtTriangle' | 'diamond' | 'parallelogram' | 'trapezoid' | 'pentagon' | 'hexagon' | 'octagon' | 'chevron' | 'star5' | 'star6' | 'star8' | 'plus' | 'heart' | 'cloud' | 'sun' | 'moon' | 'pie' | 'plaque' | 'teardrop' | 'line' | 'connector';
1510
+ interface AutosaveRecord {
1511
+ key: string;
1512
+ data: Uint8Array;
1513
+ timestamp: number;
1514
+ size: number;
1515
+ }
1516
+
1517
+ /**
1518
+ * Pure handout layout calculations, shared by every binding's print path.
1519
+ *
1520
+ * Handles distributing slides across pages, computing grid dimensions, and
1521
+ * positioning cells within A4 page space. No DOM/framework dependency: callers
1522
+ * render the resulting rectangles however their view layer prefers.
1523
+ */
1524
+ /** Supported slides-per-page values. */
1525
+ type HandoutSlidesPerPage = 1 | 2 | 3 | 4 | 6 | 9;
1526
+
1527
+ /**
1528
+ * Pure print helpers shared by every binding's print path: settings validation,
1529
+ * slide-range / colour-filter resolution, page-count estimation, HTML markup
1530
+ * builders, escaping, and the full print-document assembler.
1531
+ *
1532
+ * No DOM side effects and no `window.print()`: everything is deterministic and
1533
+ * the binding writes the returned HTML string into a print window. The handout
1534
+ * grid geometry lives in `handout-layout`; this module reuses it.
1535
+ *
1536
+ * ng-packagr constraint honoured here (the Angular binding inlines this source
1537
+ * and compiles it through ng-packagr): NO `String.prototype.replaceAll`
1538
+ * (`escapeHtml` uses `.split(x).join(y)` instead).
1539
+ */
1540
+
1541
+ /** What to print. */
1542
+ type PrintWhat = 'slides' | 'handouts' | 'notes' | 'outline';
1543
+ /** Page orientation for the printed output. */
1544
+ type PrintOrientation = 'portrait' | 'landscape';
1545
+ /** Colour mode for the printed output. */
1546
+ type PrintColorMode = 'color' | 'grayscale' | 'blackAndWhite';
1547
+ /** Slide range mode. */
1548
+ type PrintSlideRange = 'all' | 'current' | 'custom';
1549
+ /** Resolved print settings emitted on confirm. */
1550
+ interface PrintSettings {
1551
+ printWhat: PrintWhat;
1552
+ orientation: PrintOrientation;
1553
+ colorMode: PrintColorMode;
1554
+ frameSlides: boolean;
1555
+ slidesPerPage: HandoutSlidesPerPage;
1556
+ slideRange: PrintSlideRange;
1557
+ customRangeFrom: number;
1558
+ customRangeTo: number;
1559
+ }
1560
+
1561
+ /**
1562
+ * A tiny framework-free reactive store: `get` / `set(patch)` / `subscribe`.
1563
+ *
1564
+ * This is deliberately minimal (a few dozen lines, no external deps). It only
1565
+ * carries the vanilla binding's *view state*; all domain logic (parsing,
1566
+ * styles, geometry, text building) stays in `pptx-viewer-core` and
1567
+ * `pptx-viewer-shared`.
1568
+ */
1569
+ /** Listener invoked after every state change with the next and previous state. */
1570
+ type StoreListener<T> = (state: T, previous: T) => void;
1571
+ interface Store<T extends object> {
1572
+ /** Current state snapshot (do not mutate). */
1573
+ get(): T;
1574
+ /** Shallow-merge a partial patch into the state and notify subscribers. */
1575
+ set(patch: Partial<T>): void;
1576
+ /** Subscribe to state changes; returns an unsubscribe function. */
1577
+ subscribe(listener: StoreListener<T>): () => void;
1578
+ }
1579
+
1580
+ /** `zoom` is either an explicit scale factor (1 = 100%) or fit-to-viewport. */
1581
+ type ZoomLevel = number | 'fit';
1582
+ /**
1583
+ * The vanilla viewer's reactive view state. Kept intentionally flat and small;
1584
+ * everything here is what the DOM render layer consumes.
1585
+ */
1586
+ interface ViewerState {
1587
+ /** Parsed slides (image/media URLs already patched in by the load pipeline). */
1588
+ slides: P[];
1589
+ /** Slide canvas size in CSS pixels. */
1590
+ canvasSize: CanvasSize;
1591
+ /** Archive-path to displayable URL map for media + poster frames. */
1592
+ mediaDataUrls: Map<string, string>;
1593
+ /** Zero-based index of the visible slide. */
1594
+ currentSlide: number;
1595
+ /** Requested zoom (explicit factor or fit-to-viewport). */
1596
+ zoom: ZoomLevel;
1597
+ /** True while a load is in flight. */
1598
+ loading: boolean;
1599
+ /** Error message from the last failed load, or null. */
1600
+ error: string | null;
1601
+ /** True while presentation (fullscreen) mode is active. */
1602
+ presenting: boolean;
1603
+ /** True when editing interactions (select/move/resize/...) are enabled. */
1604
+ editable: boolean;
1605
+ /** Id of the selected element on the current slide, or null. */
1606
+ selectedElementId: string | null;
1607
+ /** True when the document has unsaved edits (cleared by a save). */
1608
+ dirty: boolean;
1609
+ /**
1610
+ * True while a pointer gesture (drag/resize/rotate) is in flight. Thumbnail
1611
+ * re-renders are deferred until the gesture ends.
1612
+ */
1613
+ interactionActive: boolean;
1614
+ /**
1615
+ * True when the speaker-notes panel body is expanded. Persists across slide
1616
+ * navigation for the life of the viewer instance (in-memory only).
1617
+ */
1618
+ notesExpanded: boolean;
1619
+ /** In-memory clipboard payload from the last copy/cut, or null. */
1620
+ clipboardPayload: ElementClipboardPayload | null;
1621
+ }
1622
+
1623
+ /**
1624
+ * Z-order, align, flip, and group/ungroup actions for the ribbon's
1625
+ * Home > Arrange group.
1626
+ *
1627
+ * Align/distribute: the vanilla editor's selection model is single-element
1628
+ * only (see `state/viewer-state.ts`), so `alignElements` reaches the
1629
+ * single-selection "align to slide" mode ({@link alignToCanvas}), matching
1630
+ * PowerPoint's own behaviour when nothing else is selected. Multi-selection
1631
+ * "align to selection bounding box" / distribute (needing >= 2 / >= 3
1632
+ * elements, see `editor-arrange-mutations.ts`) is unreachable until a
1633
+ * multi-select model lands; `canDistribute` is therefore always `false` today
1634
+ * (see `editing-chrome-sync.ts`), a documented limitation rather than a bug.
1635
+ *
1636
+ * Group: needs >= 2 selected elements to be meaningful, so it too is
1637
+ * unreachable under single selection; `groupSelected` is a deliberate no-op
1638
+ * kept only so the ribbon button (permanently disabled, with an explanatory
1639
+ * title) has something to call. Ungroup works today: it applies to whichever
1640
+ * single `group` element is selected.
1641
+ */
1642
+ interface ArrangeActions {
1643
+ bringForward(): void;
1644
+ sendBackward(): void;
1645
+ bringToFront(): void;
1646
+ sendToBack(): void;
1647
+ alignElements(edge: AlignEdge): void;
1648
+ flipHorizontal(): void;
1649
+ flipVertical(): void;
1650
+ groupSelected(): void;
1651
+ ungroupSelected(): void;
1652
+ }
1653
+
1654
+ /**
1655
+ * Cut/copy/paste actions for the ribbon's Home > Clipboard group, backed by
1656
+ * the shared `element-clipboard.ts` codec. The in-memory clipboard payload
1657
+ * lives on `ViewerState.clipboardPayload` (not a module-level variable) so
1658
+ * the ribbon's selection sync can reactively enable/disable the Paste button.
1659
+ */
1660
+ interface ClipboardActions {
1661
+ copy(): void;
1662
+ cut(): void;
1663
+ paste(): void;
1664
+ }
1665
+
1666
+ /**
1667
+ * Insert-element factories for the vanilla editor.
1668
+ *
1669
+ * The pure builders wrap the framework-agnostic core/shared factories
1670
+ * (`createTextElement`, `createShapeElement`, `createConnectorElement`,
1671
+ * `newTableElement`) and centre the new element on the slide canvas. The one
1672
+ * async helper (`pickImageElement`) owns the file-picker + `FileReader` DOM
1673
+ * side effects needed to turn a chosen image file into a data-URL image
1674
+ * element; it too returns a centred, ready-to-insert element.
1675
+ */
1676
+ /** The element kinds the Insert ribbon tab can create directly. */
1677
+ type InsertKind = 'text' | 'table' | 'shape';
1678
+
1679
+ /**
1680
+ * New/duplicate/delete-slide actions for the ribbon's Home > Slides group,
1681
+ * backed by the shared `slide-operations.ts` factory. Every mutation is
1682
+ * history-integrated (matches the element-level actions in `editor-edit-ops`)
1683
+ * and renumbers `slideNumber` across the whole deck so it always matches the
1684
+ * array index (mirrors the Vue `useSlideOperations` contract).
1685
+ */
1686
+ interface SlideActions {
1687
+ addSlide(): void;
1688
+ duplicateSlide(): void;
1689
+ deleteSlide(): void;
1690
+ }
1691
+
1692
+ /**
1693
+ * Character + paragraph formatting actions for the ribbon's Home > Font and
1694
+ * Home > Paragraph groups. Every method is a thin `applyToSelected` wrapper
1695
+ * around the pure builders in `editor-format-mutations.ts` /
1696
+ * `editor-paragraph-mutations.ts`; this file owns none of the mutation logic
1697
+ * itself, only the selection/history wiring shared by the whole action set.
1698
+ */
1699
+ interface TextActions {
1700
+ toggleBold(): void;
1701
+ toggleItalic(): void;
1702
+ toggleUnderline(): void;
1703
+ toggleStrikethrough(): void;
1704
+ toggleTextShadow(): void;
1705
+ changeFontSize(delta: number): void;
1706
+ setFontSize(size: number): void;
1707
+ setFontFamily(family: string): void;
1708
+ setTextColor(color: string): void;
1709
+ setHighlightColor(color: string): void;
1710
+ setCharacterSpacing(value: number): void;
1711
+ changeCase(mode: ChangeCaseMode): void;
1712
+ clearFormatting(): void;
1713
+ toggleBulletList(): void;
1714
+ toggleNumberedList(): void;
1715
+ increaseIndent(): void;
1716
+ decreaseIndent(): void;
1717
+ setTextAlign(align: a0['align']): void;
1718
+ setLineSpacing(value: number): void;
1719
+ }
1720
+
1721
+ /** A geometry patch from the inspector (all fields optional). */
1722
+ interface GeometryPatch {
1723
+ x?: number;
1724
+ y?: number;
1725
+ width?: number;
1726
+ height?: number;
1727
+ rotation?: number;
1728
+ }
1729
+ /**
1730
+ * The full set of formatting / insert / arrange / clipboard / slide actions
1731
+ * exposed to the editing chrome (ribbon, inspector). Composed from the
1732
+ * focused per-concern action files (`editor-text-actions.ts`,
1733
+ * `editor-arrange-actions.ts`, `editor-clipboard-actions.ts`,
1734
+ * `editor-slide-actions.ts`) plus the shape/geometry/insert actions owned
1735
+ * directly here. Every mutating action is history-integrated (push -> mutate
1736
+ * -> commit) via the shared {@link EditorOps}.
1737
+ */
1738
+ interface EditActions extends TextActions, ArrangeActions, ClipboardActions, SlideActions {
1739
+ setShapeFill(color: string): void;
1740
+ setShapeStroke(color: string): void;
1741
+ setShapeStrokeWidth(width: number): void;
1742
+ /** Commit an inspector geometry edit (X/Y/W/H/rotation). */
1743
+ setGeometry(patch: GeometryPatch): void;
1744
+ insert(kind: InsertKind, shapeType?: ShapePresetType): void;
1745
+ insertImage(): Promise<void>;
1746
+ duplicateSelected(): void;
1747
+ deleteSelected(): void;
1748
+ }
1749
+
1750
+ /**
1751
+ * Find & Replace actions for the ribbon's Home > Editing group, backed by the
1752
+ * shared `find-replace.ts` helpers. A lean, docked-panel implementation:
1753
+ * `search` reports a match count for the query, `replaceCurrent` replaces the
1754
+ * first match, and `replaceAll` replaces every match; there is no in-canvas
1755
+ * match highlighting or "next/previous match" cursor (that needs per-match
1756
+ * selection/scroll wiring into the renderer, out of scope for this wave).
1757
+ * Every replace is history-integrated like every other editor action.
1758
+ */
1759
+ interface FindReplaceActions {
1760
+ /** Count occurrences of `query` across all slides (does not mutate). */
1761
+ search(query: string, matchCase: boolean): number;
1762
+ /** Replace the first match of `query`; returns the number replaced (0 or 1). */
1763
+ replaceCurrent(query: string, replacement: string, matchCase: boolean): number;
1764
+ /** Replace every match of `query`; returns the number replaced. */
1765
+ replaceAll(query: string, replacement: string, matchCase: boolean): number;
1766
+ }
1767
+
1374
1768
  /**
1375
1769
  * Minimal i18n for the vanilla binding.
1376
1770
  *
@@ -1394,6 +1788,25 @@ type TranslationMessages = Record<string, Record<string, string>>;
1394
1788
  */
1395
1789
  declare function createTranslator(locale?: string, messages?: TranslationMessages): Translator;
1396
1790
 
1791
+ /** Selection-derived state the inspector reflects. */
1792
+ interface InspectorState {
1793
+ hasSelection: boolean;
1794
+ canShape: boolean;
1795
+ x: number;
1796
+ y: number;
1797
+ width: number;
1798
+ height: number;
1799
+ rotation: number;
1800
+ fillColor: string | undefined;
1801
+ strokeColor: string | undefined;
1802
+ strokeWidth: number;
1803
+ }
1804
+ interface Inspector {
1805
+ el: HTMLElement;
1806
+ update(state: InspectorState): void;
1807
+ setEditable(editable: boolean): void;
1808
+ }
1809
+
1397
1810
  /** State the panel needs to reflect: the slide to read notes from, and whether edits are allowed. */
1398
1811
  interface NotesPanelUpdate {
1399
1812
  slide: P | undefined;
@@ -1411,44 +1824,54 @@ interface NotesPanel {
1411
1824
  setExpanded(expanded: boolean): void;
1412
1825
  }
1413
1826
 
1414
- interface ThumbnailRail {
1415
- el: HTMLElement;
1416
- /** Rebuild the rail for a new slide list (uses `renderStage` per slide). */
1417
- render(slides: P[], canvasSize: CanvasSize, renderStage: (slide: P, scale: number) => HTMLElement): void;
1418
- /** Highlight the active slide and scroll it into view. */
1419
- setActive(index: number): void;
1420
- /** Show or hide the rail. */
1421
- setVisible(visible: boolean): void;
1422
- }
1423
-
1424
- interface ToolbarUpdate {
1425
- /** Zero-based current slide index. */
1827
+ /** Nav-row state (prev/next/counter/zoom label). */
1828
+ interface RibbonNavState {
1426
1829
  current: number;
1427
- /** Total slide count. */
1428
1830
  total: number;
1429
- /** Effective zoom percentage (100 = 1:1). */
1430
1831
  zoomPercent: number;
1431
1832
  }
1432
- /** Editing-cluster state (Save / Undo / Redo buttons). */
1433
- interface ToolbarEditState {
1434
- /** Shows/hides the whole editing cluster. */
1833
+ /** Primary-row + tab-bar visibility state. */
1834
+ interface RibbonEditState {
1435
1835
  editable: boolean;
1436
1836
  canUndo: boolean;
1437
1837
  canRedo: boolean;
1438
1838
  }
1439
- interface Toolbar {
1839
+ /** Selection-derived state the Home tab's Font/Paragraph/Arrange groups reflect. */
1840
+ interface RibbonSelectionState {
1841
+ hasClipboard: boolean;
1842
+ slideCount: number;
1843
+ }
1844
+
1845
+ interface Ribbon {
1440
1846
  el: HTMLElement;
1441
- update(state: ToolbarUpdate): void;
1442
- setEditState(state: ToolbarEditState): void;
1443
- /** Reflect the notes panel's expanded/collapsed state on the Notes button. */
1847
+ update(state: RibbonNavState): void;
1848
+ setEditState(state: RibbonEditState): void;
1444
1849
  setNotesExpanded(expanded: boolean): void;
1850
+ setAutosaveStatus(label: string, kind: 'idle' | 'saving' | 'saved' | 'error'): void;
1851
+ /** Show/hide the whole editing surface (Home/Insert tab content + find/replace). */
1852
+ setEditable(editable: boolean): void;
1853
+ /** Reflect the current selection across the Home tab's Font/Paragraph/Arrange groups. */
1854
+ updateSelection(selectedElement: a | undefined, extra: RibbonSelectionState): void;
1855
+ }
1856
+
1857
+ interface ThumbnailRail {
1858
+ el: HTMLElement;
1859
+ /** Rebuild the rail for a new slide list (uses `renderStage` per slide). */
1860
+ render(slides: P[], canvasSize: CanvasSize, renderStage: (slide: P, scale: number) => HTMLElement): void;
1861
+ /** Highlight the active slide and scroll it into view. */
1862
+ setActive(index: number): void;
1863
+ /** Show or hide the rail. */
1864
+ setVisible(visible: boolean): void;
1445
1865
  }
1446
1866
 
1447
1867
  /** The viewer's static DOM skeleton plus the mutable overlay controls. */
1448
1868
  interface ViewerChrome {
1449
1869
  /** `.pptxv` root (focusable; keyboard navigation attaches here). */
1450
1870
  root: HTMLElement;
1451
- toolbar: Toolbar | null;
1871
+ /** The tabbed ribbon (primary row, nav row, tab bar + File/Home/Insert/View content); null when disabled. */
1872
+ ribbon: Ribbon | null;
1873
+ /** Property inspector panel; null when disabled. */
1874
+ inspector: Inspector | null;
1452
1875
  thumbnails: ThumbnailRail | null;
1453
1876
  /** Scrollable centring viewport around the stage. */
1454
1877
  viewport: HTMLElement;
@@ -1473,6 +1896,80 @@ interface PresentationController {
1473
1896
  dispose(): void;
1474
1897
  }
1475
1898
 
1899
+ interface EditorController {
1900
+ /** (Re)wire listeners + overlay into the current chrome (after mount). */
1901
+ attachChrome(): void;
1902
+ detachChrome(): void;
1903
+ /** Called by the render controller after every stage render. */
1904
+ onStageRendered(): void;
1905
+ /** True while editing owns the keyboard (selection or inline editing). */
1906
+ capturesKeyboard(): boolean;
1907
+ /** Drop history/selection/dirty state (new content loaded). */
1908
+ reset(): void;
1909
+ setEditable(editable: boolean): void;
1910
+ undo(): void;
1911
+ redo(): void;
1912
+ canUndo(): boolean;
1913
+ canRedo(): boolean;
1914
+ deleteSelected(): void;
1915
+ duplicateSelected(): string | null;
1916
+ getSelectedElementId(): string | null;
1917
+ /** The formatting / insert / arrange actions for the editing chrome. */
1918
+ getEditActions(): EditActions;
1919
+ /** The Find & Replace actions for the ribbon's docked panel. */
1920
+ getFindReplaceActions(): FindReplaceActions;
1921
+ /** Commit the speaker-notes textarea's plain text onto the current slide. */
1922
+ commitNotes(notes: string): void;
1923
+ save(): Promise<Uint8Array>;
1924
+ downloadPptx(fileName?: string): Promise<void>;
1925
+ destroy(): void;
1926
+ }
1927
+
1928
+ /** Everything the controller needs after a stage (re)render. */
1929
+ interface SyncStageParams {
1930
+ doc: Document;
1931
+ /** The stage host (position: relative), for layering a transition overlay. */
1932
+ stageWrap: HTMLElement;
1933
+ /** The freshly rendered, fully-visible main stage node. */
1934
+ stage: HTMLElement;
1935
+ /** The slide the stage renders, or `undefined` when empty. */
1936
+ slide: P | undefined;
1937
+ /** Zero-based index of the rendered slide. */
1938
+ slideIndex: number;
1939
+ /** True only when the live (fullscreen) presentation stage is active. */
1940
+ presenting: boolean;
1941
+ }
1942
+ /**
1943
+ * The presentation-mode playback state machine for the vanilla binding.
1944
+ *
1945
+ * It owns the click-stepped animation cursor and the slide-transition overlay,
1946
+ * both driven by the shared framework-agnostic helpers. It is consulted from
1947
+ * two seams in the rebuild-per-change render flow:
1948
+ *
1949
+ * - {@link PresentationPlayback.syncStage} runs after every stage render:
1950
+ * on a slide entry it rebuilds the click groups, resets the step, hides
1951
+ * pending entrances, and (when the slide changed mid-show) plays the
1952
+ * incoming slide's transition over a snapshot of the outgoing stage.
1953
+ * - {@link PresentationPlayback.advance} runs on each forward navigation
1954
+ * key/tap while presenting: it reveals the next animation build in place
1955
+ * (no rebuild) and reports whether a build remained, so the caller only
1956
+ * advances the slide once the timeline is exhausted.
1957
+ */
1958
+ interface PresentationPlayback {
1959
+ /**
1960
+ * Reveal the next on-click animation build for the current slide. Returns
1961
+ * `true` if a build was revealed (stay on the slide); `false` when the
1962
+ * slide's builds are exhausted (the caller should advance to the next slide).
1963
+ */
1964
+ advance(): boolean;
1965
+ /** True when every click group on the current slide has been revealed. */
1966
+ isComplete(): boolean;
1967
+ /** Sync playback + transitions after a stage (re)render. */
1968
+ syncStage(params: SyncStageParams): void;
1969
+ /** Cancel any running transition and forget all per-slide state. */
1970
+ reset(): void;
1971
+ }
1972
+
1476
1973
  /** Discriminant values of the {@link PptxElement} union (`'text'`, `'chart'`, ...). */
1477
1974
  type PptxElementType = a['type'];
1478
1975
  /**
@@ -1620,79 +2117,137 @@ declare function renderSlideStage(options: SlideStageOptions): HTMLElement;
1620
2117
  */
1621
2118
  declare function createDefaultRegistry(): ElementRendererRegistry;
1622
2119
 
2120
+ interface RenderController {
2121
+ /** Re-render everything (used after a chrome rebuild). */
2122
+ renderAll(): void;
2123
+ /** Re-render the main stage + toolbar counters at the current state. */
2124
+ renderStage(): void;
2125
+ /** Rebuild the thumbnail rail from the current slide list. */
2126
+ renderThumbnails(): void;
2127
+ /** Resolve the requested zoom into a concrete scale factor. */
2128
+ effectiveScale(): number;
2129
+ /**
2130
+ * Presentation-mode animation/transition playback, driven by the stage
2131
+ * rebuild flow. Navigation (`viewer-controls`) consults `advance()` so a
2132
+ * "next" first steps the on-click animation timeline before advancing slides.
2133
+ */
2134
+ readonly presentationPlayback: PresentationPlayback;
2135
+ }
2136
+
1623
2137
  /**
1624
- * A tiny framework-free reactive store: `get` / `set(patch)` / `subscribe`.
2138
+ * Debounced autosave for the vanilla viewer, layered on the shared IndexedDB
2139
+ * recovery store (`pptx-viewer-shared/autosave-store`).
1625
2140
  *
1626
- * This is deliberately minimal (a few dozen lines, no external deps). It only
1627
- * carries the vanilla binding's *view state*; all domain logic (parsing,
1628
- * styles, geometry, text building) stays in `pptx-viewer-core` and
1629
- * `pptx-viewer-shared`.
2141
+ * Semantics mirror the React/Vue bindings: a local edit marks the document
2142
+ * dirty; a debounce timer then re-serializes the deck through
2143
+ * `PptxHandler.save` and persists it under `filePath` via
2144
+ * `saveAutosaveSnapshot` (evicting the oldest record on quota exhaustion). The
2145
+ * recovery blob is a crash-safety net only; it never clears the editor's dirty
2146
+ * flag or stands in for the user's real Save.
2147
+ *
2148
+ * On construction it probes `getAutosaveSnapshot(filePath)` and, when a snapshot
2149
+ * from a previous session exists, offers it back through `onRecovery` so the
2150
+ * host can decide whether to restore it (e.g. via `loadFile`).
1630
2151
  */
1631
- /** Listener invoked after every state change with the next and previous state. */
1632
- type StoreListener<T> = (state: T, previous: T) => void;
1633
- interface Store<T extends object> {
1634
- /** Current state snapshot (do not mutate). */
1635
- get(): T;
1636
- /** Shallow-merge a partial patch into the state and notify subscribers. */
1637
- set(patch: Partial<T>): void;
1638
- /** Subscribe to state changes; returns an unsubscribe function. */
1639
- subscribe(listener: StoreListener<T>): () => void;
1640
- }
2152
+ type AutosaveStatus = 'idle' | 'saving' | 'saved' | 'error';
2153
+
2154
+ /** Per-slide progress callback: `(currentSlideIndex, totalSlides)`. */
2155
+ type ExportProgress = (current: number, total: number) => void;
1641
2156
 
1642
- /** `zoom` is either an explicit scale factor (1 = 100%) or fit-to-viewport. */
1643
- type ZoomLevel = number | 'fit';
1644
2157
  /**
1645
- * The vanilla viewer's reactive view state. Kept intentionally flat and small;
1646
- * everything here is what the DOM render layer consumes.
2158
+ * Animated-GIF export for the vanilla binding. All slides are captured (one
2159
+ * frame per slide) via the injected `rasterizeSlide`, then encoded with the
2160
+ * shared pure-JS GIF89a encoder (`pptx-viewer-shared` `gif-encoder`: median-cut
2161
+ * quantisation + LZW). Frame timing comes from the shared `planGifFrames`
2162
+ * planner and oversized captures are downscaled via `clampGifDimensions`; only
2163
+ * the DOM capture / canvas scaling / Blob download driver lives here.
1647
2164
  */
1648
- interface ViewerState {
1649
- /** Parsed slides (image/media URLs already patched in by the load pipeline). */
1650
- slides: P[];
1651
- /** Slide canvas size in CSS pixels. */
1652
- canvasSize: CanvasSize;
1653
- /** Archive-path to displayable URL map for media + poster frames. */
1654
- mediaDataUrls: Map<string, string>;
1655
- /** Zero-based index of the visible slide. */
1656
- currentSlide: number;
1657
- /** Requested zoom (explicit factor or fit-to-viewport). */
1658
- zoom: ZoomLevel;
1659
- /** True while a load is in flight. */
1660
- loading: boolean;
1661
- /** Error message from the last failed load, or null. */
1662
- error: string | null;
1663
- /** True while presentation (fullscreen) mode is active. */
1664
- presenting: boolean;
1665
- /** True when editing interactions (select/move/resize/...) are enabled. */
1666
- editable: boolean;
1667
- /** Id of the selected element on the current slide, or null. */
1668
- selectedElementId: string | null;
1669
- /** True when the document has unsaved edits (cleared by a save). */
1670
- dirty: boolean;
2165
+ /** Options for the animated-GIF export (all slides, one frame per slide). */
2166
+ interface ExportGifOptions {
1671
2167
  /**
1672
- * True while a pointer gesture (drag/resize/rotate) is in flight. Thumbnail
1673
- * re-renders are deferred until the gesture ends.
2168
+ * Duration each slide is shown, in milliseconds (default 2000). Per-slide
2169
+ * overrides can be supplied via {@link ExportGifOptions.slideTimingsMs}.
1674
2170
  */
1675
- interactionActive: boolean;
2171
+ slideDurationMs?: number;
1676
2172
  /**
1677
- * True when the speaker-notes panel body is expanded. Persists across slide
1678
- * navigation for the life of the viewer instance (in-memory only).
2173
+ * Per-slide duration overrides in milliseconds (index maps to slide index,
2174
+ * e.g. rehearsed timings). Flows through the shared `planGifFrames` plan
2175
+ * into per-frame GIF delays.
1679
2176
  */
1680
- notesExpanded: boolean;
2177
+ slideTimingsMs?: number[];
2178
+ /**
2179
+ * Cap on the longer side of the encoded frames, in pixels (default 1920).
2180
+ * Captured canvases larger than this are downscaled before quantisation,
2181
+ * keeping encode time and file size manageable.
2182
+ */
2183
+ maxDimension?: number;
2184
+ /** Capture-phase progress callback: `(currentSlide, totalSlides)`. */
2185
+ onProgress?: ExportProgress;
2186
+ /** Abort the export early; the capture loop checks this between slides. */
2187
+ signal?: AbortSignal;
1681
2188
  }
1682
2189
 
1683
- interface RenderController {
1684
- /** Re-render everything (used after a chrome rebuild). */
1685
- renderAll(): void;
1686
- /** Re-render the main stage + toolbar counters at the current state. */
1687
- renderStage(): void;
1688
- /** Rebuild the thumbnail rail from the current slide list. */
1689
- renderThumbnails(): void;
1690
- /** Resolve the requested zoom into a concrete scale factor. */
1691
- effectiveScale(): number;
2190
+ /**
2191
+ * Print for the vanilla binding, assembled entirely from the shared print
2192
+ * module (`pptx-viewer-shared` `print-document`): `validatePrintSettings`,
2193
+ * `computeSlideIndices` / `computeColorFilter`, the `build*Html` body markup
2194
+ * builders, and the DOMPurify-hardened `buildPrintHtmlDocument` assembler.
2195
+ * Only the drivers live here: rasterising the selected slides to data URLs and
2196
+ * writing the document into a print window. Vanilla port of Vue's `usePrint`
2197
+ * raster path (slides / notes / handouts / outline).
2198
+ */
2199
+ /**
2200
+ * Open a print window for a complete HTML document and trigger printing.
2201
+ * Returns `false` when the window could not be opened (popup blocker).
2202
+ */
2203
+ type OpenPrintWindow = (htmlDocument: string) => boolean;
2204
+ /**
2205
+ * Options for `print`: any subset of the shared `PrintSettings` (unspecified
2206
+ * fields fall back to `DEFAULT_PRINT_SETTINGS`, i.e. all slides, landscape,
2207
+ * full colour) plus progress/abort and a print-window override.
2208
+ */
2209
+ interface PrintOptions extends Partial<PrintSettings> {
2210
+ /** Rasterisation progress callback: `(currentSlide, totalSlidesToPrint)`. */
2211
+ onProgress?: ExportProgress;
2212
+ /** Abort before the window opens; checked between slide captures. */
2213
+ signal?: AbortSignal;
2214
+ /**
2215
+ * Override how the assembled document is opened (e.g. write it into a
2216
+ * hidden iframe). Popup-blocker caveat: the default opener uses
2217
+ * `window.open`, which browsers typically only allow inside a user
2218
+ * gesture, so call `print()` from a click handler; when the popup is
2219
+ * blocked the returned promise resolves `false`.
2220
+ */
2221
+ openPrintWindow?: OpenPrintWindow;
2222
+ }
2223
+
2224
+ /**
2225
+ * WebM video export for the vanilla binding, driven by the shared `video-plan`
2226
+ * module: `planVideoSegments` (per-slide segment timing + frame counts),
2227
+ * `fpsToFrameIntervalMs` (draw-loop pacing), and `pickSupportedMimeType` over
2228
+ * `WEBM_MIME_CANDIDATES` (MediaRecorder codec selection). Only the browser
2229
+ * driver lives here: capture each slide to a canvas, replay the canvases onto
2230
+ * a recording canvas fed to `captureStream()` + `MediaRecorder`, and download
2231
+ * the resulting Blob. Vanilla port of React's `exportAllSlidesAsVideo`.
2232
+ */
2233
+ /** Options for the WebM video export (all slides). */
2234
+ interface ExportVideoOptions {
2235
+ /** Duration each slide is held, in milliseconds (default 3000). */
2236
+ slideDurationMs?: number;
2237
+ /** Per-slide duration overrides in milliseconds (index maps to slide index). */
2238
+ slideTimingsMs?: number[];
2239
+ /** Recording frame rate in frames per second (default 30). */
2240
+ fps?: number;
2241
+ /** MediaRecorder video bitrate in bits per second (default 5,000,000). */
2242
+ videoBitsPerSecond?: number;
2243
+ /** Capture-phase progress callback: `(currentSlide, totalSlides)`. */
2244
+ onProgress?: ExportProgress;
2245
+ /** Recording-phase progress callback: `(currentSlide, totalSlides)`. */
2246
+ onRecordProgress?: ExportProgress;
2247
+ /** Abort the export early; checked between slides and between frames. */
2248
+ signal?: AbortSignal;
1692
2249
  }
1693
2250
 
1694
- /** Per-slide progress callback: `(currentSlideIndex, totalSlides)`. */
1695
- type ExportProgress = (current: number, total: number) => void;
1696
2251
  /** Options for the multi-slide PDF export (progress + cooperative cancel). */
1697
2252
  interface ExportPdfOptions {
1698
2253
  /** Capture-phase progress callback: `(currentSlide, totalSlides)`. */
@@ -1707,13 +2262,6 @@ interface ExportPdfOptions {
1707
2262
  */
1708
2263
  type PptxViewerSource = ArrayBuffer | Uint8Array | Blob | string;
1709
2264
 
1710
- /**
1711
- * Public API types for the vanilla (zero-framework) PowerPoint viewer.
1712
- *
1713
- * Derived from the Vue binding's `PowerPointViewerProps` / emits, translated
1714
- * to plain options + callback functions (there is no framework event system
1715
- * to emit through).
1716
- */
1717
2265
  /** Callbacks mirroring the Vue component's emits. */
1718
2266
  interface PptxViewerCallbacks {
1719
2267
  /** Fired after a presentation loads successfully. */
@@ -1735,6 +2283,16 @@ interface PptxViewerCallbacks {
1735
2283
  onDirtyChange?: (dirty: boolean) => void;
1736
2284
  /** Fired when the selected element changes (`null` = no selection). */
1737
2285
  onSelectionChange?: (elementId: string | null) => void;
2286
+ /** Fired on every autosave lifecycle transition (`saving`/`saved`/`error`). */
2287
+ onAutosaveStatus?: (status: AutosaveStatus) => void;
2288
+ /**
2289
+ * Offered a recovery snapshot found in the shared IndexedDB store on start
2290
+ * (a previous session's autosave for the same `autosaveFilePath`). The host
2291
+ * decides whether to restore it, e.g. `viewer.loadFile(record.data)`.
2292
+ */
2293
+ onAutosaveRecovery?: (record: AutosaveRecord) => void;
2294
+ /** Fired on every collaboration connection-status transition. */
2295
+ onCollaborationStatus?: (status: ConnectionStatus) => void;
1738
2296
  }
1739
2297
  interface PptxViewerOptions extends PptxViewerCallbacks {
1740
2298
  /**
@@ -1769,6 +2327,16 @@ interface PptxViewerOptions extends PptxViewerCallbacks {
1769
2327
  showToolbar?: boolean;
1770
2328
  /** Show the thumbnail sidebar (default `true`). */
1771
2329
  showThumbnails?: boolean;
2330
+ /**
2331
+ * Build the editing format toolbar row (bold/fill/insert/z-order); default
2332
+ * `true`. The row is only *visible* while editing is enabled.
2333
+ */
2334
+ showFormatToolbar?: boolean;
2335
+ /**
2336
+ * Build the property inspector panel (position/size/fill/line); default
2337
+ * `true`. Only *visible* while editing is enabled.
2338
+ */
2339
+ showInspector?: boolean;
1772
2340
  /**
1773
2341
  * Custom element-renderer registry. Defaults to `createDefaultRegistry()`;
1774
2342
  * pass your own (or mutate the default via `getRegistry()`) to add or
@@ -1784,6 +2352,30 @@ interface PptxViewerOptions extends PptxViewerCallbacks {
1784
2352
  * mirroring the Vue/React/Angular bindings).
1785
2353
  */
1786
2354
  smartArt3D?: boolean;
2355
+ /**
2356
+ * Enable debounced autosave (default `false`): after each local edit the deck
2357
+ * is re-serialized and stashed in the shared IndexedDB recovery store as a
2358
+ * crash-safety net (it never replaces the user's real Save). The toolbar shows
2359
+ * a small status pill; a snapshot from a prior session is offered through
2360
+ * {@link PptxViewerCallbacks.onAutosaveRecovery}.
2361
+ */
2362
+ autosave?: boolean;
2363
+ /** Debounce window (ms) between an edit and the persisted snapshot (default 2000). */
2364
+ autosaveIntervalMs?: number;
2365
+ /** IndexedDB recovery key for autosave (default `'presentation.pptx'`). */
2366
+ autosaveFilePath?: string;
2367
+ /**
2368
+ * Start a real-time collaboration session immediately (Yjs over y-websocket
2369
+ * or serverless y-webrtc). Local edits publish to peers and remote edits
2370
+ * merge in granularly; a `role: 'viewer'` config forces read-only. Start or
2371
+ * stop a session later with {@link PptxViewerInstance.startCollaboration} /
2372
+ * {@link PptxViewerInstance.stopCollaboration}.
2373
+ *
2374
+ * Note: media/OLE/3D/ink binary payloads are not carried over the wire (a
2375
+ * shared codec limitation), and a remote update replaces the whole local
2376
+ * slide array, so a joiner's host-provided media can degrade.
2377
+ */
2378
+ collaboration?: CollaborationConfig;
1787
2379
  }
1788
2380
  /** The viewer handle returned by `createPptxViewer`. */
1789
2381
  interface PptxViewerInstance {
@@ -1843,6 +2435,26 @@ interface PptxViewerInstance {
1843
2435
  * `jspdf` is dynamically imported on first use.
1844
2436
  */
1845
2437
  exportPdf(options?: ExportPdfOptions): Promise<void>;
2438
+ /**
2439
+ * Export every slide as an animated GIF download (one frame per slide,
2440
+ * `slideDurationMs` per frame). Slides are captured off-screen like
2441
+ * `exportSlidePng` and encoded with the shared pure-JS GIF89a encoder.
2442
+ */
2443
+ exportGif(options?: ExportGifOptions): Promise<void>;
2444
+ /**
2445
+ * Export every slide as a WebM video download: each captured slide is held
2446
+ * for its configured duration on a canvas stream recorded by
2447
+ * `MediaRecorder` (codec picked from the shared WebM candidates).
2448
+ */
2449
+ exportVideo(options?: ExportVideoOptions): Promise<void>;
2450
+ /**
2451
+ * Assemble the printable document (slides / notes / handouts / outline)
2452
+ * and open it in a new print window. Resolves `false` when the popup was
2453
+ * blocked: browsers typically only allow `window.open` inside a user
2454
+ * gesture, so call this from a click handler (or pass a custom
2455
+ * `openPrintWindow` that writes into an iframe you own).
2456
+ */
2457
+ print(options?: PrintOptions): Promise<boolean>;
1846
2458
  /** The element-renderer registry in effect (extension point). */
1847
2459
  getRegistry(): ElementRendererRegistry;
1848
2460
  /**
@@ -1851,6 +2463,18 @@ interface PptxViewerInstance {
1851
2463
  * archive access) without extra APIs here.
1852
2464
  */
1853
2465
  getHandler(): PptxHandler | null;
2466
+ /**
2467
+ * Start (or restart) a real-time collaboration session. Resolves once the
2468
+ * transport is created; connection status arrives via
2469
+ * {@link PptxViewerCallbacks.onCollaborationStatus}.
2470
+ */
2471
+ startCollaboration(config: CollaborationConfig): Promise<void>;
2472
+ /** Stop the active collaboration session (no-op when none is running). */
2473
+ stopCollaboration(): void;
2474
+ /** Current collaboration connection status (`'disconnected'` when inactive). */
2475
+ getCollaborationStatus(): ConnectionStatus;
2476
+ /** Force an immediate autosave snapshot (no-op when autosave is disabled). */
2477
+ autosaveNow(): Promise<void>;
1854
2478
  /** Tear down DOM, listeners, Blob URLs, and the core handler. */
1855
2479
  destroy(): void;
1856
2480
  }
@@ -1874,6 +2498,8 @@ interface ChromeHost {
1874
2498
  lifecycle: ChromeLifecycle;
1875
2499
  editor: {
1876
2500
  commitNotes(notes: string): void;
2501
+ getEditActions(): EditActions;
2502
+ getFindReplaceActions(): FindReplaceActions;
1877
2503
  };
1878
2504
  prev(): void;
1879
2505
  next(): void;
@@ -1888,41 +2514,51 @@ interface ChromeHost {
1888
2514
  getSlideCount(): number;
1889
2515
  enterPresentation(): Promise<void>;
1890
2516
  exitPresentation(): Promise<void>;
2517
+ exportSlidePng(): Promise<void>;
2518
+ exportPdf(): Promise<void>;
2519
+ exportGif(): Promise<void>;
2520
+ exportVideo(): Promise<void>;
2521
+ print(): Promise<boolean>;
1891
2522
  }
1892
2523
 
1893
- interface EditorController {
1894
- /** (Re)wire listeners + overlay into the current chrome (after mount). */
1895
- attachChrome(): void;
1896
- detachChrome(): void;
1897
- /** Called by the render controller after every stage render. */
1898
- onStageRendered(): void;
1899
- /** True while editing owns the keyboard (selection or inline editing). */
1900
- capturesKeyboard(): boolean;
1901
- /** Drop history/selection/dirty state (new content loaded). */
1902
- reset(): void;
1903
- setEditable(editable: boolean): void;
1904
- undo(): void;
1905
- redo(): void;
1906
- canUndo(): boolean;
1907
- canRedo(): boolean;
1908
- deleteSelected(): void;
1909
- duplicateSelected(): string | null;
1910
- getSelectedElementId(): string | null;
1911
- /** Commit the speaker-notes textarea's plain text onto the current slide. */
1912
- commitNotes(notes: string): void;
1913
- save(): Promise<Uint8Array>;
1914
- downloadPptx(fileName?: string): Promise<void>;
2524
+ /** The export slice of the public viewer API (see `PptxViewerInstance`). */
2525
+ interface ViewerExportApi {
2526
+ /** Export a single slide as a PNG download. Defaults to the current slide. */
2527
+ exportSlidePng(index?: number): Promise<void>;
2528
+ /** Export every slide as a multi-page PDF download (one slide per page). */
2529
+ exportPdf(options?: ExportPdfOptions): Promise<void>;
2530
+ /** Export every slide as an animated GIF download (one frame per slide). */
2531
+ exportGif(options?: ExportGifOptions): Promise<void>;
2532
+ /** Export every slide as a WebM video download (MediaRecorder). */
2533
+ exportVideo(options?: ExportVideoOptions): Promise<void>;
2534
+ /** Open the assembled print document in a print window (`false` = blocked). */
2535
+ print(options?: PrintOptions): Promise<boolean>;
2536
+ }
2537
+ interface ExportLifecycle extends ViewerExportApi {
2538
+ /** Remove the off-screen capture stage from the DOM. */
1915
2539
  destroy(): void;
1916
2540
  }
2541
+ /**
2542
+ * Base class implementing the export slice of `PptxViewerInstance` by
2543
+ * delegating to the instance's {@link ExportLifecycle}. `PptxViewer` extends
2544
+ * this, so new export formats are wired here (and in the export controller)
2545
+ * without growing the size-capped `PptxViewer.ts`.
2546
+ */
2547
+ declare abstract class ViewerExportHost implements ViewerExportApi {
2548
+ protected abstract readonly exporter: ExportLifecycle;
2549
+ exportSlidePng(index?: number): Promise<void>;
2550
+ exportPdf(options?: ExportPdfOptions): Promise<void>;
2551
+ exportGif(options?: ExportGifOptions): Promise<void>;
2552
+ exportVideo(options?: ExportVideoOptions): Promise<void>;
2553
+ print(options?: PrintOptions): Promise<boolean>;
2554
+ }
1917
2555
 
1918
2556
  /**
1919
- * The zero-framework PowerPoint viewer. Construct via {@link createPptxViewer}
1920
- * (or `new PptxViewer(container, options)`): builds its chrome inside
1921
- * `container`, loads `options.source` when given, and re-renders through a
1922
- * tiny reactive store. All parsing lives in `pptx-viewer-core`; all pure
1923
- * render logic in `pptx-viewer-shared`.
2557
+ * The zero-framework PowerPoint viewer. Construct via {@link createPptxViewer}:
2558
+ * builds chrome inside `container`, loads `options.source` when given, and
2559
+ * re-renders through a tiny reactive store.
1924
2560
  */
1925
- declare class PptxViewer implements PptxViewerInstance, ChromeHost {
2561
+ declare class PptxViewer extends ViewerExportHost implements PptxViewerInstance, ChromeHost {
1926
2562
  readonly container: HTMLElement;
1927
2563
  readonly doc: Document;
1928
2564
  readonly options: PptxViewerOptions;
@@ -1931,9 +2567,11 @@ declare class PptxViewer implements PptxViewerInstance, ChromeHost {
1931
2567
  t: Translator;
1932
2568
  lifecycle: ChromeLifecycle;
1933
2569
  editor: EditorController;
2570
+ protected readonly exporter: ExportLifecycle;
1934
2571
  private readonly loading;
1935
- private readonly exportLifecycle;
1936
2572
  private readonly registry;
2573
+ private readonly sessions;
2574
+ private readonly controls;
1937
2575
  private destroyed;
1938
2576
  constructor(container: HTMLElement, options?: PptxViewerOptions);
1939
2577
  loadFile(file: Blob | ArrayBuffer | Uint8Array): Promise<void>;
@@ -1960,30 +2598,22 @@ declare class PptxViewer implements PptxViewerInstance, ChromeHost {
1960
2598
  save(): Promise<Uint8Array>;
1961
2599
  downloadPptx(fileName?: string): Promise<void>;
1962
2600
  deleteSelected(): void;
1963
- exportSlidePng(index?: number): Promise<void>;
1964
- exportPdf(options?: ExportPdfOptions): Promise<void>;
1965
2601
  getSelectedElementId(): string | null;
1966
2602
  enterPresentation(): Promise<void>;
1967
2603
  exitPresentation(): Promise<void>;
1968
2604
  getRegistry(): ElementRendererRegistry;
1969
2605
  getHandler(): PptxHandler | null;
2606
+ startCollaboration(config: CollaborationConfig): Promise<void>;
2607
+ stopCollaboration(): void;
2608
+ getCollaborationStatus(): ConnectionStatus;
2609
+ autosaveNow(): Promise<void>;
1970
2610
  destroy(): void;
1971
2611
  private remountChrome;
1972
2612
  }
1973
- /**
1974
- * Create a PowerPoint viewer inside `container`.
1975
- *
1976
- * ```ts
1977
- * import { createPptxViewer } from 'pptx-vanilla-viewer';
1978
- * const viewer = createPptxViewer(document.querySelector('#host')!, {
1979
- * source: '/deck.pptx',
1980
- * onSlideChange: (i) => console.log('slide', i + 1),
1981
- * });
1982
- * ```
1983
- */
2613
+ /** Create a PowerPoint viewer inside `container` (see {@link PptxViewerOptions}). */
1984
2614
  declare function createPptxViewer(container: HTMLElement, options?: PptxViewerOptions): PptxViewerInstance;
1985
2615
 
1986
2616
  /** The complete viewer stylesheet text (for hosts that self-manage CSS). */
1987
2617
  declare function getViewerCss(): string;
1988
2618
 
1989
- export { type CssStyleMap, type ElementRenderContext, type ElementRenderer, type ElementRendererRegistry, type ExportPdfOptions, type ExportProgress, type PptxElementType, PptxHandler, PptxViewer, type PptxViewerCallbacks, type PptxViewerInstance, type PptxViewerOptions, type PptxViewerSource, type SlideStageOptions, type TranslationMessages, type Translator, type ViewerState, type ZoomLevel, applyStyleMap, createDefaultRegistry, createEl, createElementRendererRegistry, createPptxViewer, createSvgEl, createTranslator, getViewerCss, renderSlideStage };
2619
+ export { type AutosaveRecord, type AutosaveStatus, type CollaborationConfig, type CollaborationRole, type CollaborationTransport, type ConnectionStatus, type CssStyleMap, type ElementRenderContext, type ElementRenderer, type ElementRendererRegistry, type ExportGifOptions, type ExportPdfOptions, type ExportProgress, type ExportVideoOptions, type OpenPrintWindow, type PptxElementType, PptxHandler, PptxViewer, type PptxViewerCallbacks, type PptxViewerInstance, type PptxViewerOptions, type PptxViewerSource, type PrintOptions, type SlideStageOptions, type TranslationMessages, type Translator, type ViewerState, type ZoomLevel, applyStyleMap, createDefaultRegistry, createEl, createElementRendererRegistry, createPptxViewer, createSvgEl, createTranslator, getViewerCss, renderSlideStage };