@silurus/ooxml 0.68.0 → 0.70.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.
@@ -105,6 +105,10 @@ declare type BodyElement = {
105
105
  headers?: HeadersFooters;
106
106
  footers?: HeadersFooters;
107
107
  titlePage?: boolean;
108
+ /** ECMA-376 §17.6.13 / §17.6.11 — this ENDING section's page geometry
109
+ * (size + margins). Absent when the sectPr inherits both pgSz and pgMar
110
+ * (the renderer then falls back to the body-level section geometry). */
111
+ geom?: SectionGeom;
108
112
  };
109
113
 
110
114
  declare interface Border {
@@ -133,6 +137,43 @@ declare interface BorderSpec {
133
137
  style: string;
134
138
  }
135
139
 
140
+ /**
141
+ * Build the transparent text-selection overlay for a rendered docx page: one
142
+ * absolutely-positioned, color-transparent `<span>` per {@link DocxTextRunInfo}
143
+ * (emitted by `renderPage`'s `onTextRun`), so the browser's native selection
144
+ * lands on the drawn glyphs. Extracted verbatim from `DocxViewer._buildTextLayer`
145
+ * so both the pager (DocxViewer) and the continuous-scroll viewer (DocxScrollViewer)
146
+ * share one implementation; also public API for integrators building their own
147
+ * overlay (design §10). MAIN render mode only — `onTextRun` cannot cross the
148
+ * worker boundary.
149
+ *
150
+ * @param layer the overlay div (position:relative parent expected).
151
+ * @param runs per-run geometry from `renderPage({ onTextRun })`.
152
+ * @param canvasCssWidth the rendered canvas's CSS width (e.g. `"700px"`), used
153
+ * to size the overlay to match the canvas.
154
+ * @param canvasCssHeight the rendered canvas's CSS height.
155
+ */
156
+ declare function buildDocxTextLayer(layer: HTMLDivElement, runs: DocxTextRunInfo[], canvasCssWidth: string, canvasCssHeight: string): void;
157
+
158
+ /**
159
+ * Build the transparent text-selection overlay for a rendered pptx slide. Unlike
160
+ * docx (flat spans), pptx groups runs into one positioned `<div>` per shape frame
161
+ * (keyed by the shape's geometry + total rotation) and applies a CSS `rotate()` to
162
+ * the group when the shape is rotated, so the browser selection tracks the drawn,
163
+ * rotated text as a unit. Each run's `<span>` is absolutely positioned INSIDE its
164
+ * shape div (`inShapeX`/`inShapeY`). Extracted verbatim from
165
+ * `PptxViewer._buildTextLayer` so the pager (PptxViewer) and the continuous-scroll
166
+ * viewer (PptxScrollViewer, WS4) share one implementation; public API for
167
+ * integrators (design §10). MAIN render mode only — `onTextRun` cannot cross the
168
+ * worker boundary.
169
+ *
170
+ * @param layer the overlay div.
171
+ * @param runs per-run + per-shape geometry from `renderSlide({ onTextRun })`.
172
+ * @param cssWidth the rendered canvas's CSS width (px, number).
173
+ * @param cssHeight the rendered canvas's CSS height (px, number).
174
+ */
175
+ declare function buildPptxTextLayer(layer: HTMLDivElement, runs: PptxTextRunInfo[], cssWidth: number, cssHeight: number): void;
176
+
136
177
  declare type Bullet = {
137
178
  type: 'none';
138
179
  } | {
@@ -357,140 +398,11 @@ declare interface ChartAnchor {
357
398
  toColOff: number;
358
399
  toRow: number;
359
400
  toRowOff: number;
360
- chart: ChartData;
361
- }
362
-
363
- /**
364
- * XLSX parser's raw chart output. Retains parser-native `barDir` + `grouping`
365
- * which the renderer combines into a canonical `ChartModel.chartType` (e.g.
366
- * `clusteredBarH`, `stackedBarPct`) at render time.
367
- */
368
- declare interface ChartData {
369
- /** Primary chart type: "bar"|"line"|"area"|"pie"|"doughnut"|"radar"|"scatter" */
370
- chartType: string;
371
- /** "col" (vertical bars) | "row" (horizontal bars) */
372
- barDir: string;
373
- /** "clustered"|"stacked"|"standard"|"percentStacked" */
374
- grouping: string;
375
- title: string | null;
376
- categories: string[];
377
- series: XlsxChartSeries[];
378
- /** Whether data labels are enabled (c:dLbls showVal/showPercent). */
379
- showDataLabels?: boolean;
380
- /** Category axis title (c:catAx/c:title). */
381
- catAxisTitle?: string | null;
382
- /** Value axis title (c:valAx/c:title). */
383
- valAxisTitle?: string | null;
384
- /** `<c:catAx><c:title>` run-prop font size (hpt), from the parser's
385
- * catAxisTitleSize. Distinct from `catAxisFontSizeHpt` (tick labels). */
386
- catAxisTitleSize?: number | null;
387
- /** `<c:catAx><c:title>` run-prop bold flag. */
388
- catAxisTitleBold?: boolean | null;
389
- /** `<c:catAx><c:title>` run-prop color (hex without '#'). */
390
- catAxisTitleColor?: string | null;
391
- /** `<c:valAx><c:title>` run-prop font size (hpt). */
392
- valAxisTitleSize?: number | null;
393
- /** `<c:valAx><c:title>` run-prop bold flag. */
394
- valAxisTitleBold?: boolean | null;
395
- /** `<c:valAx><c:title>` run-prop color (hex without '#'). */
396
- valAxisTitleColor?: string | null;
397
- /** True when <c:legend> is present. Absence means the legend is hidden. */
398
- showLegend?: boolean;
399
- /** `<c:legendPos val>` — "r"|"l"|"t"|"b"|"tr". null/undefined = default ("r"). */
400
- legendPos?: 'r' | 'l' | 't' | 'b' | 'tr' | null;
401
- /** Chart title font size in OOXML hundredths of a point (e.g. 1400 = 14pt). */
402
- titleFontSizeHpt?: number | null;
403
- /** Chart title font color as a hex string without '#' (srgbClr only). */
404
- titleFontColor?: string | null;
405
- /** Chart title font family from `<a:latin typeface>` (ECMA-376 §20.1.4.2.24). */
406
- titleFontFace?: string | null;
407
- /** Category axis tick-label font size in hpt (ECMA-376 §21.2.2.17 c:txPr). */
408
- catAxisFontSizeHpt?: number | null;
409
- /** Value axis tick-label font size in hpt. */
410
- valAxisFontSizeHpt?: number | null;
411
- /** Outer chartSpace background (ECMA-376 §21.2.2.5 `<c:spPr>`). Hex without
412
- * '#' for a solid fill, undefined for `<a:noFill/>` or an absent spPr.
413
- * Combine with `hasChartSpPr` to distinguish explicit-transparent from
414
- * default-opaque-white. */
415
- chartBg?: string | null;
416
- /** True when the parser saw a `<c:chartSpace><c:spPr>` element. Lets the
417
- * adapter tell "spec said noFill → transparent" from "no spPr → default". */
418
- hasChartSpPr?: boolean;
419
- /** `<c:legend><c:manualLayout>` fractions of chart space (§21.2.2.31). */
420
- legendManualLayout?: LegendManualLayout_2 | null;
421
- /** `<c:catAx><c:delete val="1"/>` — hide the category axis (§21.2.2.40). */
422
- catAxisHidden?: boolean;
423
- /** `<c:valAx><c:delete val="1"/>` — hide the value axis (§21.2.2.40). */
424
- valAxisHidden?: boolean;
425
- /** `<c:catAx><c:spPr><a:ln><a:noFill>` — line-only hide (labels stay). */
426
- catAxisLineHidden?: boolean;
427
- /** `<c:valAx><c:spPr><a:ln><a:noFill>` — line-only hide (labels stay). */
428
- valAxisLineHidden?: boolean;
429
- /** `<c:radarChart><c:radarStyle val>` — "standard" (line only, default),
430
- * "marker" (line + markers), "filled" (closed polygon with area fill).
431
- * Sample-1 "Biodiversity Index" uses "marker" — Excel renders no area
432
- * fill, but our renderer was filling at 25 % opacity regardless. */
433
- radarStyle?: string | null;
434
- /** `<c:valAx><c:numFmt@formatCode>` — number format for value-axis tick
435
- * labels (ECMA-376 §21.2.2.21). */
436
- valAxisFormatCode?: string | null;
437
- /** `<c:barChart><c:gapWidth>` — space between category groups as a percent
438
- * of bar width (§21.2.2.13). */
439
- barGapWidth?: number | null;
440
- /** `<c:barChart><c:overlap>` — signed percent overlap between bars in a
441
- * cluster (§21.2.2.25). Negative = gap. */
442
- barOverlap?: number | null;
443
- /** `<c:dLbls><c:dLblPos>` — data label position (§21.2.2.16). */
444
- dataLabelPosition?: string | null;
445
- /** Hex (no `#`) for data label text color, resolved from `<c:dLbls><c:txPr>`. */
446
- dataLabelFontColor?: string | null;
447
- /** `<c:dLbls><c:numFmt@formatCode>` — chart-level override for data label
448
- * number format (§21.2.2.35). */
449
- dataLabelFormatCode?: string | null;
450
- /** `<c:title>...defRPr@b>` chart title bold flag. */
451
- titleFontBold?: boolean;
452
- /** `<c:catAx><c:txPr>...defRPr@b>` X-axis tick label bold flag. */
453
- catAxisFontBold?: boolean;
454
- /** `<c:valAx><c:txPr>...defRPr@b>` Y-axis tick label bold flag. */
455
- valAxisFontBold?: boolean;
456
- /** `<c:catAx><c:crosses val>` (`autoZero` | `min` | `max`). Drives where
457
- * the X axis sits along the Y axis. Default `autoZero`. */
458
- catAxisCrosses?: string;
459
- /** `<c:catAx><c:crossesAt val>` explicit numeric crossing. */
460
- catAxisCrossesAt?: number;
461
- valAxisCrosses?: string;
462
- valAxisCrossesAt?: number;
463
- /** Axis line color (hex without `#`) and width in EMU, from
464
- * `<c:catAx|valAx><c:spPr><a:ln>`. */
465
- catAxisLineColor?: string;
466
- catAxisLineWidthEmu?: number;
467
- valAxisLineColor?: string;
468
- valAxisLineWidthEmu?: number;
469
- /** Explicit chart border color (hex without '#') from
470
- * `<c:chartSpace><c:spPr><a:ln><a:solidFill><a:srgbClr>`. Only set when the
471
- * XML explicitly declares a paintable line (no default border). */
472
- chartBorderColor?: string | null;
473
- /** `<c:chartSpace><c:spPr><a:ln@w>` border width in EMU. */
474
- chartBorderWidthEmu?: number | null;
475
- /** `<c:catAx | valAx><c:majorTickMark val>` / `<c:minorTickMark val>` —
476
- * one of `none`/`out`/`in`/`cross` (ECMA-376 §21.2.2.49). */
477
- catAxisMajorTickMark?: string;
478
- catAxisMinorTickMark?: string;
479
- valAxisMajorTickMark?: string;
480
- valAxisMinorTickMark?: string;
481
- /** `<c:catAx><c:numFmt@formatCode>` (or scatter X-axis valAx). */
482
- catAxisFormatCode?: string;
483
- /** `<c:catAx><c:scaling><c:min/max>` — explicit X-axis range. */
484
- catAxisMin?: number;
485
- catAxisMax?: number;
486
- /** `<c:valAx><c:scaling><c:min/max>` — explicit Y-axis range. */
487
- valAxisMin?: number;
488
- valAxisMax?: number;
489
- /** `<c:title><c:layout><c:manualLayout>` (§21.2.2.27) absolute placement. */
490
- titleManualLayout?: ManualLayout;
491
- /** `<c:plotArea><c:layout><c:manualLayout>` — `layoutTarget=inner` /
492
- * `outer` controls whether axes are inside the rect or outside. */
493
- plotAreaManualLayout?: ManualLayout;
401
+ /** The chart payload, already in the canonical {@link ChartModel} shape the
402
+ * Rust parser emits. The parser adapts its internal parse structure into
403
+ * `ChartModel` (formerly the TS `adaptChartData`); this is passed straight
404
+ * to `renderChart`. */
405
+ chart: ChartModel;
494
406
  }
495
407
 
496
408
  declare interface ChartDataLabelOverride {
@@ -522,117 +434,19 @@ declare interface ChartDataPointOverride {
522
434
  */
523
435
  declare interface ChartElement {
524
436
  type: 'chart';
437
+ /** Frame geometry on the slide, in EMU. */
525
438
  x: number;
526
439
  y: number;
527
440
  width: number;
528
441
  height: number;
529
- chartType: string;
530
- title: string | null;
531
- categories: string[];
532
- series: ChartSeries[];
533
- valMax: number | null;
534
- valMin: number | null;
535
- subtotalIndices: number[];
536
- showDataLabels: boolean;
537
- catAxisHidden: boolean;
538
- valAxisHidden: boolean;
539
- /** `<c:catAx><c:spPr><a:ln><a:noFill>` — line-only hide; labels stay. */
540
- catAxisLineHidden?: boolean;
541
- /** `<c:valAx><c:spPr><a:ln><a:noFill>` — line-only hide; labels stay. */
542
- valAxisLineHidden?: boolean;
543
- plotAreaBg: string | null;
544
- /** Outer chartSpace background (hex without '#'). null when noFill/absent. */
545
- chartBg: string | null;
546
- /** True when <c:legend> is declared; false suppresses the legend entirely. */
547
- showLegend: boolean;
548
- /** catAx crossBetween: "between" (default, 0.5-step padding) or "midCat". */
549
- catAxisCrossBetween: 'between' | 'midCat' | string;
550
- /** `<c:valAx><c:majorTickMark>`. "cross" (default) | "out" | "in" | "none". */
551
- valAxisMajorTickMark: 'cross' | 'out' | 'in' | 'none' | string;
552
- /** `<c:catAx><c:majorTickMark>`. */
553
- catAxisMajorTickMark: 'cross' | 'out' | 'in' | 'none' | string;
554
- /** Title font size in OOXML hundredths of a point (1600 = 16pt). null = default. */
555
- titleFontSizeHpt: number | null;
556
- /** Title font color as a hex string without '#'. null = default/theme. */
557
- titleFontColor?: string | null;
558
- /** Title font family (`<a:latin typeface>`). null = default/theme. */
559
- titleFontFace?: string | null;
560
- /** `<c:catAx><c:txPr>` font size (hpt). null = proportional default. */
561
- catAxisFontSizeHpt: number | null;
562
- /** `<c:valAx><c:txPr>` font size (hpt). null = proportional default. */
563
- valAxisFontSizeHpt: number | null;
564
- /** `<c:catAx><c:txPr>…<a:solidFill>` tick-label color (hex without '#'). */
565
- catAxisFontColor?: string | null;
566
- /** `<c:valAx><c:txPr>…<a:solidFill>` tick-label color (hex without '#'). */
567
- valAxisFontColor?: string | null;
568
- /** `<c:catAx><c:spPr><a:ln><a:solidFill>` axis-line color (hex without '#'). */
569
- catAxisLineColor?: string | null;
570
- /** `<c:catAx><c:spPr><a:ln w>` axis-line width in EMU. */
571
- catAxisLineWidthEmu?: number | null;
572
- /** `<c:valAx><c:spPr><a:ln><a:solidFill>` axis-line color (hex without '#'). */
573
- valAxisLineColor?: string | null;
574
- /** `<c:valAx><c:spPr><a:ln w>` axis-line width in EMU. */
575
- valAxisLineWidthEmu?: number | null;
576
- /** `<c:dLbls><c:txPr>` font size (hpt) for data-point value labels. */
577
- dataLabelFontSizeHpt: number | null;
578
- /** `<c:legend><c:legendPos val>` — "r" (default) | "l" | "t" | "b" | "tr". */
579
- legendPos?: 'r' | 'l' | 't' | 'b' | 'tr' | null;
580
- /** `<c:barChart><c:gapWidth val>` — % of bar width between category groups (default 150). */
581
- barGapWidth?: number | null;
582
- /** `<c:barChart><c:overlap val>` — signed % of bar width for cluster overlap. */
583
- barOverlap?: number | null;
584
- /** `<c:dLbls><c:dLblPos val>` — data label placement ("ctr" | "inEnd" | "outEnd" | …). */
585
- dataLabelPosition?: string | null;
586
- /** `<c:dLbls><c:txPr>…<a:solidFill>` resolved to hex (no '#'). null = renderer default. */
587
- dataLabelFontColor?: string | null;
588
- /** `<c:dLbls><c:numFmt formatCode>` — data label number format. */
589
- dataLabelFormatCode?: string | null;
590
- /** `<c:valAx><c:numFmt formatCode>` — value-axis tick label number format. */
591
- valAxisFormatCode?: string | null;
592
- /** `<c:plotArea><c:layout><c:manualLayout>` (ECMA-376 §21.2.2.32) — explicit
593
- * plot-area placement so bars don't extend past the chart-frame's intended
594
- * inner region (sample-2 slide-16 horizontal bar chart). */
595
- plotAreaManualLayout?: ChartManualLayout | null;
596
- /** `<c:scatterChart><c:scatterStyle val>` (ECMA-376 §21.2.2.42) — drives
597
- * whether scatter charts connect points with straight or smooth lines. */
598
- scatterStyle?: string | null;
599
- /** `<c:radarChart><c:radarStyle val>` (ECMA-376 §21.2.3.10). */
600
- radarStyle?: string | null;
601
- /** `<c:catAx><c:title>` plain text (ECMA-376 §21.2.2.6). For scatter the
602
- * bottom `<c:valAx>` (axPos b/t) feeds this. null = no title. */
603
- catAxisTitle?: string | null;
604
- /** `<c:valAx><c:title>` plain text. For scatter the left `<c:valAx>`
605
- * (axPos l/r) feeds this. null = no title. */
606
- valAxisTitle?: string | null;
607
- /** `<c:catAx><c:title>` run-prop font size (hpt). Distinct from
608
- * `catAxisFontSizeHpt` (tick labels). null = renderer default. */
609
- catAxisTitleSize?: number | null;
610
- /** `<c:catAx><c:title>` run-prop bold flag. null = not bold. */
611
- catAxisTitleBold?: boolean | null;
612
- /** `<c:catAx><c:title>` run-prop color (hex without '#'). null = default. */
613
- catAxisTitleColor?: string | null;
614
- /** `<c:valAx><c:title>` run-prop font size (hpt). null = renderer default. */
615
- valAxisTitleSize?: number | null;
616
- /** `<c:valAx><c:title>` run-prop bold flag. null = not bold. */
617
- valAxisTitleBold?: boolean | null;
618
- /** `<c:valAx><c:title>` run-prop color (hex without '#'). null = default. */
619
- valAxisTitleColor?: string | null;
620
- /** `<c:title>...defRPr@b` chart title bold flag. null = not bold. */
621
- titleFontBold?: boolean | null;
622
- /** `<c:catAx><c:txPr>...defRPr@b` X-axis tick-label bold flag. null = not bold. */
623
- catAxisFontBold?: boolean | null;
624
- /** `<c:valAx><c:txPr>...defRPr@b` Y-axis tick-label bold flag. null = not bold. */
625
- valAxisFontBold?: boolean | null;
626
- /** Explicit chart border color (hex without '#') from
627
- * `<c:chartSpace><c:spPr><a:ln><a:solidFill><a:srgbClr>`. Only set when the
628
- * XML explicitly declares a paintable line; null otherwise (no default border). */
629
- chartBorderColor?: string | null;
630
- /** `<c:chartSpace><c:spPr><a:ln@w>` border width in EMU. null = 1px hairline
631
- * when a color is present. */
632
- chartBorderWidthEmu?: number | null;
633
- /** Secondary value axis for combo charts (bar + line with a right-hand
634
- * axis). null/absent for the common single value-axis case. */
635
- secondaryValAxis?: SecondaryValueAxis | null;
442
+ /**
443
+ * The chart payload, already in the canonical {@link ChartModel} shape emitted
444
+ * by the Rust parser (`ooxml_common::chart::ChartModel`). Passed straight to
445
+ * `@silurus/ooxml-core`'s `renderChart` — no per-field adapter. The former
446
+ * 60-field flat copy on this interface is gone; all chart properties now live
447
+ * on `chart`.
448
+ */
449
+ chart: ChartModel;
636
450
  }
637
451
 
638
452
  declare interface ChartErrBars {
@@ -1014,24 +828,11 @@ declare interface ConditionalFormat {
1014
828
  rules: CfRule[];
1015
829
  }
1016
830
 
1017
- declare interface DataLabelOverride {
1018
- idx: number;
1019
- text: string;
1020
- /** "l"|"r"|"t"|"b"|"ctr"|"outEnd"|"bestFit" or undefined → inherit. */
1021
- position?: string;
1022
- fontColor?: string;
1023
- fontSizeHpt?: number;
1024
- fontBold?: boolean;
1025
- }
831
+ /** @deprecated Use `ChartDataLabelOverride` from @silurus/ooxml-core. */
832
+ declare type DataLabelOverride = ChartDataLabelOverride;
1026
833
 
1027
- declare interface DataPointOverride {
1028
- idx: number;
1029
- color?: string;
1030
- markerSymbol?: string;
1031
- markerSize?: number;
1032
- markerFill?: string;
1033
- markerLine?: string;
1034
- }
834
+ /** @deprecated Use `ChartDataPointOverride` from @silurus/ooxml-core. */
835
+ declare type DataPointOverride = ChartDataPointOverride;
1035
836
 
1036
837
  /** One `<dataValidation>` rule (ECMA-376 §18.3.1.33). `type` is the constraint
1037
838
  * class (`list` | `whole` | `decimal` | `date` | `time` | `textLength` |
@@ -1060,6 +861,20 @@ declare interface DefinedName {
1060
861
  formula: string;
1061
862
  }
1062
863
 
864
+ /**
865
+ * Translucent overlay drawn over a finished slide so it reads faintly
866
+ * (PowerPoint's hidden-slide thumbnail look). A pure render mechanism: the
867
+ * renderer never decides *when* to dim — the caller ({@link PptxViewer}'s
868
+ * `'dim'` mode) does. Both fields are required at the engine boundary; the
869
+ * viewer-facing override (`PptxViewerOptions.hiddenSlideDim`) is partial.
870
+ */
871
+ declare interface DimOptions {
872
+ /** CSS color of the overlay (e.g. `'#ffffff'`). */
873
+ color: string;
874
+ /** Overlay opacity 0..1 (e.g. `0.6` ⇒ underlying content shows at 40%). */
875
+ opacity: number;
876
+ }
877
+
1063
878
  declare interface DocComment {
1064
879
  id: string;
1065
880
  author?: string;
@@ -1184,6 +999,10 @@ declare interface DocSettings {
1184
999
  * justification (ST_Jc math: left|right|center|centerGroup). `undefined`
1185
1000
  * ⇒ the renderer uses the spec default `centerGroup`. */
1186
1001
  mathDefJc?: string;
1002
+ /** §17.15.1.25 `w:defaultTabStop@w:val` — interval (points) between automatic
1003
+ * tab stops generated after all custom stops. `undefined` ⇒ the renderer
1004
+ * uses the spec default of 720 twips (36pt). */
1005
+ defaultTabStop?: number;
1187
1006
  }
1188
1007
 
1189
1008
  declare interface DocTable {
@@ -1266,12 +1085,16 @@ export declare namespace docx {
1266
1085
  WireRenderPageOptions,
1267
1086
  DocxViewer,
1268
1087
  DocxViewerOptions,
1088
+ DocxScrollViewer,
1089
+ DocxScrollViewerOptions,
1090
+ buildDocxTextLayer,
1269
1091
  autoResize,
1270
1092
  AutoResizeOptions,
1271
1093
  noteText,
1272
1094
  DocxDocumentModel,
1273
1095
  DocSettings,
1274
1096
  SectionProps,
1097
+ SectionGeom,
1275
1098
  ColumnsSpec,
1276
1099
  ColSpec,
1277
1100
  HeadersFooters,
@@ -1341,6 +1164,11 @@ declare class DocxDocument {
1341
1164
  */
1342
1165
  getImage(imagePath: string, mimeType: string): Promise<Blob>;
1343
1166
  get pageCount(): number;
1167
+ /** The render mode this engine was loaded with ('main' | 'worker'). A fact for
1168
+ * integrators and the scroll viewer: an injected engine's mode decides whether
1169
+ * pages render via renderPage (main) or renderPageToBitmap (worker) — no
1170
+ * probing (design §11: no silent mis-pathing). */
1171
+ get mode(): 'main' | 'worker';
1344
1172
  /**
1345
1173
  * The raw parsed document model. Available only in `mode: 'main'`; in
1346
1174
  * `mode: 'worker'` the model stays in the worker and this throws.
@@ -1370,6 +1198,20 @@ declare class DocxDocument {
1370
1198
  */
1371
1199
  get endnotes(): DocNote[];
1372
1200
  private _getPages;
1201
+ /**
1202
+ * ECMA-376 §17.6.13 / §17.6.11 — the page size (pt) of page `pageIndex`, per
1203
+ * section (a mixed portrait/landscape document returns different sizes per page).
1204
+ * Available in BOTH modes: worker mode reads the worker-built `pageSizes` meta;
1205
+ * main mode reads the paginated pages' stamped geometry. Returns the body-level
1206
+ * section size for an out-of-range index (clamped) or a page with no stamped
1207
+ * geometry. `{ 0, 0 }` means "not loaded" (before `load()` resolves or after
1208
+ * `destroy()`). Returns a fresh object per call — safe to mutate.
1209
+ * The recommended way to ask "how big is page i?" for layout.
1210
+ */
1211
+ pageSize(pageIndex: number): {
1212
+ widthPt: number;
1213
+ heightPt: number;
1214
+ };
1373
1215
  renderPage(target: HTMLCanvasElement | OffscreenCanvas, pageIndex: number, opts?: RenderPageOptions): Promise<void>;
1374
1216
  /**
1375
1217
  * Render a page and return it as an ImageBitmap. Works in both modes; in
@@ -1435,6 +1277,453 @@ declare interface DocxRunBorder {
1435
1277
  space: number;
1436
1278
  }
1437
1279
 
1280
+ declare class DocxScrollViewer {
1281
+ private _doc;
1282
+ private readonly _injected;
1283
+ private readonly _opts;
1284
+ private readonly _container;
1285
+ private readonly _wrapper;
1286
+ private readonly _scrollHost;
1287
+ private readonly _spacer;
1288
+ /** Resolved render mode. When an engine is injected the engine's own `mode`
1289
+ * is authoritative (design §11 — no silent mis-pathing / no probing); an
1290
+ * explicitly conflicting `opts.mode` is rejected at construction. When self-
1291
+ * loading, `opts.mode` decides and `load()` passes it to `DocxDocument.load`. */
1292
+ private _mode;
1293
+ /** px-per-pt zoom multiplier. Base fit maps the first page's width to the
1294
+ * container width (or opts.width). Zoom multiplies this (design §7). */
1295
+ private _scale;
1296
+ /** Whether the base fit scale has been established. Set true the first time
1297
+ * `relayout()` resolves a positive base scale. We use an explicit flag rather
1298
+ * than a `_scale === 1` sentinel because a fit scale of exactly 1 is a valid
1299
+ * established state (a 1× fit would otherwise be re-fit forever). */
1300
+ private _scaleEstablished;
1301
+ /** Live slots keyed by page index. */
1302
+ private readonly _slots;
1303
+ /** Recyclable detached slots (canvas + textLayer reused across pages). */
1304
+ private readonly _free;
1305
+ /** Cached per-page heights in px at the current scale (index-aligned). */
1306
+ private _heights;
1307
+ private _lastRange;
1308
+ private _lastTopIndex;
1309
+ private _scrollListener;
1310
+ /** Set by `destroy()`. Async render callbacks (main + worker) check it before
1311
+ * reporting an error so a rejection that lands after teardown is swallowed
1312
+ * rather than surfaced to a `onError` on a dead viewer. */
1313
+ private _destroyed;
1314
+ /** Worker mode: page indices whose bitmap render is currently dispatched to the
1315
+ * engine. Coalesces a scroll storm — we never dispatch a second render for a
1316
+ * page whose first is still in flight — and lets us drop pages that scrolled
1317
+ * out of the window before dispatch (design §11 worker coalescing).
1318
+ *
1319
+ * T4 ZOOM HAZARD (RESOLVED by the render epoch below): coalescing keys on page
1320
+ * INDEX only, with no notion of the scale a dispatch was made at. Once
1321
+ * `setScale` can change the zoom mid-flight, an in-flight bitmap dispatched at
1322
+ * the OLD scale can still pass the on-resolution identity check if the SAME
1323
+ * slot object is re-mounted for page `i` (the pool reuses slot objects, so
1324
+ * `_slots.get(i) === slot && slot.renderedPage === i` can hold for an old
1325
+ * dispatch), and get painted at the WRONG resolution. We fix this with a render
1326
+ * epoch (`_renderEpoch`): each dispatch captures the epoch, and on resolution a
1327
+ * moved epoch ⇒ STALE (close + re-dispatch the live slot). See
1328
+ * `_renderSlotBitmap`. */
1329
+ private readonly _bitmapInFlight;
1330
+ /** Render generation, bumped on every effective `setScale` (and the resize
1331
+ * re-fit in `_onResize`, which routes through `setScale`). Stamped into each async render
1332
+ * dispatch; a resolution whose captured epoch ≠ this value is STALE — its
1333
+ * pixels/geometry are at a superseded scale. Worker path: close the orphan
1334
+ * bitmap + re-dispatch the live slot. Main path: skip the (stale) text-layer
1335
+ * build; the engine's per-canvas token already discards the stale pixels. */
1336
+ private _renderEpoch;
1337
+ /** Pending settle-render timer handle (design §7 mechanism 2). Set by
1338
+ * `_scheduleSettle` after each `setScale`, reset on the next one so a burst
1339
+ * dispatches ONE settle at the end, and cleared in `destroy()`. `ReturnType`
1340
+ * of `setTimeout` (a number in the DOM, a Timeout object in node) so the type
1341
+ * is host-agnostic. */
1342
+ private _settleTimer;
1343
+ private _wheelListener;
1344
+ /** One-shot latch for the worker-mode text-selection warning. The overlay is a
1345
+ * main-mode-only feature: in worker mode the per-run `onTextRun` geometry
1346
+ * cannot cross the worker boundary, so an `enableTextSelection` overlay stays
1347
+ * empty. We warn once (parity with `DocxViewer`) rather than per slot. */
1348
+ private _warnedNoTextSelection;
1349
+ /** Observes the container so a width change re-fits the base scale. Disconnected
1350
+ * in `destroy()`. */
1351
+ private _resizeObserver;
1352
+ /** The base fit scale at the last established/re-fit layout. `_onResize` divides
1353
+ * `_scale` by this to recover the current zoom multiplier so a width change
1354
+ * re-fits the base while preserving the user's zoom (design §11). */
1355
+ private _prevBase;
1356
+ /** The fit width (px) the base scale was last established at. Lets `_onResize`
1357
+ * skip the re-fit when only the height changed (a ResizeObserver fires on ANY
1358
+ * box change, but only a WIDTH change alters the fit-to-width base scale). */
1359
+ private _lastFitWidth;
1360
+ /** Resolved page-canvas `box-shadow` (design: the recipe drop shadow by
1361
+ * default). Resolved ONCE with `??` — NOT `||` — so `pageShadow: false`
1362
+ * survives as the "no shadow" sentinel (a `||` would treat `false` as absent
1363
+ * and wrongly re-apply the default). Applied by `_applyPageShadow` at EVERY
1364
+ * canvas-creation site (`_acquireSlot` and the double-buffer spare in
1365
+ * `_settleSlot`) so a recycled/re-mounted slot and a settle-swapped spare all
1366
+ * carry it. */
1367
+ private readonly _pageShadow;
1368
+ constructor(container: HTMLElement, opts?: DocxScrollViewerOptions);
1369
+ /**
1370
+ * Load a DOCX from URL or ArrayBuffer and render the first window.
1371
+ * UNSUPPORTED when an engine was injected via `opts.document` (throws) — the
1372
+ * caller already owns the parsed engine.
1373
+ */
1374
+ load(source: string | ArrayBuffer): Promise<void>;
1375
+ get pageCount(): number;
1376
+ /** CSS px width of page `i` at the current scale. */
1377
+ private _pageWidthPx;
1378
+ /** CSS px height of page `i` at the current scale. */
1379
+ private _pageHeightPx;
1380
+ /** The fit width (px), deferring when the container is unlaid-out. An EXPLICIT
1381
+ * `opts.width` is the page's CSS-width contract and is returned UNCHANGED (the
1382
+ * gutters still apply around placement, not to the width). The container-derived
1383
+ * default instead targets `containerWidth − padL − padR` so a page sits INSIDE
1384
+ * the horizontal gutters at 100%. A non-positive result (gutters wider than the
1385
+ * container) is treated as unlaid-out — the same deferral as a zero-width box. */
1386
+ private _fitWidthPx;
1387
+ /** Base scale: first page's width fit to the fit-width. Returns 0 when the
1388
+ * container has no width yet (deferral). */
1389
+ private _baseScale;
1390
+ /**
1391
+ * Recompute per-page heights + the spacer and re-mount the visible window.
1392
+ *
1393
+ * The viewer already calls this automatically after `load()`, an injected
1394
+ * engine, a container resize, and a zoom, so most integrations never need it.
1395
+ * It is public as a deliberate escape hatch: if the host mutates the layout in
1396
+ * a way the `ResizeObserver` cannot observe (e.g. a CSS change on an ancestor
1397
+ * that resizes the container without a box-size event, or a font that finishes
1398
+ * loading after first paint), call `relayout()` to force a re-fit. Idempotent —
1399
+ * safe to call repeatedly, and a no-op while the container has zero width (the
1400
+ * fit is deferred until width appears, design §11).
1401
+ */
1402
+ relayout(): void;
1403
+ private _recomputeHeights;
1404
+ private _gap;
1405
+ private _overscan;
1406
+ /** Desk padding fed to `computeVisibleRange`: `paddingTop`/`paddingBottom`,
1407
+ * each defaulting to `gap` (uniform rhythm). Resolved here (not stored) to
1408
+ * mirror `_gap()`/`_overscan()`, and consumed at EVERY `computeVisibleRange`
1409
+ * call site so the padded offsets are the single source of geometry. */
1410
+ private _pad;
1411
+ /** Horizontal desk gutters: `paddingLeft`/`paddingRight`, each defaulting to
1412
+ * `gap` (uniform rhythm — the horizontal gutters match the vertical padding).
1413
+ * Consumed by `_fitWidthPx` (to shrink the container-derived fit), by
1414
+ * `_positionSlot` (the flush-left floor), and by `_syncSpacer` (the spacer
1415
+ * width). Resolved here (not stored) to mirror `_gap()`/`_pad()`. */
1416
+ private _padH;
1417
+ private _range;
1418
+ private _syncSpacer;
1419
+ /** Horizontal scroll extent: the widest page (docx pages can differ in width)
1420
+ * plus both gutters. A spacer NARROWER than the container never creates a
1421
+ * scrollbar (scrollWidth = max(clientWidth, content)), so it is always safe to
1422
+ * set — it only matters when a zoomed-in page grows past the viewport, where it
1423
+ * gives the gutters something to scroll to on either side. Max over per-page
1424
+ * widths so the extent covers the widest page in the document. Called from
1425
+ * `_syncSpacer` and after every scale change (zoom / resize re-fit) so the
1426
+ * extent tracks the current page px width. */
1427
+ private _syncSpacerWidth;
1428
+ private _onScroll;
1429
+ /** Mount/recycle slots for the current visible window. */
1430
+ private _mountVisible;
1431
+ /** Apply the resolved page-canvas shadow (design: recipe drop shadow by
1432
+ * default, `false` ⇒ none). Single source so `_acquireSlot` and the
1433
+ * double-buffer spare in `_settleSlot` stay in lock-step — a spare that missed
1434
+ * this would lose the shadow on the settle swap. `box-shadow` never affects
1435
+ * layout, so this is safe to (re)set on a live/pooled canvas without shifting
1436
+ * any offset. */
1437
+ private _applyPageShadow;
1438
+ private _acquireSlot;
1439
+ private _recycleSlot;
1440
+ private _positionSlot;
1441
+ /** Device-pixel ratio for a render (opts override → window → 1). */
1442
+ private _dpr;
1443
+ /**
1444
+ * Render page `i` into `slot`. Routes strictly on the constructor-resolved
1445
+ * `_mode` (design §11 — no probing, no silent mis-pathing): `main` ⇒ paint the
1446
+ * slot's canvas directly via `renderPage`; `worker` ⇒ transfer an ImageBitmap
1447
+ * from `renderPageToBitmap`.
1448
+ *
1449
+ * Slot-identity guard: a slot recycled to a DIFFERENT page while a previous
1450
+ * render is in flight must not repaint the stale page. `slot.renderedPage`
1451
+ * tracks the page this slot is committed to; we stamp it up-front and bail on
1452
+ * resolution if it changed (the engine's own token guard is per-canvas; this is
1453
+ * the viewer's per-slot page-identity check).
1454
+ *
1455
+ * Render epoch (main path): pixel staleness after a mid-flight `setScale` is
1456
+ * already handled by the engine's per-canvas token (the newer renderPage on the
1457
+ * same canvas wins) — `setScale` recycles + re-mounts, and the re-mount always
1458
+ * re-dispatches `renderPage` (renderedPage reset to -1), so a fresh render is
1459
+ * always issued. But the viewer-side side effects of a STALE resolution — the
1460
+ * text-layer build (its run geometry is at the OLD scale) and the renderedPage
1461
+ * bookkeeping — must NOT run, or a superseded render would rebuild the overlay
1462
+ * with stale x/y/w/h (the pool reuses slot objects, so the identity check alone
1463
+ * can pass for an old-epoch resolution). We gate them on the captured epoch.
1464
+ */
1465
+ private _renderSlot;
1466
+ /** Warn once when an `enableTextSelection` overlay was requested but the render
1467
+ * mode is `worker` (so the overlay stays empty). Same wording as
1468
+ * `DocxViewer._render` — one warning per viewer, not per slot. */
1469
+ private _maybeWarnNoTextSelection;
1470
+ /** Route an async render failure to `onError`, or `console.error` when none is
1471
+ * set (so failures are never fully silent), and never after teardown. */
1472
+ private _reportRenderError;
1473
+ /**
1474
+ * Worker-mode slot render: dispatch `renderPageToBitmap`, transfer the result
1475
+ * via a per-slot `bitmaprenderer` context, and manage the ImageBitmap lifecycle.
1476
+ *
1477
+ * Coalescing / drop-stale (design §11):
1478
+ * - Skip if page `i` is already in flight (a scroll storm won't double-dispatch).
1479
+ * - Skip if page `i` already left the mounted window before dispatch.
1480
+ * - On resolution, if `slot` is no longer THIS page's live slot (it recycled to
1481
+ * another page, or page `i` re-mounted onto a DIFFERENT slot while this render
1482
+ * was in flight), close the orphan bitmap and skip the paint. In that
1483
+ * re-mount case a live slot for `i` still awaits a render, so once we clear
1484
+ * the in-flight guard we re-dispatch it — a page that recycled and re-mounted
1485
+ * mid-flight must never stay blank.
1486
+ * - RENDER EPOCH: the dispatch captures `this._renderEpoch`. `setScale` bumps
1487
+ * the epoch, so a resolution whose captured epoch ≠ the live epoch is STALE
1488
+ * even when the SAME slot object is still mounted for page `i` (the pool
1489
+ * reuses slot objects, so the identity check alone can't catch a zoom that
1490
+ * happened mid-flight). A moved epoch ⇒ close the orphan + re-dispatch the
1491
+ * live slot at the new scale, never paint the old-scale bitmap.
1492
+ */
1493
+ private _renderSlotBitmap;
1494
+ /**
1495
+ * Set the absolute px-per-pt zoom scale, clamped inline to
1496
+ * `[zoomMin ?? 0.1, zoomMax ?? 4]` (absolute bounds, XlsxViewer convention — NOT
1497
+ * multiples of the base fit; design §3 keeps the clamp in the viewer, not core),
1498
+ * then re-anchor VERTICALLY so the page currently under the viewport top stays
1499
+ * fixed. A no-op when nothing is loaded or when the clamped scale is unchanged.
1500
+ *
1501
+ * FLICKER-FREE (design §7): this does NOT re-render the visible pages inline.
1502
+ * It shows an immediate CSS preview (stretch the existing bitmaps, scale the
1503
+ * overlays) and DEBOUNCES a full-resolution settle re-render for ZOOM_SETTLE_MS,
1504
+ * so a wheel/pinch burst never blanks a page and coalesces into one crisp render.
1505
+ *
1506
+ * Re-anchor (written from scratch — XlsxViewer only re-anchors horizontally):
1507
+ * capture `top = topIndex` and the intra-page fraction `intraFrac` from the
1508
+ * CURRENT range BEFORE rescale; after recomputing heights at the new scale,
1509
+ * `newScrollTop = offsets'[top] + intraFrac × heights'[top]`, clamped to
1510
+ * `[0, totalHeight' − viewportHeight]`. Because a page's height scales linearly
1511
+ * with `_scale`, the same fractional position maps exactly to the new geometry.
1512
+ *
1513
+ * CAVEAT — base fit below the floor: `relayout()` sets `_scale = base` WITHOUT
1514
+ * clamping to `[zoomMin, zoomMax]`. If the base fit is below `zoomMin` (a wide
1515
+ * page in a narrow container), the initial scale sits under the floor, but once
1516
+ * the user zooms via `setScale` the clamp pins the minimum to `zoomMin`, so they
1517
+ * can no longer return below the floor to the original base fit through this API.
1518
+ */
1519
+ setScale(scale: number): void;
1520
+ /**
1521
+ * CSS preview of the visible window at the current `_scale` (design §7
1522
+ * mechanism 1), WITHOUT re-rendering. Slots leaving the window recycle normally;
1523
+ * slots ENTERING the window mount fresh (rendered at the current scale directly,
1524
+ * so they never need a preview); slots that STAY are repositioned and their
1525
+ * canvas + text overlay are CSS-transformed to the new size (the device buffer
1526
+ * is untouched — that is the whole point: no synchronous clear, no blank frame).
1527
+ */
1528
+ private _previewVisible;
1529
+ /**
1530
+ * CSS-preview a single already-mounted slot at the new geometry (design §7): the
1531
+ * wrapper is repositioned + sized (via `_positionSlot`), the canvas bitmap is
1532
+ * STRETCHED to the new CSS size (no `canvas.width` — the device buffer, and thus
1533
+ * the drawn pixels, are left intact, just scaled by the browser), and the text
1534
+ * overlay is scaled by `newScale / renderedScale` so it tracks the stretched
1535
+ * page. `renderedScale <= 0` means the slot's first render hasn't resolved yet
1536
+ * (nothing to stretch); the pending render captured the current scale, so it
1537
+ * lands correct and no preview is needed.
1538
+ */
1539
+ private _previewSlot;
1540
+ /** (Re)schedule the debounced settle re-render (design §7 mechanism 2). Resets
1541
+ * the timer on every call so a burst of `setScale` dispatches ONE settle
1542
+ * ZOOM_SETTLE_MS after the LAST call. Cleared in `destroy()`. */
1543
+ private _scheduleSettle;
1544
+ /** Full-resolution settle re-render of the visible window (design §7 mechanisms
1545
+ * 2+3). Re-renders each mounted slot at the current scale via the double-buffer
1546
+ * swap (main) / same-canvas transfer (worker). Main mode also rebuilds the text
1547
+ * overlay and clears its preview transform; in worker mode the overlay is
1548
+ * permanently empty (text selection is main-mode-only), so the transform is
1549
+ * inert there and is reset on recycle. Dispatched at the CURRENT epoch; the
1550
+ * existing epoch gate discards it if a later `setScale` supersedes it
1551
+ * mid-render. */
1552
+ private _settleRender;
1553
+ /**
1554
+ * Settle-render one slot at the current scale (design §7 mechanism 3).
1555
+ *
1556
+ * WORKER: re-dispatch the bitmap render into the SAME canvas. The worker path
1557
+ * sizes the device buffer and `transferFromImageBitmap`s it in ONE synchronous
1558
+ * step (no await between `canvas.width = …` and the transfer), so the browser
1559
+ * never composites an intermediate blank frame — no spare canvas is needed. The
1560
+ * `renderedScale === _scale` gate in `_settleRender` plus the epoch gate inside
1561
+ * `_renderSlotBitmap` keep this correct and idempotent.
1562
+ *
1563
+ * MAIN: `renderPage` (via renderDocumentToCanvas) synchronously sets
1564
+ * `canvas.width = …` (which CLEARS the backing store to blank) BEFORE its first
1565
+ * await and paints AFTER — so rendering into the on-screen canvas would flash it
1566
+ * white. Render into a SPARE off-DOM canvas instead; only once it resolves at the
1567
+ * current epoch do we swap it into the wrapper (replacing the old canvas, which is
1568
+ * DISCARDED — the pooled unit is the slot, not the canvas). The old canvas keeps
1569
+ * showing the stretched preview until the instant of the swap — blank-free.
1570
+ */
1571
+ private _settleSlot;
1572
+ /**
1573
+ * Scroll so page `index`'s top edge sits at the viewport top. Clamps `index` to
1574
+ * `[0, pageCount-1]` (the pager convention) and the resulting scrollTop to
1575
+ * `[0, totalHeight − viewportHeight]` so the last pages don't scroll past the
1576
+ * end. A no-op when nothing is loaded or the document is empty.
1577
+ *
1578
+ * `opts.behavior` ('auto' | 'smooth', default 'auto') is honoured via
1579
+ * `scrollHost.scrollTo({ top, behavior })` when the host supports it (a real
1580
+ * browser); the stub-DOM has no `scrollTo`, so the fallback sets `scrollTop`
1581
+ * directly (which is what the tests assert). We then call `_mountVisible` once.
1582
+ *
1583
+ * MOUNTING CAVEAT: synchronous mounting of the target page is guaranteed only on
1584
+ * the DEFAULT/'auto' path — there `scrollTop` has already jumped to `top`, so the
1585
+ * `_mountVisible` call reads the final scroll position and the target page's slots
1586
+ * exist immediately. With `behavior: 'smooth'` the scroll animates ASYNCHRONOUSLY:
1587
+ * `scrollTop` is still near the old position when `_mountVisible` runs, so the
1588
+ * target page mounts lazily via the animation's subsequent `scroll` events, not
1589
+ * from this call.
1590
+ */
1591
+ scrollToPage(index: number, opts?: {
1592
+ behavior?: 'auto' | 'smooth';
1593
+ }): void;
1594
+ /**
1595
+ * Re-fit the base scale on a container resize while PRESERVING the current zoom
1596
+ * multiplier (design §11), then re-anchor + re-render. A `ResizeObserver` fires
1597
+ * on any box change, but only a WIDTH change alters the fit-to-width base scale;
1598
+ * a height-only change skips the re-fit yet STILL re-mounts the visible window
1599
+ * (via `_mountVisible`), because a taller viewport reveals rows that were below
1600
+ * the fold and would otherwise stay blank until the next scroll. Empty/unloaded
1601
+ * ⇒ no-op; a still-zero width ⇒ defer.
1602
+ *
1603
+ * Zero-width recovery: a container that was 0-wide at construction never
1604
+ * established a scale (`_scaleEstablished` is false), so the first non-zero
1605
+ * resize establishes it here via `relayout()` — completing the T2 deferral.
1606
+ *
1607
+ * Re-fit math (zoom multiplier preserved):
1608
+ * mult = _scale / _prevBase (the user's zoom over the old base)
1609
+ * newScale = newBase × mult
1610
+ * Routing through `setScale(newScale)` bumps `_renderEpoch` (resize IS an epoch
1611
+ * event — T4 banner) and re-anchors + CSS-previews + debounces a settle re-render
1612
+ * of every slot at the new geometry, exactly like a zoom (design §7 flicker-free
1613
+ * path — a rapid ResizeObserver burst therefore also coalesces into one settle).
1614
+ * `setScale`'s clamp/no-op guards apply: an unchanged newScale (identical width)
1615
+ * is a no-op there — so we short-circuit BEFORE it when the fit-width is
1616
+ * unchanged (mounting the revealed window without a needless re-render), and
1617
+ * after it we call `_mountVisible` again to cover the case where the clamp made
1618
+ * `setScale` no-op yet the viewport still grew.
1619
+ */
1620
+ private _onResize;
1621
+ get topVisiblePage(): number;
1622
+ /* Excluded from this release type: mountedPageIndicesForTest */
1623
+ /* Excluded from this release type: scaleForTest */
1624
+ /* Excluded from this release type: baseScaleForTest */
1625
+ /* Excluded from this release type: renderEpochForTest */
1626
+ /* Excluded from this release type: resizeForTest */
1627
+ /**
1628
+ * Tear down the viewer: remove the DOM subtree and (only for a self-loaded
1629
+ * engine) destroy the engine. An injected engine is left intact — the caller
1630
+ * owns its lifecycle. Per-slot worker ImageBitmaps are closed on recycle.
1631
+ */
1632
+ destroy(): void;
1633
+ }
1634
+
1635
+ /**
1636
+ * Options for {@link DocxScrollViewer}. Extends `RenderPageOptions` (per-page
1637
+ * render knobs, minus `onTextRun`) and `LoadOptions` (parse/worker knobs). See
1638
+ * design §8.1.
1639
+ *
1640
+ * `onTextRun` is omitted deliberately: the viewer drives it internally per
1641
+ * mounted slot to build the optional per-page selection overlay (gated by
1642
+ * `enableTextSelection`), so exposing it here would let a caller's callback be
1643
+ * silently overridden.
1644
+ */
1645
+ declare interface DocxScrollViewerOptions extends Omit<RenderPageOptions, 'onTextRun'>, LoadOptions_4 {
1646
+ /** Base fit width in CSS px → base zoom scale. Default: the container's width
1647
+ * at first non-zero layout (design §7/§11 zero-width deferral). */
1648
+ width?: number;
1649
+ /** Vertical gap (px) between consecutive pages. Default 16. */
1650
+ gap?: number;
1651
+ /** Desk padding (px) ABOVE the FIRST page — the margin a PDF reader leaves
1652
+ * between the top of the scroll surface and the first sheet. Default: `gap`
1653
+ * (uniform desk rhythm — the first page sits the same distance from the top as
1654
+ * pages sit from each other). Pass `0` for a flush-top layout. */
1655
+ paddingTop?: number;
1656
+ /** Desk padding (px) BELOW the LAST page — the margin below the final sheet.
1657
+ * Default: `gap`. Pass `0` for a flush-bottom layout. */
1658
+ paddingBottom?: number;
1659
+ /** Desk gutter (px) to the LEFT of the pages — the horizontal margin between the
1660
+ * left edge of the scroll surface and a page sitting flush-left (i.e. once
1661
+ * zoomed wide enough that centering no longer applies). Default: `gap` (uniform
1662
+ * desk rhythm — the horizontal gutters match the vertical ones). It also shrinks
1663
+ * the container-derived FIT width so a page sits inside the gutters at 100%
1664
+ * (an EXPLICIT `opts.width` is the page's CSS-width contract and is NOT reduced;
1665
+ * the gutters still apply around placement). Pass `0` for a flush-left layout. */
1666
+ paddingLeft?: number;
1667
+ /** Desk gutter (px) to the RIGHT of the pages. Default: `gap`. Shrinks the
1668
+ * container-derived fit width symmetrically with `paddingLeft`. Pass `0` for a
1669
+ * flush-right layout. */
1670
+ paddingRight?: number;
1671
+ /** Pages kept mounted beyond the viewport on each side. Default 1. */
1672
+ overscan?: number;
1673
+ /** Per-page transparent text-selection overlay. MAIN render mode only:
1674
+ * in worker mode `onTextRun` cannot cross the worker boundary, so the overlay
1675
+ * stays empty and the viewer logs one warning (design §11). */
1676
+ enableTextSelection?: boolean;
1677
+ /** Minimum zoom scale (px-per-pt multiplier floor). Default 0.1. */
1678
+ zoomMin?: number;
1679
+ /** Maximum zoom scale. Default 4. */
1680
+ zoomMax?: number;
1681
+ /** Enable `Ctrl`/`Cmd`+wheel zoom. Default true. */
1682
+ enableZoom?: boolean;
1683
+ /**
1684
+ * CSS `background` shorthand for the scroll surface (the "desk") visible
1685
+ * behind and between pages — the gray a PDF reader paints around the sheet.
1686
+ * Applied to the viewer-owned scroll host. The pages themselves are always
1687
+ * drawn on the document's own white canvas and are unaffected. Default
1688
+ * `undefined`: the scroll surface stays transparent so the host container's
1689
+ * background shows through (non-breaking).
1690
+ */
1691
+ background?: string;
1692
+ /**
1693
+ * CSS `box-shadow` painted on every page CANVAS (not the wrapper — the
1694
+ * text-selection overlay must not cast its own shadow). The soft drop shadow a
1695
+ * PDF reader leaves under each sheet.
1696
+ *
1697
+ * - Default (`undefined`): `'0 1px 3px rgba(0,0,0,0.2)'` — the recipe look, so
1698
+ * the scroll viewer reproduces the Examples appearance with zero config.
1699
+ * - `false`: NO shadow (flat pages).
1700
+ * - A custom string is applied verbatim. A spread-only ring such as
1701
+ * `'0 0 0 1px #c8ccd0'` gives a crisp 1px BORDER look — and because
1702
+ * `box-shadow` never affects layout (unlike `border`, which would grow the
1703
+ * box and shift every offset), a border and a drop shadow are the SAME knob
1704
+ * here rather than two competing options.
1705
+ */
1706
+ pageShadow?: string | false;
1707
+ /**
1708
+ * Inject an already-loaded engine to share one parse across panes (design §14).
1709
+ * When set: `load()` is unsupported (throws), the engine's own `mode` wins (an
1710
+ * explicitly conflicting `opts.mode` throws at construction, design §11), and
1711
+ * `destroy()` does NOT destroy this engine (the caller owns its lifecycle).
1712
+ */
1713
+ document?: DocxDocument;
1714
+ /** Fires when the top-most visible page changes. `topIndex` from
1715
+ * `computeVisibleRange` (the first page intersecting the viewport top,
1716
+ * EXCLUDING overscan). */
1717
+ onVisiblePageChange?: (topIndex: number, total: number) => void;
1718
+ /** Error callback. When set, `load()` invokes it and resolves (otherwise the
1719
+ * error is rethrown — shared viewer error contract). It ALSO fires for async
1720
+ * per-slot render failures (both main `renderPage` and worker
1721
+ * `renderPageToBitmap` rejections); a failed page is left blank rather than
1722
+ * crashing the loop. Without an `onError`, render failures are logged via
1723
+ * `console.error` so they are never fully silent. */
1724
+ onError?: (err: Error) => void;
1725
+ }
1726
+
1438
1727
  declare interface DocxTextRun {
1439
1728
  text: string;
1440
1729
  bold: boolean;
@@ -1532,6 +1821,15 @@ declare class DocxViewer {
1532
1821
  private _currentPage;
1533
1822
  private _canvas;
1534
1823
  private _wrapper;
1824
+ /** The canvas's DOM position BEFORE the constructor reparented it into
1825
+ * {@link _wrapper}, captured so {@link destroy} can return the caller-owned
1826
+ * canvas to exactly where it was. `null` parent = canvas was passed
1827
+ * detached. */
1828
+ private _originalParent;
1829
+ private _originalNextSibling;
1830
+ /** The canvas's inline `display` before the constructor forced `block`
1831
+ * (empty string if it was unset), restored on {@link destroy}. */
1832
+ private _originalDisplay;
1535
1833
  private _textLayer;
1536
1834
  private _opts;
1537
1835
  private readonly _mode;
@@ -1556,7 +1854,15 @@ declare class DocxViewer {
1556
1854
  goToPage(index: number): Promise<void>;
1557
1855
  nextPage(): Promise<void>;
1558
1856
  prevPage(): Promise<void>;
1559
- /** Terminate the parser worker and release resources. */
1857
+ /**
1858
+ * Terminate the parser worker and release resources.
1859
+ *
1860
+ * The caller-owned `<canvas>` is returned to the DOM position it held before
1861
+ * the constructor was called (same parent, same next-sibling) and its inline
1862
+ * `display` is restored, so the canvas can be reused — e.g. to construct a new
1863
+ * viewer on the same element. If the canvas was passed detached (no parent) it
1864
+ * is simply removed from the internal wrapper. Safe to call more than once.
1865
+ */
1560
1866
  destroy(): void;
1561
1867
  private _render;
1562
1868
  private _buildTextLayer;
@@ -1603,19 +1909,8 @@ declare interface EquationRun {
1603
1909
  color?: string | null;
1604
1910
  }
1605
1911
 
1606
- declare interface ErrBars {
1607
- /** "x" | "y". */
1608
- dir: string;
1609
- /** "plus" | "minus" | "both". */
1610
- barType: string;
1611
- plus: (number | null)[];
1612
- minus: (number | null)[];
1613
- noEndCap: boolean;
1614
- /** Resolved RGB hex (no `#`). */
1615
- color?: string;
1616
- lineWidthEmu?: number;
1617
- dash?: string;
1618
- }
1912
+ /** @deprecated Use `ChartErrBars` from @silurus/ooxml-core. */
1913
+ declare type ErrBars = ChartErrBars;
1619
1914
 
1620
1915
  declare interface FieldRun {
1621
1916
  /** "page" | "numPages" | "other" */
@@ -1746,6 +2041,12 @@ declare interface HeadersFooters {
1746
2041
  even: HeaderFooter | null;
1747
2042
  }
1748
2043
 
2044
+ /** How {@link XlsxViewer} presents hidden sheets (`<sheet state>`, §18.2.19). */
2045
+ declare type HiddenSheetMode = 'show' | 'skip' | 'dim';
2046
+
2047
+ /** How {@link PptxViewer} presents hidden slides (`<p:sld show="0">`). */
2048
+ declare type HiddenSlideMode = 'show' | 'skip' | 'dim';
2049
+
1749
2050
  declare interface Hyperlink {
1750
2051
  col: number;
1751
2052
  row: number;
@@ -1951,15 +2252,6 @@ declare interface LegendManualLayout {
1951
2252
  h: number;
1952
2253
  }
1953
2254
 
1954
- declare interface LegendManualLayout_2 {
1955
- xMode: string;
1956
- yMode: string;
1957
- x: number;
1958
- y: number;
1959
- w: number;
1960
- h: number;
1961
- }
1962
-
1963
2255
  /**
1964
2256
  * `<a:lightRig>` — ECMA-376 §20.1.5.9 (`CT_LightRig`). Drives the bevel-lip
1965
2257
  * lighting (Phase B): `dir` selects the key-light octant.
@@ -2030,6 +2322,21 @@ declare interface LoadOptions_2 {
2030
2322
  * via `@font-face` in your application CSS.
2031
2323
  */
2032
2324
  useGoogleFonts?: boolean;
2325
+ /**
2326
+ * Override the URL the parser worker fetches the WebAssembly module from.
2327
+ *
2328
+ * By default each format resolves the `.wasm` asset that ships next to its
2329
+ * bundle (relative to the module URL), so no configuration is needed. Set
2330
+ * this to serve the parser WASM from a CDN or a self-hosted path instead — a
2331
+ * relative value is resolved against the current document URL. The same
2332
+ * dependency-injection contract across docx / pptx / xlsx.
2333
+ *
2334
+ * The referenced file must be the matching format's `*_parser_bg.wasm`
2335
+ * artifact (the one wasm-bindgen emitted for that parser); pointing it at a
2336
+ * mismatched or missing file makes `load()` reject when the worker
2337
+ * instantiates it.
2338
+ */
2339
+ wasmUrl?: string | URL;
2033
2340
  /**
2034
2341
  * Override the per-entry ZIP decompression cap (bytes) used by the zip-bomb
2035
2342
  * guard in the Rust parser. Defaults to 512 MiB. Raise it to load documents
@@ -2037,6 +2344,16 @@ declare interface LoadOptions_2 {
2037
2344
  * untrusted input. Zero / negative values fall back to the default.
2038
2345
  */
2039
2346
  maxZipEntryBytes?: number;
2347
+ /**
2348
+ * Reject the parse request if the parser worker does not answer within this
2349
+ * many milliseconds. Opt-in safety net for a wedged or crashed worker that
2350
+ * would otherwise leave `load()` pending forever. **Default: unlimited** —
2351
+ * parsing a large document with heavy embedded media can legitimately take
2352
+ * tens of seconds, so no timeout is imposed unless you set one. A worker that
2353
+ * throws or fails to load already rejects immediately regardless of this
2354
+ * value; this bound only covers the "silent, never-responds" case.
2355
+ */
2356
+ workerTimeoutMs?: number;
2040
2357
  /**
2041
2358
  * Opt-in OMML equation engine (MathJax + STIX Two Math, ~3 MB). Inject it
2042
2359
  * **once** here and every render of this document / presentation / workbook
@@ -2083,15 +2400,8 @@ declare interface LoadOptions_4 extends LoadOptions_2 {
2083
2400
  mode?: 'main' | 'worker';
2084
2401
  }
2085
2402
 
2086
- declare interface ManualLayout {
2087
- xMode: string;
2088
- yMode: string;
2089
- layoutTarget?: string;
2090
- x: number;
2091
- y: number;
2092
- w?: number;
2093
- h?: number;
2094
- }
2403
+ /** @deprecated Use `ChartManualLayout` from @silurus/ooxml-core. */
2404
+ declare type ManualLayout = ChartManualLayout;
2095
2405
 
2096
2406
  /** Accent (`m:acc`), e.g. hat, bar, vector arrow over the base. */
2097
2407
  declare interface MathAccent {
@@ -2608,6 +2918,9 @@ export declare namespace pptx {
2608
2918
  export {
2609
2919
  PptxViewer,
2610
2920
  PptxViewerOptions,
2921
+ HiddenSlideMode,
2922
+ PptxScrollViewer,
2923
+ PptxScrollViewerOptions,
2611
2924
  PptxPresentation,
2612
2925
  LoadOptions,
2613
2926
  RenderSlideOptions,
@@ -2616,6 +2929,7 @@ export declare namespace pptx {
2616
2929
  RenderOptions,
2617
2930
  PptxTextRunInfo,
2618
2931
  TextRunCallback,
2932
+ buildPptxTextLayer,
2619
2933
  PresentationHandle,
2620
2934
  autoResize,
2621
2935
  AutoResizeOptions,
@@ -2661,7 +2975,8 @@ export declare namespace pptx {
2661
2975
  TextRunData,
2662
2976
  LineBreak,
2663
2977
  ChartModel,
2664
- ChartSeries
2978
+ ChartSeries,
2979
+ DimOptions
2665
2980
  }
2666
2981
  }
2667
2982
 
@@ -2721,6 +3036,11 @@ declare class PptxPresentation {
2721
3036
  get slideWidth(): number;
2722
3037
  /** Slide height in EMU. */
2723
3038
  get slideHeight(): number;
3039
+ /** The render mode this engine was loaded with ('main' | 'worker'). A fact for
3040
+ * integrators and the scroll viewer: an injected engine's mode decides whether
3041
+ * slides render via renderSlide (main) or renderSlideToBitmap (worker) — no
3042
+ * probing (design §11: no silent mis-pathing). */
3043
+ get mode(): 'main' | 'worker';
2724
3044
  /**
2725
3045
  * Speaker-notes text for a slide (`ppt/notesSlides/notesSlideN.xml`,
2726
3046
  * ECMA-376 §13.3.5 — Notes Slide). Returns the notes-body text as a single
@@ -2741,6 +3061,14 @@ declare class PptxPresentation {
2741
3061
  * }
2742
3062
  */
2743
3063
  getNotes(slideIndex: number): string | null;
3064
+ /**
3065
+ * Whether the slide at `slideIndex` (0-based, absolute) is marked hidden
3066
+ * (`<p:sld show="0">`, ECMA-376 §19.3.1.38). Like {@link getNotes} the index
3067
+ * is NOT clamped — out-of-range / non-integer ⇒ `false`. This is a *fact*
3068
+ * about the model; deciding what to do with a hidden slide (skip / dim) is the
3069
+ * caller's policy (see {@link PptxViewer}'s `hiddenSlideMode` modes).
3070
+ */
3071
+ isHidden(slideIndex: number): boolean;
2744
3072
  /** Render a slide onto the given canvas. */
2745
3073
  renderSlide(canvas: HTMLCanvasElement | OffscreenCanvas, slideIndex: number, opts?: RenderSlideOptions): Promise<void>;
2746
3074
  /**
@@ -2778,6 +3106,475 @@ declare class PptxPresentation {
2778
3106
  destroy(): void;
2779
3107
  }
2780
3108
 
3109
+ declare class PptxScrollViewer {
3110
+ private _pres;
3111
+ private readonly _injected;
3112
+ private readonly _opts;
3113
+ private readonly _container;
3114
+ private readonly _wrapper;
3115
+ private readonly _scrollHost;
3116
+ private readonly _spacer;
3117
+ /** Resolved render mode. When an engine is injected the engine's own `mode`
3118
+ * is authoritative (design §11 — no silent mis-pathing / no probing); an
3119
+ * explicitly conflicting `opts.mode` is rejected at construction. When self-
3120
+ * loading, `opts.mode` decides and `load()` passes it to `PptxPresentation.load`. */
3121
+ private _mode;
3122
+ /** Dimensionless zoom multiplier over the 96-dpi natural slide size (mirrors
3123
+ * `DocxScrollViewer`, whose `_scale` multiplies `widthPt × PT_TO_PX`). The
3124
+ * natural (1×) slide width in CSS px is `slideEmu / EMU_PER_PX`; the base fit
3125
+ * sets `_scale` so that natural width maps to the container width, and zoom
3126
+ * multiplies it further (design §7). */
3127
+ private _scale;
3128
+ /** Whether the base fit scale has been established. Set true the first time
3129
+ * `relayout()` resolves a positive base scale. We use an explicit flag rather
3130
+ * than a `_scale === 1` sentinel because a fit scale of exactly 1 is a valid
3131
+ * established state (a 1× fit would otherwise be re-fit forever). */
3132
+ private _scaleEstablished;
3133
+ /** Live slots keyed by slide index. */
3134
+ private readonly _slots;
3135
+ /** Recyclable detached slots (canvas + textLayer reused across slides). */
3136
+ private readonly _free;
3137
+ /** Cached per-slide heights in px at the current scale (index-aligned). All
3138
+ * slides are the same size, so every entry equals the uniform slide height. */
3139
+ private _heights;
3140
+ private _lastRange;
3141
+ private _lastTopIndex;
3142
+ private _scrollListener;
3143
+ /** Set by `destroy()`. Async render callbacks (main + worker) check it before
3144
+ * reporting an error so a rejection that lands after teardown is swallowed
3145
+ * rather than surfaced to a `onError` on a dead viewer. */
3146
+ private _destroyed;
3147
+ /** Worker mode: slide indices whose bitmap render is currently dispatched to the
3148
+ * engine. Coalesces a scroll storm — we never dispatch a second render for a
3149
+ * slide whose first is still in flight — and lets us drop slides that scrolled
3150
+ * out of the window before dispatch (design §11 worker coalescing).
3151
+ *
3152
+ * T4 ZOOM HAZARD (RESOLVED by the render epoch below): coalescing keys on slide
3153
+ * INDEX only, with no notion of the scale a dispatch was made at. Once
3154
+ * `setScale` can change the zoom mid-flight, an in-flight bitmap dispatched at
3155
+ * the OLD scale can still pass the on-resolution identity check if the SAME
3156
+ * slot object is re-mounted for slide `i` (the pool reuses slot objects, so
3157
+ * `_slots.get(i) === slot && slot.renderedSlide === i` can hold for an old
3158
+ * dispatch), and get painted at the WRONG resolution. We fix this with a render
3159
+ * epoch (`_renderEpoch`): each dispatch captures the epoch, and on resolution a
3160
+ * moved epoch ⇒ STALE (close + re-dispatch the live slot). See
3161
+ * `_renderSlotBitmap`. */
3162
+ private readonly _slideInFlight;
3163
+ /** Render generation, bumped on every effective `setScale` (and the resize
3164
+ * re-fit in `_onResize`, which routes through `setScale`). Stamped into each async render
3165
+ * dispatch; a resolution whose captured epoch ≠ this value is STALE — its
3166
+ * pixels/geometry are at a superseded scale. Worker path: close the orphan
3167
+ * bitmap + re-dispatch the live slot. Main path: skip the (stale) text-layer
3168
+ * build; the engine's per-canvas token already discards the stale pixels. */
3169
+ private _renderEpoch;
3170
+ /** Pending settle-render timer handle (design §7 mechanism 2). Set by
3171
+ * `_scheduleSettle` after each `setScale`, reset on the next one so a burst
3172
+ * dispatches ONE settle at the end, and cleared in `destroy()`. `ReturnType`
3173
+ * of `setTimeout` (a number in the DOM, a Timeout object in node) so the type
3174
+ * is host-agnostic. */
3175
+ private _settleTimer;
3176
+ private _wheelListener;
3177
+ /** One-shot latch for the worker-mode text-selection warning. The overlay is a
3178
+ * main-mode-only feature: in worker mode the per-run `onTextRun` geometry
3179
+ * cannot cross the worker boundary, so an `enableTextSelection` overlay stays
3180
+ * empty. We warn once (parity with `PptxViewer`) rather than per slot. */
3181
+ private _warnedNoTextSelection;
3182
+ /** Observes the container so a width change re-fits the base scale. Disconnected
3183
+ * in `destroy()`. */
3184
+ private _resizeObserver;
3185
+ /** The base fit scale at the last established/re-fit layout. `_onResize` divides
3186
+ * `_scale` by this to recover the current zoom multiplier so a width change
3187
+ * re-fits the base while preserving the user's zoom (design §11). */
3188
+ private _prevBase;
3189
+ /** The fit width (px) the base scale was last established at. Lets `_onResize`
3190
+ * skip the re-fit when only the height changed (a ResizeObserver fires on ANY
3191
+ * box change, but only a WIDTH change alters the fit-to-width base scale). */
3192
+ private _lastFitWidth;
3193
+ /** Resolved slide-canvas `box-shadow` (design: the recipe drop shadow by
3194
+ * default). Resolved ONCE with `??` — NOT `||` — so `pageShadow: false`
3195
+ * survives as the "no shadow" sentinel (a `||` would treat `false` as absent
3196
+ * and wrongly re-apply the default). Applied by `_applyPageShadow` at EVERY
3197
+ * canvas-creation site (`_acquireSlot` and the double-buffer spare in
3198
+ * `_settleSlot`) so a recycled/re-mounted slot and a settle-swapped spare all
3199
+ * carry it. */
3200
+ private readonly _pageShadow;
3201
+ constructor(container: HTMLElement, opts?: PptxScrollViewerOptions);
3202
+ /**
3203
+ * Load a PPTX from URL or ArrayBuffer and render the first window.
3204
+ * UNSUPPORTED when an engine was injected via `opts.presentation` (throws) — the
3205
+ * caller already owns the parsed engine.
3206
+ */
3207
+ load(source: string | ArrayBuffer): Promise<void>;
3208
+ get slideCount(): number;
3209
+ /** Uniform slide width in CSS px at the current scale. `_scale` is a
3210
+ * dimensionless multiplier over the natural 96-dpi width (`slideEmu /
3211
+ * EMU_PER_PX`), mirroring docx's `widthPt × PT_TO_PX × _scale`. */
3212
+ private _slideWidthPx;
3213
+ /** Uniform slide height in CSS px at the current scale. */
3214
+ private _slideHeightPx;
3215
+ /** The fit width (px), deferring when the container is unlaid-out. An EXPLICIT
3216
+ * `opts.width` is the slide's CSS-width contract and is returned UNCHANGED (the
3217
+ * gutters still apply around placement, not to the width). The container-derived
3218
+ * default instead targets `containerWidth − padL − padR` so a slide sits INSIDE
3219
+ * the horizontal gutters at 100%. A non-positive result (gutters wider than the
3220
+ * container) is treated as unlaid-out — the same deferral as a zero-width box. */
3221
+ private _fitWidthPx;
3222
+ /** Base scale: the DIMENSIONLESS multiplier that fits the (uniform) slide
3223
+ * width to the fit-width. `natural = slideWidthEmu / EMU_PER_PX` is the 96-dpi
3224
+ * CSS-px width; `base = fitWidth / natural` (mirrors docx's `w / (widthPt ×
3225
+ * PT_TO_PX)`). Returns 0 when the container has no width yet (deferral). */
3226
+ private _baseScale;
3227
+ /**
3228
+ * Recompute per-slide heights + the spacer and re-mount the visible window.
3229
+ *
3230
+ * The viewer already calls this automatically after `load()`, an injected
3231
+ * engine, a container resize, and a zoom, so most integrations never need it.
3232
+ * It is public as a deliberate escape hatch: if the host mutates the layout in
3233
+ * a way the `ResizeObserver` cannot observe (e.g. a CSS change on an ancestor
3234
+ * that resizes the container without a box-size event, or a font that finishes
3235
+ * loading after first paint), call `relayout()` to force a re-fit. Idempotent —
3236
+ * safe to call repeatedly, and a no-op while the container has zero width (the
3237
+ * fit is deferred until width appears, design §11).
3238
+ */
3239
+ relayout(): void;
3240
+ /** All slides are the same size, so heights = n × uniform. We still feed this
3241
+ * full array to computeVisibleRange (never special-case uniform) so offsets /
3242
+ * topIndex live in one tested place (design §5.1). */
3243
+ private _recomputeHeights;
3244
+ private _gap;
3245
+ private _overscan;
3246
+ /** Desk padding fed to `computeVisibleRange`: `paddingTop`/`paddingBottom`,
3247
+ * each defaulting to `gap` (uniform rhythm). Resolved here (not stored) to
3248
+ * mirror `_gap()`/`_overscan()`, and consumed at EVERY `computeVisibleRange`
3249
+ * call site so the padded offsets are the single source of geometry. */
3250
+ private _pad;
3251
+ /** Horizontal desk gutters: `paddingLeft`/`paddingRight`, each defaulting to
3252
+ * `gap` (uniform rhythm — the horizontal gutters match the vertical padding).
3253
+ * Consumed by `_fitWidthPx` (to shrink the container-derived fit), by
3254
+ * `_positionSlot` (the flush-left floor), and by `_syncSpacerWidth` (the spacer
3255
+ * width). Resolved here (not stored) to mirror `_gap()`/`_pad()`. */
3256
+ private _padH;
3257
+ private _range;
3258
+ private _syncSpacer;
3259
+ /** Horizontal scroll extent: the (uniform deck-wide) slide width plus both
3260
+ * gutters. A spacer NARROWER than the container never creates a scrollbar
3261
+ * (scrollWidth = max(clientWidth, content)), so it is always safe to set — it
3262
+ * only matters when a zoomed-in slide grows past the viewport, where it gives
3263
+ * the gutters something to scroll to on either side. Called from `_syncSpacer`
3264
+ * and after every scale change (zoom / resize re-fit) so the extent tracks the
3265
+ * current slide px width. */
3266
+ private _syncSpacerWidth;
3267
+ private _onScroll;
3268
+ /** Mount/recycle slots for the current visible window. */
3269
+ private _mountVisible;
3270
+ /** Apply the resolved slide-canvas shadow (design: recipe drop shadow by
3271
+ * default, `false` ⇒ none). Single source so `_acquireSlot` and the
3272
+ * double-buffer spare in `_settleSlot` stay in lock-step — a spare that missed
3273
+ * this would lose the shadow on the settle swap. `box-shadow` never affects
3274
+ * layout, so this is safe to (re)set on a live/pooled canvas without shifting
3275
+ * any offset. */
3276
+ private _applyPageShadow;
3277
+ private _acquireSlot;
3278
+ private _recycleSlot;
3279
+ private _positionSlot;
3280
+ /** Device-pixel ratio for a render (opts override → window → 1). */
3281
+ private _dpr;
3282
+ /**
3283
+ * Render slide `i` into `slot`. Routes strictly on the constructor-resolved
3284
+ * `_mode` (design §11 — no probing, no silent mis-pathing): `main` ⇒ paint the
3285
+ * slot's canvas directly via `renderSlide`; `worker` ⇒ transfer an ImageBitmap
3286
+ * from `renderSlideToBitmap`.
3287
+ *
3288
+ * Slot-identity guard: a slot recycled to a DIFFERENT slide while a previous
3289
+ * render is in flight must not repaint the stale slide. `slot.renderedSlide`
3290
+ * tracks the slide this slot is committed to; we stamp it up-front and bail on
3291
+ * resolution if it changed (the engine's own token guard is per-canvas; this is
3292
+ * the viewer's per-slot slide-identity check).
3293
+ *
3294
+ * Render epoch (main path): pixel staleness after a mid-flight `setScale` is
3295
+ * already handled by the engine's per-canvas token (the newer renderSlide on the
3296
+ * same canvas wins) — `setScale` recycles + re-mounts, and the re-mount always
3297
+ * re-dispatches `renderSlide` (renderedSlide reset to -1), so a fresh render is
3298
+ * always issued. But the viewer-side side effects of a STALE resolution — the
3299
+ * text-layer build (its run geometry is at the OLD scale) and the renderedSlide
3300
+ * bookkeeping — must NOT run, or a superseded render would rebuild the overlay
3301
+ * with stale x/y/w/h (the pool reuses slot objects, so the identity check alone
3302
+ * can pass for an old-epoch resolution). We gate them on the captured epoch.
3303
+ */
3304
+ private _renderSlot;
3305
+ /** Warn once when an `enableTextSelection` overlay was requested but the render
3306
+ * mode is `worker` (so the overlay stays empty). Same wording as
3307
+ * `PptxViewer` — one warning per viewer, not per slot. */
3308
+ private _maybeWarnNoTextSelection;
3309
+ /** Route an async render failure to `onError`, or `console.error` when none is
3310
+ * set (so failures are never fully silent), and never after teardown. */
3311
+ private _reportRenderError;
3312
+ /**
3313
+ * Worker-mode slot render: dispatch `renderSlideToBitmap`, transfer the result
3314
+ * via a per-slot `bitmaprenderer` context, and manage the ImageBitmap lifecycle.
3315
+ *
3316
+ * Coalescing / drop-stale (design §11):
3317
+ * - Skip if slide `i` is already in flight (a scroll storm won't double-dispatch).
3318
+ * - Skip if slide `i` already left the mounted window before dispatch.
3319
+ * - On resolution, if `slot` is no longer THIS slide's live slot (it recycled to
3320
+ * another slide, or slide `i` re-mounted onto a DIFFERENT slot while this render
3321
+ * was in flight), close the orphan bitmap and skip the paint. In that
3322
+ * re-mount case a live slot for `i` still awaits a render, so once we clear
3323
+ * the in-flight guard we re-dispatch it — a slide that recycled and re-mounted
3324
+ * mid-flight must never stay blank.
3325
+ * - RENDER EPOCH: the dispatch captures `this._renderEpoch`. `setScale` bumps
3326
+ * the epoch, so a resolution whose captured epoch ≠ the live epoch is STALE
3327
+ * even when the SAME slot object is still mounted for slide `i` (the pool
3328
+ * reuses slot objects, so the identity check alone can't catch a zoom that
3329
+ * happened mid-flight). A moved epoch ⇒ close the orphan + re-dispatch the
3330
+ * live slot at the new scale, never paint the old-scale bitmap.
3331
+ *
3332
+ * Do NOT pass `dim` or `skipMediaControls` to `renderSlideToBitmap`. The scroll
3333
+ * viewer never dims slides (design §8.2 / Delta 6); passing neither means the
3334
+ * static play-badge renders on media slides (matching `PptxViewer`'s
3335
+ * non-media-playback path) — acceptable for v1.
3336
+ */
3337
+ private _renderSlotBitmap;
3338
+ /**
3339
+ * Set the absolute (dimensionless) zoom scale — a multiplier over the 96-dpi
3340
+ * natural slide size, matching `DocxScrollViewer` — clamped inline to
3341
+ * `[zoomMin ?? 0.1, zoomMax ?? 4]` (absolute bounds, XlsxViewer convention — NOT
3342
+ * multiples of the base fit; design §3 keeps the clamp in the viewer, not core),
3343
+ * then re-anchor VERTICALLY so the slide currently under the viewport top stays
3344
+ * fixed. A no-op when nothing is loaded or when the clamped scale is unchanged.
3345
+ *
3346
+ * FLICKER-FREE (design §7): this does NOT re-render the visible slides inline.
3347
+ * It shows an immediate CSS preview (stretch the existing bitmaps, scale the
3348
+ * overlays) and DEBOUNCES a full-resolution settle re-render for ZOOM_SETTLE_MS,
3349
+ * so a wheel/pinch burst never blanks a slide and coalesces into one crisp render.
3350
+ *
3351
+ * Re-anchor (written from scratch — XlsxViewer only re-anchors horizontally):
3352
+ * capture `top = topIndex` and the intra-slide fraction `intraFrac` from the
3353
+ * CURRENT range BEFORE rescale; after recomputing heights at the new scale,
3354
+ * `newScrollTop = offsets'[top] + intraFrac × heights'[top]`, clamped to
3355
+ * `[0, totalHeight' − viewportHeight]`. Because a slide's height scales linearly
3356
+ * with `_scale`, the same fractional position maps exactly to the new geometry.
3357
+ *
3358
+ * CAVEAT — base fit below the floor: `relayout()` sets `_scale = base` WITHOUT
3359
+ * clamping to `[zoomMin, zoomMax]`. If the base fit is below `zoomMin` (a wide
3360
+ * slide in a narrow container), the initial scale sits under the floor, but once
3361
+ * the user zooms via `setScale` the clamp pins the minimum to `zoomMin`, so they
3362
+ * can no longer return below the floor to the original base fit through this API.
3363
+ */
3364
+ setScale(scale: number): void;
3365
+ /**
3366
+ * CSS preview of the visible window at the current `_scale` (design §7
3367
+ * mechanism 1), WITHOUT re-rendering. Slots leaving the window recycle normally;
3368
+ * slots ENTERING the window mount fresh (rendered at the current scale directly,
3369
+ * so they never need a preview); slots that STAY are repositioned and their
3370
+ * canvas + text overlay are CSS-transformed to the new size (the device buffer
3371
+ * is untouched — that is the whole point: no synchronous clear, no blank frame).
3372
+ */
3373
+ private _previewVisible;
3374
+ /**
3375
+ * CSS-preview a single already-mounted slot at the new geometry (design §7): the
3376
+ * wrapper is repositioned + sized (via `_positionSlot`), the canvas bitmap is
3377
+ * STRETCHED to the new CSS size (no `canvas.width` — the device buffer, and thus
3378
+ * the drawn pixels, are left intact, just scaled by the browser), and the text
3379
+ * overlay is scaled by `newScale / renderedScale` so it tracks the stretched
3380
+ * slide. `renderedScale <= 0` means the slot's first render hasn't resolved yet
3381
+ * (nothing to stretch); the pending render captured the current scale, so it
3382
+ * lands correct and no preview is needed.
3383
+ */
3384
+ private _previewSlot;
3385
+ /** (Re)schedule the debounced settle re-render (design §7 mechanism 2). Resets
3386
+ * the timer on every call so a burst of `setScale` dispatches ONE settle
3387
+ * ZOOM_SETTLE_MS after the LAST call. Cleared in `destroy()`. */
3388
+ private _scheduleSettle;
3389
+ /** Full-resolution settle re-render of the visible window (design §7 mechanisms
3390
+ * 2+3). Re-renders each mounted slot at the current scale via the double-buffer
3391
+ * swap (main) / same-canvas transfer (worker). Main mode also rebuilds the text
3392
+ * overlay and clears its preview transform; in worker mode the overlay is
3393
+ * permanently empty (text selection is main-mode-only), so the transform is
3394
+ * inert there and is reset on recycle. Dispatched at the CURRENT epoch; the
3395
+ * existing epoch gate discards it if a later `setScale` supersedes it
3396
+ * mid-render. */
3397
+ private _settleRender;
3398
+ /**
3399
+ * Settle-render one slot at the current scale (design §7 mechanism 3).
3400
+ *
3401
+ * WORKER: re-dispatch the bitmap render into the SAME canvas. The worker path
3402
+ * sizes the device buffer and `transferFromImageBitmap`s it in ONE synchronous
3403
+ * step (no await between `canvas.width = …` and the transfer), so the browser
3404
+ * never composites an intermediate blank frame — no spare canvas is needed. The
3405
+ * `renderedScale === _scale` gate in `_settleRender` plus the epoch gate inside
3406
+ * `_renderSlotBitmap` keep this correct and idempotent.
3407
+ *
3408
+ * MAIN: `renderSlide` synchronously sets `canvas.width = …` (which CLEARS the
3409
+ * backing store to blank) BEFORE its first await and paints AFTER — so rendering
3410
+ * into the on-screen canvas would flash it white. Render into a SPARE off-DOM
3411
+ * canvas instead; only once it resolves at the current epoch do we swap it into
3412
+ * the wrapper (replacing the old canvas). The old canvas keeps showing the
3413
+ * stretched preview until the instant of the swap — blank-free.
3414
+ */
3415
+ private _settleSlot;
3416
+ /**
3417
+ * Scroll so slide `index`'s top edge sits at the viewport top. Clamps `index` to
3418
+ * `[0, slideCount-1]` (the pager convention) and the resulting scrollTop to
3419
+ * `[0, totalHeight − viewportHeight]` so the last slides don't scroll past the
3420
+ * end. A no-op when nothing is loaded or the deck is empty.
3421
+ *
3422
+ * `opts.behavior` ('auto' | 'smooth', default 'auto') is honoured via
3423
+ * `scrollHost.scrollTo({ top, behavior })` when the host supports it (a real
3424
+ * browser); the stub-DOM has no `scrollTo`, so the fallback sets `scrollTop`
3425
+ * directly (which is what the tests assert). We then call `_mountVisible` once.
3426
+ *
3427
+ * MOUNTING CAVEAT: synchronous mounting of the target slide is guaranteed only on
3428
+ * the DEFAULT/'auto' path — there `scrollTop` has already jumped to `top`, so the
3429
+ * `_mountVisible` call reads the final scroll position and the target slide's slots
3430
+ * exist immediately. With `behavior: 'smooth'` the scroll animates ASYNCHRONOUSLY:
3431
+ * `scrollTop` is still near the old position when `_mountVisible` runs, so the
3432
+ * target slide mounts lazily via the animation's subsequent `scroll` events, not
3433
+ * from this call.
3434
+ */
3435
+ scrollToSlide(index: number, opts?: {
3436
+ behavior?: 'auto' | 'smooth';
3437
+ }): void;
3438
+ /**
3439
+ * Re-fit the base scale on a container resize while PRESERVING the current zoom
3440
+ * multiplier (design §11), then re-anchor + re-render. A `ResizeObserver` fires
3441
+ * on any box change, but only a WIDTH change alters the fit-to-width base scale;
3442
+ * a height-only change skips the re-fit yet STILL re-mounts the visible window
3443
+ * (via `_mountVisible`), because a taller viewport reveals rows that were below
3444
+ * the fold and would otherwise stay blank until the next scroll. Empty/unloaded
3445
+ * ⇒ no-op; a still-zero width ⇒ defer.
3446
+ *
3447
+ * Zero-width recovery: a container that was 0-wide at construction never
3448
+ * established a scale (`_scaleEstablished` is false), so the first non-zero
3449
+ * resize establishes it here via `relayout()` — completing the T2 deferral.
3450
+ *
3451
+ * Re-fit math (zoom multiplier preserved):
3452
+ * mult = _scale / _prevBase (the user's zoom over the old base)
3453
+ * newScale = newBase × mult
3454
+ * Routing through `setScale(newScale)` bumps `_renderEpoch` (resize IS an epoch
3455
+ * event — T4 banner) and re-anchors + CSS-previews + debounces a settle re-render
3456
+ * of every slot at the new geometry, exactly like a zoom (design §7 flicker-free
3457
+ * path — a rapid ResizeObserver burst therefore also coalesces into one settle).
3458
+ * `setScale`'s clamp/no-op guards apply: an unchanged newScale (identical width)
3459
+ * is a no-op there — so we short-circuit BEFORE it when the fit-width is
3460
+ * unchanged (mounting the revealed window without a needless re-render), and
3461
+ * after it we call `_mountVisible` again to cover the case where the clamp made
3462
+ * `setScale` no-op yet the viewport still grew.
3463
+ */
3464
+ private _onResize;
3465
+ get topVisibleSlide(): number;
3466
+ /* Excluded from this release type: mountedSlideIndicesForTest */
3467
+ /* Excluded from this release type: scaleForTest */
3468
+ /* Excluded from this release type: baseScaleForTest */
3469
+ /* Excluded from this release type: renderEpochForTest */
3470
+ /* Excluded from this release type: resizeForTest */
3471
+ /**
3472
+ * Tear down the viewer: remove the DOM subtree and (only for a self-loaded
3473
+ * engine) destroy the engine. An injected engine is left intact — the caller
3474
+ * owns its lifecycle. Per-slot worker ImageBitmaps are closed on recycle.
3475
+ */
3476
+ destroy(): void;
3477
+ }
3478
+
3479
+ /**
3480
+ * Options for {@link PptxScrollViewer}. Extends `RenderSlideOptions` (per-slide
3481
+ * render knobs, minus `onTextRun`) and `LoadOptions` (parse/worker knobs). See
3482
+ * design §8.2.
3483
+ *
3484
+ * `onTextRun` is omitted deliberately: the viewer drives it internally per
3485
+ * mounted slot to build the optional per-slide selection overlay (gated by
3486
+ * `enableTextSelection`), so exposing it here would let a caller's callback be
3487
+ * silently overridden.
3488
+ *
3489
+ * NOTE: `RenderSlideOptions` also carries `dim` and `skipMediaControls`. The v1
3490
+ * scroll viewer never sets `dim` or `skipMediaControls` (hidden-slide dimming is
3491
+ * a PAGER policy, not a scroll-viewer feature — design §8.2 / Delta 6). These
3492
+ * inherited fields are accepted for type-compatibility but are not part of the
3493
+ * scroll-viewer's supported API.
3494
+ */
3495
+ declare interface PptxScrollViewerOptions extends Omit<RenderSlideOptions, 'onTextRun'>, LoadOptions {
3496
+ /** Base fit width in CSS px → base zoom scale. Default: the container's width
3497
+ * at first non-zero layout (design §7/§11 zero-width deferral). */
3498
+ width?: number;
3499
+ /** Vertical gap (px) between consecutive slides. Default 16. */
3500
+ gap?: number;
3501
+ /** Desk padding (px) ABOVE the FIRST slide — the margin a presentation viewer
3502
+ * leaves between the top of the scroll surface and the first slide. Default:
3503
+ * `gap` (uniform desk rhythm — the first slide sits the same distance from the
3504
+ * top as slides sit from each other). Pass `0` for a flush-top layout. */
3505
+ paddingTop?: number;
3506
+ /** Desk padding (px) BELOW the LAST slide — the margin below the final slide.
3507
+ * Default: `gap`. Pass `0` for a flush-bottom layout. */
3508
+ paddingBottom?: number;
3509
+ /** Desk gutter (px) to the LEFT of the slides — the horizontal margin between
3510
+ * the left edge of the scroll surface and a slide sitting flush-left (i.e. once
3511
+ * zoomed wide enough that centering no longer applies). Default: `gap` (uniform
3512
+ * desk rhythm — the horizontal gutters match the vertical ones). It also shrinks
3513
+ * the container-derived FIT width so a slide sits inside the gutters at 100%
3514
+ * (an EXPLICIT `opts.width` is the slide's CSS-width contract and is NOT reduced;
3515
+ * the gutters still apply around placement). Pass `0` for a flush-left layout. */
3516
+ paddingLeft?: number;
3517
+ /** Desk gutter (px) to the RIGHT of the slides. Default: `gap`. Shrinks the
3518
+ * container-derived fit width symmetrically with `paddingLeft`. Pass `0` for a
3519
+ * flush-right layout. */
3520
+ paddingRight?: number;
3521
+ /** Slides kept mounted beyond the viewport on each side. Default 1. */
3522
+ overscan?: number;
3523
+ /** Per-slide transparent text-selection overlay. MAIN render mode only:
3524
+ * in worker mode `onTextRun` cannot cross the worker boundary, so the overlay
3525
+ * stays empty and the viewer logs one warning (design §11). */
3526
+ enableTextSelection?: boolean;
3527
+ /** Minimum zoom scale — a DIMENSIONLESS multiplier over the 96-dpi natural
3528
+ * slide size (10% = 0.1), matching `DocxScrollViewer`. Default 0.1. */
3529
+ zoomMin?: number;
3530
+ /** Maximum zoom scale (dimensionless multiplier, 400% = 4). Default 4. */
3531
+ zoomMax?: number;
3532
+ /** Enable `Ctrl`/`Cmd`+wheel zoom. Default true. */
3533
+ enableZoom?: boolean;
3534
+ /**
3535
+ * CSS `background` shorthand for the scroll surface (the "desk") visible
3536
+ * behind and between slides — the gray a presentation viewer paints around the
3537
+ * slide. Applied to the viewer-owned scroll host. The slides themselves are
3538
+ * always drawn on their own white canvas and are unaffected. Default
3539
+ * `undefined`: the scroll surface stays transparent so the host container's
3540
+ * background shows through (non-breaking).
3541
+ */
3542
+ background?: string;
3543
+ /**
3544
+ * CSS `box-shadow` painted on every slide CANVAS (not the wrapper — the
3545
+ * text-selection overlay must not cast its own shadow). The soft drop shadow a
3546
+ * presentation viewer leaves under each slide.
3547
+ *
3548
+ * - Default (`undefined`): `'0 1px 3px rgba(0,0,0,0.2)'` — the recipe look, so
3549
+ * the scroll viewer reproduces the Examples appearance with zero config.
3550
+ * - `false`: NO shadow (flat slides).
3551
+ * - A custom string is applied verbatim. A spread-only ring such as
3552
+ * `'0 0 0 1px #c8ccd0'` gives a crisp 1px BORDER look — and because
3553
+ * `box-shadow` never affects layout (unlike `border`, which would grow the
3554
+ * box and shift every offset), a border and a drop shadow are the SAME knob
3555
+ * here rather than two competing options.
3556
+ */
3557
+ pageShadow?: string | false;
3558
+ /**
3559
+ * Inject an already-loaded engine to share one parse across panes (design §14).
3560
+ * When set: `load()` is unsupported (throws), the engine's own `mode` wins (an
3561
+ * explicitly conflicting `opts.mode` throws at construction, design §11), and
3562
+ * `destroy()` does NOT destroy this engine (the caller owns its lifecycle).
3563
+ */
3564
+ presentation?: PptxPresentation;
3565
+ /** Fires when the top-most visible slide changes. `topIndex` from
3566
+ * `computeVisibleRange` (the first slide intersecting the viewport top,
3567
+ * EXCLUDING overscan). */
3568
+ onVisibleSlideChange?: (topIndex: number, total: number) => void;
3569
+ /** Error callback. When set, `load()` invokes it and resolves (otherwise the
3570
+ * error is rethrown — shared viewer error contract). It ALSO fires for async
3571
+ * per-slot render failures (both main `renderSlide` and worker
3572
+ * `renderSlideToBitmap` rejections); a failed slide is left blank rather than
3573
+ * crashing the loop. Without an `onError`, render failures are logged via
3574
+ * `console.error` so they are never fully silent. */
3575
+ onError?: (err: Error) => void;
3576
+ }
3577
+
2781
3578
  /** Information about a rendered text segment for building a transparent selection overlay. */
2782
3579
  declare interface PptxTextRunInfo {
2783
3580
  text: string;
@@ -2823,10 +3620,20 @@ declare interface PptxTextRunInfo {
2823
3620
  declare class PptxViewer {
2824
3621
  private readonly canvas;
2825
3622
  private readonly wrapper;
3623
+ /** The canvas's DOM position BEFORE the constructor reparented it into
3624
+ * {@link wrapper}, captured so {@link destroy} can return the caller-owned
3625
+ * canvas to exactly where it was. `null` parent = canvas was passed
3626
+ * detached. */
3627
+ private readonly _originalParent;
3628
+ private readonly _originalNextSibling;
3629
+ /** The canvas's inline `display` before the constructor forced `block`
3630
+ * (empty string if it was unset), restored on {@link destroy}. */
3631
+ private readonly _originalDisplay;
2826
3632
  private textLayer;
2827
3633
  private engine;
2828
3634
  private readonly opts;
2829
3635
  private currentSlide;
3636
+ private _hiddenMode;
2830
3637
  private handle;
2831
3638
  private readonly _mode;
2832
3639
  /** The canvas's bitmaprenderer context, used only by the static worker-mode
@@ -2847,6 +3654,21 @@ declare class PptxViewer {
2847
3654
  goToSlide(index: number): Promise<void>;
2848
3655
  nextSlide(): Promise<void>;
2849
3656
  prevSlide(): Promise<void>;
3657
+ /** Next index for sequential nav: skip mode jumps over hidden slides. */
3658
+ private _step;
3659
+ /** Initial slide for load() / mode switch: skip mode lands on a visible one. */
3660
+ private _initialSlide;
3661
+ /** Resolved `'dim'` overlay (defaults merged with the `hiddenSlideDim` option). */
3662
+ private _dim;
3663
+ /**
3664
+ * Switch the hidden-slide mode at runtime and re-render. Entering `'skip'`
3665
+ * while on a hidden slide advances to the nearest visible slide.
3666
+ */
3667
+ setHiddenSlideMode(mode: HiddenSlideMode): Promise<void>;
3668
+ /** The current hidden-slide mode. */
3669
+ get hiddenSlideMode(): HiddenSlideMode;
3670
+ /** Number of non-hidden slides (absolute `slideCount` is unchanged). */
3671
+ get visibleSlideCount(): number;
2850
3672
  get slideIndex(): number;
2851
3673
  get slideCount(): number;
2852
3674
  /**
@@ -2860,7 +3682,15 @@ declare class PptxViewer {
2860
3682
  get canvasElement(): HTMLCanvasElement;
2861
3683
  private renderCurrentSlide;
2862
3684
  private _buildTextLayer;
2863
- /** Clean up the viewer and terminate the background worker. */
3685
+ /**
3686
+ * Clean up the viewer and terminate the background worker.
3687
+ *
3688
+ * The caller-owned `<canvas>` is returned to the DOM position it held before
3689
+ * the constructor was called (same parent, same next-sibling) and its inline
3690
+ * `display` is restored, so the canvas can be reused — e.g. to construct a new
3691
+ * viewer on the same element. If the canvas was passed detached (no parent) it
3692
+ * is simply removed from the internal wrapper. Safe to call more than once.
3693
+ */
2864
3694
  destroy(): void;
2865
3695
  }
2866
3696
 
@@ -2882,6 +3712,24 @@ declare interface PptxViewerOptions extends RenderOptions, LoadOptions {
2882
3712
  * browser's native text selection works on slide content.
2883
3713
  */
2884
3714
  enableTextSelection?: boolean;
3715
+ /**
3716
+ * How hidden slides (`<p:sld show="0">`, §19.3.1.38) are presented:
3717
+ * - `'show'` (default): drawn like any other slide.
3718
+ * - `'skip'`: sequential navigation (`nextSlide`/`prevSlide`, initial load)
3719
+ * jumps over them; absolute indices are unchanged, and an explicit
3720
+ * `goToSlide(i)` to a hidden slide is still honored.
3721
+ * - `'dim'`: drawn under a translucent overlay (PowerPoint thumbnail look).
3722
+ *
3723
+ * Named to match the {@link PptxViewer.hiddenSlideMode} getter and
3724
+ * {@link PptxViewer.setHiddenSlideMode} setter.
3725
+ */
3726
+ hiddenSlideMode?: HiddenSlideMode;
3727
+ /**
3728
+ * Overrides for the `'dim'` overlay. Merged over the default
3729
+ * `{ color: '#ffffff', opacity: 0.6 }`. A `Partial<DimOptions>` so it stays
3730
+ * in sync if {@link DimOptions} gains a field.
3731
+ */
3732
+ hiddenSlideDim?: Partial<DimOptions>;
2885
3733
  }
2886
3734
 
2887
3735
  declare interface Presentation {
@@ -2965,7 +3813,11 @@ declare interface RenderOptions {
2965
3813
  }
2966
3814
 
2967
3815
  declare interface RenderPageOptions {
2968
- /** Canvas CSS width in px; height is auto-computed from page aspect ratio */
3816
+ /** Canvas CSS width in px; height is auto-computed from page aspect ratio.
3817
+ * Applies per CALL — pages of different physical widths (per-section pgSz,
3818
+ * §17.6.13) rendered at the same `width` get different px-per-pt scales.
3819
+ * For a uniform document scale, derive a per-page width from
3820
+ * `DocxDocument.pageSize(i)` instead of passing a constant. */
2969
3821
  width?: number;
2970
3822
  dpr?: number;
2971
3823
  defaultTextColor?: string;
@@ -3005,6 +3857,8 @@ declare interface RenderSlideOptions {
3005
3857
  * its own play/pause chrome without duplication.
3006
3858
  */
3007
3859
  skipMediaControls?: boolean;
3860
+ /** Translucent overlay drawn over the finished slide (hidden-slide dimming). */
3861
+ dim?: DimOptions;
3008
3862
  }
3009
3863
 
3010
3864
  /** Options for {@link PptxPresentation.renderSlideToBitmap}. */
@@ -3014,6 +3868,8 @@ declare interface RenderSlideToBitmapOptions {
3014
3868
  /** Device pixel ratio. Defaults to window.devicePixelRatio (workers have none). */
3015
3869
  dpr?: number;
3016
3870
  /* Excluded from this release type: skipMediaControls */
3871
+ /** Translucent overlay drawn over the finished slide (hidden-slide dimming). */
3872
+ dim?: DimOptions;
3017
3873
  }
3018
3874
 
3019
3875
  declare interface RenderViewportOptions {
@@ -3177,6 +4033,32 @@ declare interface SecondaryValueAxis {
3177
4033
  titleFontColor?: string | null;
3178
4034
  }
3179
4035
 
4036
+ /** ECMA-376 §17.6.13 `<w:pgSz>` + §17.6.11 `<w:pgMar>` — a section's page
4037
+ * geometry: page size + margins + header/footer distances (pt). Mirrors the Rust
4038
+ * `SectionGeom`. Carried on a {@link BodyElement} `sectionBreak` arm (`geom`) so a
4039
+ * mid-body section keeps its own page size; the FINAL section's geometry lives on
4040
+ * {@link DocxDocumentModel.section}. Also stamped per {@link PaginatedBodyElement}
4041
+ * (`sectionGeom`) by the paginator so the renderer sizes each page from its own
4042
+ * section. `orient` is omitted — Word swaps w/h for landscape, so verbatim w/h
4043
+ * already give the correct dims.
4044
+ *
4045
+ * ⚠ Spread over the body-level {@link SectionProps} in `renderDocumentToCanvas`
4046
+ * (`{ ...doc.section, ...pageGeom }`): only add per-section PAGE-BOX fields that
4047
+ * exist on `SectionProps` with the same name and semantics — an optional field
4048
+ * colliding with a non-geometry `SectionProps` name would silently override the
4049
+ * body-level value the renderer promises to preserve. */
4050
+ declare interface SectionGeom {
4051
+ pageWidth: number;
4052
+ pageHeight: number;
4053
+ /** §17.6.11 — top/bottom MAY be negative (ST_SignedTwipsMeasure); keep the sign. */
4054
+ marginTop: number;
4055
+ marginRight: number;
4056
+ marginBottom: number;
4057
+ marginLeft: number;
4058
+ headerDistance: number;
4059
+ footerDistance: number;
4060
+ }
4061
+
3180
4062
  declare interface SectionProps {
3181
4063
  pageWidth: number;
3182
4064
  pageHeight: number;
@@ -3217,17 +4099,8 @@ declare interface SectionProps {
3217
4099
 
3218
4100
  declare type SelectionMode_2 = 'cells' | 'rows' | 'cols' | 'all';
3219
4101
 
3220
- declare interface SeriesDataLabels {
3221
- showVal: boolean;
3222
- showCatName: boolean;
3223
- showSerName: boolean;
3224
- showPercent: boolean;
3225
- position?: string;
3226
- fontColor?: string;
3227
- formatCode?: string;
3228
- fontBold?: boolean;
3229
- fontSizeHpt?: number;
3230
- }
4102
+ /** @deprecated Use `ChartSeriesDataLabels` from @silurus/ooxml-core. */
4103
+ declare type SeriesDataLabels = ChartSeriesDataLabels;
3231
4104
 
3232
4105
  declare interface Shadow {
3233
4106
  color: string;
@@ -3391,6 +4264,19 @@ declare interface ShapeParagraph {
3391
4264
  /** `<a:pPr@rtl>` — whether the paragraph reads right-to-left
3392
4265
  * (ECMA-376 §21.1.2.2.7). Omitted (undefined) when false. */
3393
4266
  rtl?: boolean;
4267
+ /** `<a:pPr@marL>` — left margin in EMU (ECMA-376 §21.1.2.2.7,
4268
+ * `CT_TextParagraphProperties`). Direct attribute only — xlsx text boxes
4269
+ * have no lstStyle/level cascade. Omitted (undefined) when unset. */
4270
+ marL?: number;
4271
+ /** `<a:pPr@marR>` — right margin in EMU (ECMA-376 §21.1.2.2.7).
4272
+ * Omitted (undefined) when unset. */
4273
+ marR?: number;
4274
+ /** `<a:pPr@indent>` — first-line indent in EMU (negative = hanging),
4275
+ * ECMA-376 §21.1.2.2.7. Omitted (undefined) when unset. */
4276
+ indent?: number;
4277
+ /** `<a:pPr>/<a:lnSpc>` line spacing (ECMA-376 §21.1.2.2.5). Direct-only;
4278
+ * omitted when unset. */
4279
+ spaceLine?: SpaceLine | null;
3394
4280
  runs: ShapeTextRun[];
3395
4281
  }
3396
4282
 
@@ -3476,6 +4362,12 @@ declare interface ShapeRun {
3476
4362
  textBlocks?: ShapeText_2[];
3477
4363
  /** "t" | "ctr" | "b" — vertical anchor for the shape's text body (`<wps:bodyPr @anchor>`). */
3478
4364
  textAnchor?: string | null;
4365
+ /** ECMA-376 §21.1.2.1.1 auto-fit mode from `<wps:bodyPr>`, normalized to the
4366
+ * shared core `autoFit` vocabulary (core `src/types/common.ts`): "none"
4367
+ * (`<a:noAutofit/>`, fixed box — overflowing text is CLIPPED to the box),
4368
+ * "sp" (`<a:spAutoFit/>`, box grows to text), or "norm" (`<a:normAutofit/>`,
4369
+ * text shrinks). Absent ⇒ overflow visible. */
4370
+ textAutofit?: string | null;
3479
4371
  textInsetL?: number;
3480
4372
  textInsetT?: number;
3481
4373
  textInsetR?: number;
@@ -3488,6 +4380,27 @@ declare interface ShapeText {
3488
4380
  anchor: string;
3489
4381
  /** `<a:bodyPr@wrap>` — `square` (wrap to width) | `none`. */
3490
4382
  wrap: string;
4383
+ /** `<a:bodyPr>` autofit child — `'sp'` (`spAutoFit`), `'norm'` (`normAutofit`),
4384
+ * or `'none'` (`noAutofit`/absent). ECMA-376 §21.1.2.1.1-.3. Always present
4385
+ * (default `'none'`), mirroring the core `TextBody.autoFit`. */
4386
+ autoFit?: string;
4387
+ /** `<a:normAutofit@fontScale>` — stored font-shrink fraction (e.g. 0.625 for
4388
+ * `fontScale="62500"`). Null/absent when unset. Modeled for parity with
4389
+ * pptx; the xlsx renderer does not currently apply it. */
4390
+ fontScale?: number | null;
4391
+ /** `<a:normAutofit@lnSpcReduction>` — stored line-spacing reduction fraction
4392
+ * (e.g. 0.20 for `lnSpcReduction="20000"`). Null/absent when unset. */
4393
+ lnSpcReduction?: number | null;
4394
+ /** `<a:bodyPr@lIns>` — left text inset in EMU (ECMA-376 §21.1.2.1.1). Always
4395
+ * present; the spec default 91440 EMU (7.2 pt) when the attribute is absent.
4396
+ * Same EMU convention as `ShapeParagraph.marL`. */
4397
+ lIns: number;
4398
+ /** `<a:bodyPr@tIns>` — top text inset in EMU. Default 45720 (3.6 pt). */
4399
+ tIns: number;
4400
+ /** `<a:bodyPr@rIns>` — right text inset in EMU. Default 91440 (7.2 pt). */
4401
+ rIns: number;
4402
+ /** `<a:bodyPr@bIns>` — bottom text inset in EMU. Default 45720 (3.6 pt). */
4403
+ bIns: number;
3491
4404
  paragraphs: ShapeParagraph[];
3492
4405
  }
3493
4406
 
@@ -3511,6 +4424,36 @@ declare interface ShapeText_2 {
3511
4424
  /** ECMA-376 §17.3.1.33 `<w:spacing w:after>` of this text-box paragraph, in
3512
4425
  * pt — reserved BELOW the paragraph. Absent/0 ⇒ no offset. */
3513
4426
  spaceAfter?: number;
4427
+ /** ECMA-376 §17.3.1.33 line spacing value (style-chain resolved). Encoded per
4428
+ * {@link lineSpacingRule}: "auto" ⇒ a MULTIPLIER on the natural line box
4429
+ * (1.15 = 276/240), "exact"/"atLeast" ⇒ pt. Absent ⇒ single (natural). */
4430
+ lineSpacingVal?: number;
4431
+ /** "auto" | "exact" | "atLeast" — see {@link lineSpacingVal}. */
4432
+ lineSpacingRule?: string;
4433
+ /** ECMA-376 §17.3.1.12 `<w:ind w:left/@start>` — paragraph left indent (pt).
4434
+ * Absent/0 ⇒ flush to the box's inner left edge. */
4435
+ indentLeft?: number;
4436
+ /** ECMA-376 §17.3.1.12 `<w:ind w:right/@end>` — paragraph right indent (pt).
4437
+ * Absent/0 ⇒ flush to the box's inner right edge. */
4438
+ indentRight?: number;
4439
+ /** `<w:ind>` first-line indent (pt, SIGNED: `w:firstLine` positive,
4440
+ * `w:hanging` negative). A negative value hangs the first line further LEFT
4441
+ * than the continuation lines (the body renderer honors the sign too — Word
4442
+ * applies a signed hanging first-line list-independently). Absent/0 ⇒ the
4443
+ * first line aligns with the continuation lines. */
4444
+ indentFirst?: number;
4445
+ /** ECMA-376 §17.3.1.37 `<w:tabs>` — explicit tab stops of this text-box
4446
+ * paragraph, resolved through the style chain like {@link DocParagraph.tabStops}.
4447
+ * Absent/empty ⇒ only the automatic default-tab grid applies. The renderer
4448
+ * feeds these to the SAME line engine the body uses so a `\t` inside a text box
4449
+ * advances to its stop (the old shape wrapper dropped tabs entirely). */
4450
+ tabStops?: TabStop_2[];
4451
+ /** ECMA-376 §17.3.1.6 `<w:bidi>` — right-to-left text-box paragraph, resolved
4452
+ * through the style chain like {@link DocParagraph.bidi}. `true` = RTL,
4453
+ * `false` = explicitly LTR, absent = unspecified. Consumed as the paragraph
4454
+ * base direction for the UAX#9 reordering pass (the body renderer reads the
4455
+ * identical field). */
4456
+ bidi?: boolean;
3514
4457
  /** Zip path of an inline image inside this text-box paragraph
3515
4458
  * (`<w:drawing><wp:inline><a:blip r:embed>`), e.g. `word/media/image1.emf`.
3516
4459
  * Absent for a text-only paragraph. */
@@ -3540,6 +4483,18 @@ declare type ShapeTextRun = {
3540
4483
  size: number;
3541
4484
  color?: string;
3542
4485
  fontFace?: string;
4486
+ /** East-Asian typeface (`<a:ea@typeface>`, ECMA-376 §21.1.2.3.1). The
4487
+ * common Japanese encoding sets Meiryo here while leaving `<a:latin>`
4488
+ * default; the renderer floors the line box by this face's design line
4489
+ * too (see `drawShapeText`). Undefined when the run declares no `<a:ea>`. */
4490
+ fontFaceEa?: string;
4491
+ /** Complex-script typeface (`<a:cs@typeface>`, ECMA-376 §21.1.2.3.1).
4492
+ * Parsed/modeled but NOT used in the line-box floor: the cs face renders
4493
+ * only complex-script glyphs (Arabic/Hebrew/Thai), so flooring the whole
4494
+ * line box by it would over-grow Latin/CJK runs (deferred to per-glyph
4495
+ * handling — see `drawShapeText`). Undefined when the run declares no
4496
+ * `<a:cs>`. */
4497
+ fontFaceCs?: string;
3543
4498
  } | {
3544
4499
  type: 'break';
3545
4500
  } | {
@@ -3585,8 +4540,16 @@ declare interface SheetMeta {
3585
4540
  * `#RRGGBB`. Surfaced at workbook-list time so tabs can be painted up front.
3586
4541
  * Absent when the sheet declares no tab color. */
3587
4542
  tabColor?: string | null;
4543
+ /** Sheet visibility (`<sheet state>`, ECMA-376 §18.2.19 `ST_SheetState`).
4544
+ * Absent ⇒ visible. `'veryHidden'` sheets are revealable only
4545
+ * programmatically in Excel. Read via `XlsxWorkbook.isHidden` /
4546
+ * `XlsxWorkbook.sheetVisibility`. */
4547
+ visibility?: 'hidden' | 'veryHidden';
3588
4548
  }
3589
4549
 
4550
+ /** Sheet visibility (`<sheet state>`, ECMA-376 §18.2.19 `ST_SheetState`). */
4551
+ declare type SheetVisibility = 'visible' | 'hidden' | 'veryHidden';
4552
+
3590
4553
  declare interface SlicerAnchor {
3591
4554
  fromCol: number;
3592
4555
  fromColOff: number;
@@ -3625,6 +4588,13 @@ declare interface Slide {
3625
4588
  * when the slide has no comments.
3626
4589
  */
3627
4590
  comments?: PptxComment[];
4591
+ /**
4592
+ * `<p:sld show="0">` — the slide is marked hidden in the slide show
4593
+ * (ECMA-376 §19.3.1.38). Absent (`undefined`) ⇒ shown. The renderer ignores
4594
+ * this; it is a fact surfaced for tools and for {@link PptxViewer}'s hidden-
4595
+ * slide modes (read it via `PptxPresentation.isHidden`).
4596
+ */
4597
+ hidden?: boolean;
3628
4598
  }
3629
4599
 
3630
4600
  declare type SlideElement = ShapeElement | PictureElement | TableElement | ChartElement | MediaElement;
@@ -3637,6 +4607,7 @@ declare type SlideElement = ShapeElement | PictureElement | TableElement | Chart
3637
4607
  */
3638
4608
  declare type SlideRenderOptions = RenderOptions & {
3639
4609
  math?: MathRenderer;
4610
+ dim?: DimOptions;
3640
4611
  };
3641
4612
 
3642
4613
  /** ECMA-376 §20.1.8.31 (CT_SoftEdgesEffect) — feather radius in EMU. */
@@ -4199,6 +5170,7 @@ export declare namespace xlsx {
4199
5170
  XlsxViewer,
4200
5171
  ResolvedList,
4201
5172
  XlsxViewerOptions,
5173
+ HiddenSheetMode,
4202
5174
  CellAddress,
4203
5175
  CellRange_2 as CellRange,
4204
5176
  SelectionMode_2 as SelectionMode,
@@ -4206,6 +5178,7 @@ export declare namespace xlsx {
4206
5178
  AutoResizeOptions,
4207
5179
  Workbook,
4208
5180
  SheetMeta,
5181
+ SheetVisibility,
4209
5182
  Worksheet,
4210
5183
  Row,
4211
5184
  Cell,
@@ -4252,63 +5225,28 @@ export declare namespace xlsx {
4252
5225
  ShapeTextRun,
4253
5226
  PathInfo,
4254
5227
  PathCmd_2 as PathCmd,
4255
- ChartData,
5228
+ ChartModel,
5229
+ ChartSeries,
5230
+ ChartSeriesDataLabels,
5231
+ ChartDataLabelOverride,
5232
+ ChartDataPointOverride,
5233
+ ChartErrBars,
5234
+ ChartManualLayout,
5235
+ LegendManualLayout,
4256
5236
  XlsxChartSeries,
4257
5237
  SeriesDataLabels,
4258
5238
  DataLabelOverride,
4259
5239
  DataPointOverride,
4260
5240
  ErrBars,
4261
- ManualLayout,
4262
- LegendManualLayout_2 as LegendManualLayout
5241
+ ManualLayout
4263
5242
  }
4264
5243
  }
4265
5244
 
4266
5245
  /**
4267
- * XLSX parser's raw chart series (includes XLSX-only `seriesType` for mixed
4268
- * charts). Adapted to `ChartSeries` from @silurus/ooxml-core before rendering.
5246
+ * @deprecated Chart series are now the core {@link ChartModel}'s `ChartSeries`.
5247
+ * Kept as an alias for backward-compatible imports.
4269
5248
  */
4270
- declare interface XlsxChartSeries {
4271
- name: string;
4272
- /** Chart sub-type for this series (allows mixed charts). */
4273
- seriesType: string;
4274
- categories: string[];
4275
- values: (number | null)[];
4276
- /** Explicit fill color hex (from c:spPr). Undefined = use palette. */
4277
- color?: string | null;
4278
- /** Marker visibility resolved from `<c:marker>`/chart-level default
4279
- * (ECMA-376 §21.2.2.32). */
4280
- showMarker?: boolean;
4281
- /** `<c:val>/<c:numRef>/<c:formatCode>` — Excel number format for series
4282
- * values (ECMA-376 §21.2.2.37). */
4283
- valFormatCode?: string | null;
4284
- /** `<c:ser><c:dLbls><c:txPr>…solidFill` — per-series data-label text color
4285
- * (ECMA-376 §21.2.2.216). Takes precedence over the chart-level color so
4286
- * each series can color its labels independently. */
4287
- labelColor?: string | null;
4288
- /** `<c:ser><c:order>` — stacking/legend display order (§21.2.2.28). */
4289
- order?: number;
4290
- /** `<c:marker><c:symbol val>` — point marker shape (ECMA-376 §21.2.2.32). */
4291
- markerSymbol?: string;
4292
- /** `<c:marker><c:size val>` — point marker side length in pt (§21.2.2.34). */
4293
- markerSize?: number;
4294
- /** `<c:marker><c:spPr>` resolved fill (no `#`). */
4295
- markerFill?: string;
4296
- /** `<c:marker><c:spPr><a:ln>` resolved stroke (no `#`). */
4297
- markerLine?: string;
4298
- /** `<c:dPt>` per-data-point overrides (§21.2.2.39). */
4299
- dataPointOverrides?: DataPointOverride[];
4300
- /** Per-idx custom data labels (`<c:dLbl idx>`, §21.2.2.45). Custom rich
4301
- * text is flattened to plain string at parse time; CELLRANGE field
4302
- * placeholders are resolved against the series' `<c15:datalabelsRange>`
4303
- * cache. */
4304
- dataLabelOverrides?: DataLabelOverride[];
4305
- /** Series-level `<c:dLbls>` defaults applied to every point lacking its
4306
- * own override. */
4307
- seriesDataLabels?: SeriesDataLabels;
4308
- /** `<c:errBars>` (§21.2.2.20). At parse time, plus / minus deltas are
4309
- * resolved into absolute per-point values regardless of `errValType`. */
4310
- errBars?: ErrBars[];
4311
- }
5249
+ declare type XlsxChartSeries = ChartSeries;
4312
5250
 
4313
5251
  /** One cell comment. Sourced from the classic notes file `xl/commentsN.xml`
4314
5252
  * (ECMA-376 §18.7) when present, otherwise from the Office-365 threaded
@@ -4345,6 +5283,10 @@ declare interface XlsxTextRunInfo {
4345
5283
 
4346
5284
  declare class XlsxViewer {
4347
5285
  private wb;
5286
+ /** The single subtree root the constructor appended to the caller's
5287
+ * container. destroy() removes it to return the container to its original
5288
+ * (empty) state. */
5289
+ private wrapper;
4348
5290
  private canvas;
4349
5291
  private canvasArea;
4350
5292
  private scrollHost;
@@ -4360,6 +5302,7 @@ declare class XlsxViewer {
4360
5302
  private zoomSlider;
4361
5303
  private zoomLabel;
4362
5304
  private currentSheet;
5305
+ private _hiddenSheetMode;
4363
5306
  private currentWorksheet;
4364
5307
  private opts;
4365
5308
  /** 'main' renders on this thread; 'worker' paints worker-produced bitmaps. */
@@ -4369,6 +5312,32 @@ declare class XlsxViewer {
4369
5312
  * main-mode 2d render path is never used on the same canvas. */
4370
5313
  private _bitmapCtx;
4371
5314
  private resizeObserver;
5315
+ /**
5316
+ * Pending `requestAnimationFrame` handle for a coalesced re-render, or `null`
5317
+ * when none is scheduled. High-frequency event-driven repaints (scroll, live
5318
+ * resize drag, selection drag, container resize) route through
5319
+ * {@link scheduleRender} so at most one render runs per animation frame: a
5320
+ * burst of scroll events within a single frame collapses to one draw at the
5321
+ * frame's latest scroll position (`renderCurrentSheet` reads the live scroll
5322
+ * offset, so "latest wins" needs no stored position). Explicit API calls
5323
+ * (`showSheet`/`goToSheet`, `select`, `setScale`) stay synchronous — they must
5324
+ * paint immediately, not a frame later. `destroy()` cancels any pending frame.
5325
+ */
5326
+ private _rafId;
5327
+ /**
5328
+ * Monotonic render-request counter for worker-mode stale-frame dropping.
5329
+ * Every {@link renderCurrentSheet} bumps it and captures the value before it
5330
+ * awaits the worker's bitmap; on resolution a captured value below the current
5331
+ * one means a newer render was requested meanwhile (scroll moved on, the sheet
5332
+ * switched, a zoom changed), so that bitmap is stale and must be closed and
5333
+ * dropped instead of painted over the fresher frame. The WorkerBridge already
5334
+ * correlates each request↔response by id, but requests overlap — a slow bitmap
5335
+ * for an old scroll position can resolve after a newer one — so the viewer
5336
+ * needs this generation guard, the single-canvas analogue of the pptx
5337
+ * scroll-viewer's per-slot render epoch (PR #663). The main-thread path renders
5338
+ * synchronously and cannot interleave, so it needs no guard.
5339
+ */
5340
+ private _renderSeq;
4372
5341
  /**
4373
5342
  * Start-anchored horizontal scroll position (the {@link effectiveScrollLeft}
4374
5343
  * value last produced by a real user scroll or a programmatic reset), kept
@@ -4477,6 +5446,10 @@ declare class XlsxViewer {
4477
5446
  goToSheet(index: number): Promise<void>;
4478
5447
  nextSheet(): Promise<void>;
4479
5448
  prevSheet(): Promise<void>;
5449
+ /** Next sheet index for sequential nav: skip mode jumps over hidden sheets. */
5450
+ private _stepSheet;
5451
+ /** Initial sheet for load() / entering skip mode: land on a visible sheet. */
5452
+ private _initialSheet;
4480
5453
  /** Returns the cell at canvas-client coordinates, or null if outside the cell grid. */
4481
5454
  getCellAt(clientX: number, clientY: number): CellAddress | null;
4482
5455
  /** Returns the CSS-pixel rect of a cell within canvasArea, or null if not
@@ -4528,6 +5501,15 @@ declare class XlsxViewer {
4528
5501
  * immediately.
4529
5502
  */
4530
5503
  setSelectionColor(color: string): void;
5504
+ /**
5505
+ * Switch the hidden-sheet mode at runtime: restyle the tabs and re-render.
5506
+ * Entering `'skip'` while on a hidden sheet advances to the nearest visible.
5507
+ */
5508
+ setHiddenSheetMode(mode: HiddenSheetMode): Promise<void>;
5509
+ /** The current hidden-sheet mode. */
5510
+ get hiddenSheetMode(): HiddenSheetMode;
5511
+ /** Number of non-hidden sheets (absolute `sheetCount` is unchanged). */
5512
+ get visibleSheetCount(): number;
4531
5513
  /** Copy the selected cell range as tab-separated text to the clipboard. */
4532
5514
  private copySelection;
4533
5515
  private updateSelectionOverlay;
@@ -4583,6 +5565,13 @@ declare class XlsxViewer {
4583
5565
  private updateNavButtons;
4584
5566
  private updateTabActive;
4585
5567
  private tabStyle;
5568
+ /**
5569
+ * Full inline style for the tab of sheet `i`, honoring the hidden-sheet mode:
5570
+ * `'skip'` hides the tab of a hidden/veryHidden sheet (`display:none`); `'dim'`
5571
+ * greys it but leaves it clickable; `'show'` styles every tab normally. Used
5572
+ * by both buildTabs and updateTabActive so navigation never wipes the styling.
5573
+ */
5574
+ private tabCss;
4586
5575
  /** Excel-style zoom control pinned to the right end of the tab bar:
4587
5576
  * `− [────slider────] + 100%`. Live-updates the cell scale on input. */
4588
5577
  private buildZoomControl;
@@ -4596,11 +5585,39 @@ declare class XlsxViewer {
4596
5585
  * aligned tab-nav width in sync. */
4597
5586
  setScale(scale: number): void;
4598
5587
  private updateSpacerSize;
5588
+ /**
5589
+ * Coalesce a re-render into the next animation frame. Called from the
5590
+ * high-frequency event-driven paths (scroll, live column/row resize, drag-
5591
+ * selection, container resize); a burst of these within one frame schedules a
5592
+ * single {@link renderCurrentSheet}, avoiding the previous behavior where every
5593
+ * scroll event forced its own synchronous full redraw. Already-scheduled frames
5594
+ * are not re-scheduled — the one pending render reads the live scroll/scale
5595
+ * state when it runs, so the most recent position always wins without threading
5596
+ * a coordinate through. Falls back to a synchronous render when
5597
+ * `requestAnimationFrame` is unavailable (e.g. a non-DOM host), preserving the
5598
+ * old semantics there.
5599
+ */
5600
+ private scheduleRender;
4599
5601
  private renderCurrentSheet;
4600
5602
  private computeHeaderHighlight;
4601
5603
  get sheetNames(): string[];
4602
5604
  /** The underlying <canvas> element the grid is drawn on. */
4603
5605
  get canvasElement(): HTMLCanvasElement;
5606
+ /**
5607
+ * Tear down the viewer and release resources.
5608
+ *
5609
+ * The caller's container is returned to the state it had before construction
5610
+ * (empty): the entire wrapper subtree the constructor appended is removed.
5611
+ * All document-level listeners are detached — the keydown handler here, and
5612
+ * the validation-panel outside-click handler via {@link hideValidationPanel}.
5613
+ * Listeners on elements inside the wrapper (scrollHost, tabs, …) need no
5614
+ * explicit removal: removing the subtree makes them unreachable and eligible
5615
+ * for GC. Safe to call more than once.
5616
+ *
5617
+ * NOTE: the shared `<style>` in `document.head` is intentionally NOT removed —
5618
+ * it is a class constant that any still-live viewer may depend on, and one
5619
+ * leftover sheet is a bounded, harmless cost (see {@link ensureViewerStyleInjected}).
5620
+ */
4604
5621
  destroy(): void;
4605
5622
  }
4606
5623
 
@@ -4653,6 +5670,19 @@ declare interface XlsxViewerOptions extends LoadOptions_2 {
4653
5670
  * require `'main'` (the math engine cannot cross the worker boundary).
4654
5671
  */
4655
5672
  mode?: 'main' | 'worker';
5673
+ /**
5674
+ * How hidden / veryHidden sheets (`<sheet state>`, ECMA-376 §18.2.19) are
5675
+ * presented:
5676
+ * - `'show'` (default): every sheet gets a tab — current behavior.
5677
+ * - `'skip'`: hidden/veryHidden sheets get no tab and are jumped over by
5678
+ * `nextSheet`/`prevSheet` and initial load; absolute indices are unchanged,
5679
+ * and an explicit `goToSheet(i)` to a hidden sheet is still honored.
5680
+ * - `'dim'`: hidden/veryHidden tabs are shown greyed but stay selectable.
5681
+ *
5682
+ * Named to match the {@link XlsxViewer.hiddenSheetMode} getter and
5683
+ * {@link XlsxViewer.setHiddenSheetMode} setter. Mirrors pptx `hiddenSlideMode`.
5684
+ */
5685
+ hiddenSheetMode?: HiddenSheetMode;
4656
5686
  }
4657
5687
 
4658
5688
  declare class XlsxWorkbook {
@@ -4689,6 +5719,21 @@ declare class XlsxWorkbook {
4689
5719
  /** Per-sheet tab colors (`#RRGGBB`) parallel to {@link sheetNames}.
4690
5720
  * `null` for sheets that declare no tab color. */
4691
5721
  get tabColors(): (string | null)[];
5722
+ /**
5723
+ * Full visibility fact for the sheet at `sheetIndex` (0-based):
5724
+ * `'visible'` | `'hidden'` | `'veryHidden'` (`<sheet state>`, ECMA-376
5725
+ * §18.2.19). NOT clamped — out-of-range / non-integer ⇒ `'visible'`. This is a
5726
+ * *fact*; deciding what to do with a hidden sheet (hide/skip/dim its tab) is
5727
+ * {@link XlsxViewer}'s policy. `'veryHidden'` is revealable only
5728
+ * programmatically in Excel; it is surfaced distinctly here.
5729
+ */
5730
+ sheetVisibility(sheetIndex: number): SheetVisibility;
5731
+ /**
5732
+ * Whether the sheet at `sheetIndex` is hidden or veryHidden. Convenience over
5733
+ * {@link sheetVisibility}; mirrors {@link PptxPresentation.isHidden} (non-
5734
+ * clamped: out-of-range / non-integer ⇒ `false`).
5735
+ */
5736
+ isHidden(sheetIndex: number): boolean;
4692
5737
  getWorksheet(sheetIndex: number): Promise<Worksheet>;
4693
5738
  /**
4694
5739
  * Fetch an embedded image's bytes by zip path (e.g. `xl/media/image1.png`),