pptx-angular-viewer 2.12.0 → 2.13.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.
@@ -2,12 +2,12 @@ import { NgStyle, NgTemplateOutlet, NgClass } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
3
  import { InjectionToken, input, output, computed, ChangeDetectionStrategy, Component, signal, Injectable, inject, DestroyRef, Injector, effect, viewChild, ElementRef, HostListener, afterNextRender, Renderer2, ViewEncapsulation, forwardRef, untracked } from '@angular/core';
4
4
  import { TranslatePipe, TranslateService, translate } from '@ngx-translate/core';
5
- import { THEME_PRESETS, guideEmuToPx, getShapeClipPath, getAdjustmentAwareClipPath, getShapeClipPathFromPreset, getCloudPathForRendering, ooxmlGradientAngleToCssDegrees, hasShapeProperties, applyDrawingColorTransforms as applyDrawingColorTransforms$1, hslToRgb as hslToRgb$1, PRESET_COLOR_MAP, isImageLikeElement, hasTextProperties, stripXmlOrderSuffix, orderedXmlKey, chartDataAddSeries, chartDataRemoveSeries, chartDataAddCategory, chartDataRemoveCategory, chartDataUpdatePoint, rebuildTableStructureInRawXml, updateCellTextInRawXml, MIN_ELEMENT_SIZE as MIN_ELEMENT_SIZE$2, getLinkedTextBoxSegments, deriveSlideTitle, getSubstituteFontFamily, svgPathToPolygons, THEME_COLOR_SCHEME_KEYS, createBuiltinVariables, resolveCoordinate, PptxHandler, deobfuscateFont, detectFontFormat, checkMissingAltText, checkMissingSlideTitle, checkLowContrast, checkComplexTables, checkBlankSlide, checkDuplicateTitles, createChartElement, PptxMarkdownConverter, ThemePresets, formatCommentTimestamp as formatCommentTimestamp$1, EncryptedFileError, parseSignatureXml, cloneSlide as cloneSlide$1, cloneTemplateElementsBySlideId as cloneTemplateElementsBySlideId$1, cloneElement as cloneElement$1, updateSmartArtNodeText, setSmartArtNodeStyle, isInkElement, parseDataUrlToBytes, isZoomElement, SvgExporter, applyThemeToData, pptxActionToElementAction, elementActionToPptxAction, setChartTitle, setChartLegend, setChartDataLabels, setChartAxis, setChartSeriesTrendline, setChartSeriesErrorBars, setChartDataPointLabel, setChartAxisLogScale, setChartAxisTitleStyle, setChartAxisGridlineStyle, setChartSeriesMarker, setChartSeriesChartType, setChartDataPointFill, setChartDataPointMarker, setChartDataPointExplosion, chartDataChangeType, getOleObjectTypeLabel, addSmartArtNodeAsChild, removeSmartArtNode, promoteSmartArtNode, demoteSmartArtNode, reorderSmartArtNode, switchSmartArtLayout, SWITCHABLE_LAYOUT_TYPES, COLOR_MAP_ALIAS_KEYS, DEFAULT_COLOR_MAP, applyThemeOverrideToSlide, TRANSITION_VALID_DIRECTIONS } from 'pptx-viewer-core';
5
+ import { THEME_PRESETS, guideEmuToPx, getShapeClipPath, getAdjustmentAwareClipPath, getShapeClipPathFromPreset, getCloudPathForRendering, ooxmlGradientAngleToCssDegrees, hasShapeProperties, applyDrawingColorTransforms as applyDrawingColorTransforms$1, hslToRgb as hslToRgb$1, PRESET_COLOR_MAP, isImageLikeElement, hasTextProperties, stripXmlOrderSuffix, orderedXmlKey, chartDataAddSeries, chartDataRemoveSeries, chartDataAddCategory, chartDataRemoveCategory, chartDataUpdatePoint, rebuildTableStructureInRawXml, updateCellTextInRawXml, MIN_ELEMENT_SIZE as MIN_ELEMENT_SIZE$2, getLinkedTextBoxSegments, getSubstituteFontFamily, parsePanoseString, deriveSlideTitle, svgPathToPolygons, THEME_COLOR_SCHEME_KEYS, createBuiltinVariables, resolveCoordinate, PptxHandler, deobfuscateFont, detectFontFormat, checkMissingAltText, checkMissingSlideTitle, checkLowContrast, checkComplexTables, checkBlankSlide, checkDuplicateTitles, createChartElement, PptxMarkdownConverter, ThemePresets, formatCommentTimestamp as formatCommentTimestamp$1, EncryptedFileError, parseSignatureXml, cloneSlide as cloneSlide$1, cloneTemplateElementsBySlideId as cloneTemplateElementsBySlideId$1, cloneElement as cloneElement$1, updateSmartArtNodeText, setSmartArtNodeStyle, isInkElement, parseDataUrlToBytes, isZoomElement, SvgExporter, applyThemeToData, pptxActionToElementAction, elementActionToPptxAction, setChartTitle, setChartLegend, setChartDataLabels, setChartAxis, setChartSeriesTrendline, setChartSeriesErrorBars, setChartDataPointLabel, setChartAxisLogScale, setChartAxisTitleStyle, setChartAxisGridlineStyle, setChartSeriesMarker, setChartSeriesChartType, setChartDataPointFill, setChartDataPointMarker, setChartDataPointExplosion, chartDataChangeType, getOleObjectTypeLabel, addSmartArtNodeAsChild, removeSmartArtNode, promoteSmartArtNode, demoteSmartArtNode, reorderSmartArtNode, switchSmartArtLayout, SWITCHABLE_LAYOUT_TYPES, COLOR_MAP_ALIAS_KEYS, DEFAULT_COLOR_MAP, applyThemeOverrideToSlide, TRANSITION_VALID_DIRECTIONS } from 'pptx-viewer-core';
6
6
  import DOMPurify from 'dompurify';
7
7
  import { z } from 'zod';
8
8
  import * as mcp from 'pptx-viewer-mcp';
9
9
  import * as mcpSchemas from 'pptx-viewer-mcp/schemas';
10
- import { LucideX, LucideSquare, LucideCircle, LucideSlash, LucideCopy, LucideTrash2, LucideChevronsUp, LucideChevronsDown, LucideArrowUp, LucideArrowDown, LucideGroup, LucideUngroup, LucideTextAlignStart, LucideTextAlignCenter, LucideTextAlignEnd, LucideChevronUp, LucideChevronDown, LucideAlignHorizontalSpaceAround, LucideAlignVerticalSpaceAround, LucideLayers, LucideMessageSquare, LucidePlus, LucideSettings2, LucideStickyNote, LucideDownload, LucideMenu, LucidePresentation, LucideRedo, LucideShare2, LucideSparkles, LucideUndo, LucideChevronRight, LucideChevronLeft, LucideEraser, LucideHighlighter, LucideMousePointer2, LucidePanelRight, LucidePenTool, LucideTimer, LucideStar, LucideMoveRight, LucidePlay, LucideMousePointerClick, LucidePaintbrush, LucideClock3, LucidePencil, LucideType, LucideMinus, LucideSpline, LucideArrowLeft, LucideFilePlus2, LucideFolderOpen, LucideHome, LucideInfo, LucidePrinter, LucideSave, LucideSettings, LucideUpload, LucideUserRound, LucideAArrowUp, LucideAArrowDown, LucideRemoveFormatting, LucideList, LucideListOrdered, LucideListIndentDecrease, LucideListIndentIncrease, LucideTextAlignJustify, LucideClipboardPaste, LucideFolderPlus, LucideLayoutGrid, LucideRotateCcw, LucideScissors, LucideLink, LucideImage, LucideVideo, LucideDatabase, LucidePanelLeft, LucideEllipsis, LucideEye, LucideEyeOff, LucideMonitor, LucideColumns2, LucideCheck, LucideFileDown, LucideSearch, LucideZoomIn, LucideZoomOut, LucideBug, LucideSend, LucideCrosshair, LucideGitMerge, LucidePin, LucidePinOff, LucideWrench, LucideLoaderCircle, LucideTriangleAlert, LucideShapes, LucidePalette, LucideTable, LucideLayoutTemplate, LucideChartColumn, LucideMove, LucideNavigation, LucideFilm, LucideBot, LucideUser } from '@lucide/angular';
10
+ import { LucideX, LucideSquare, LucideCircle, LucideSlash, LucideCopy, LucideTrash2, LucideChevronsUp, LucideChevronsDown, LucideArrowUp, LucideArrowDown, LucideGroup, LucideUngroup, LucideTextAlignStart, LucideTextAlignCenter, LucideTextAlignEnd, LucideChevronUp, LucideChevronDown, LucideAlignHorizontalSpaceAround, LucideAlignVerticalSpaceAround, LucideLayers, LucideMessageSquare, LucidePlus, LucideSettings2, LucideStickyNote, LucideDownload, LucideMenu, LucidePresentation, LucideRedo, LucideShare2, LucideSparkles, LucideUndo, LucideChevronRight, LucideChevronLeft, LucideEraser, LucideHighlighter, LucideMousePointer2, LucidePanelRight, LucidePenTool, LucideTimer, LucideArrowLeftRight, LucideCaptions, LucideCirclePause, LucideCirclePlay, LucideGrid2x2, LucideMonitor, LucideMonitorOff, LucideRotateCcw, LucideScan, LucideZoomIn, LucideZoomOut, LucideStar, LucideMoveRight, LucidePlay, LucideMousePointerClick, LucidePaintbrush, LucideClock3, LucidePencil, LucideType, LucideMinus, LucideSpline, LucideArrowLeft, LucideFilePlus2, LucideFolderOpen, LucideHome, LucideInfo, LucidePrinter, LucideSave, LucideSettings, LucideUpload, LucideUserRound, LucideAArrowUp, LucideAArrowDown, LucideRemoveFormatting, LucideList, LucideListOrdered, LucideListIndentDecrease, LucideListIndentIncrease, LucideTextAlignJustify, LucideClipboardPaste, LucideFolderPlus, LucideLayoutGrid, LucideScissors, LucideLink, LucideImage, LucideVideo, LucideDatabase, LucidePanelLeft, LucideEllipsis, LucideEye, LucideEyeOff, LucideColumns2, LucideCheck, LucideFileDown, LucideSearch, LucideBug, LucideSend, LucideCrosshair, LucideGitMerge, LucidePin, LucidePinOff, LucideWrench, LucideLoaderCircle, LucideTriangleAlert, LucideShapes, LucidePalette, LucideTable, LucideLayoutTemplate, LucideChartColumn, LucideMove, LucideNavigation, LucideFilm, LucideBot, LucideUser } from '@lucide/angular';
11
11
  import { XMLParser } from 'fast-xml-parser';
12
12
  import JSZip from 'jszip';
13
13
  import { DomSanitizer } from '@angular/platform-browser';
@@ -6374,6 +6374,387 @@ function compileEquationTemplateMathMl() {
6374
6374
  return EQUATION_TEMPLATES.map((tmpl) => latexToMathMl(tmpl.latex));
6375
6375
  }
6376
6376
 
6377
+ /**
6378
+ * PowerPoint's automatic value-axis scale.
6379
+ *
6380
+ * A chart that declares no `c:min` / `c:max` / `c:majorUnit` leaves the bounds
6381
+ * to the application, and PowerPoint does NOT simply run the axis to the largest
6382
+ * data point. It anchors the axis at zero, pads the data, then rounds out to a
6383
+ * round number and labels it in round steps. The reporter's deck (issue #132)
6384
+ * shows the difference plainly: a percentage chart whose bars top out at 52%
6385
+ * gets `0% 20% 40% 60%` from PowerPoint and got `0% 10% 21% 31% 42% 52%` here,
6386
+ * because the axis ran to the data maximum and was then divided into five.
6387
+ *
6388
+ * The rules, from Microsoft's documented automatic-scaling behaviour:
6389
+ *
6390
+ * - **Zero anchor.** With all-positive data, the axis starts at zero unless the
6391
+ * smallest value is at least 5/6 of the largest, in which case the data
6392
+ * occupies a narrow band high up and a zero-anchored axis would flatten it.
6393
+ * All-negative data mirrors this, ending at zero.
6394
+ * - **Headroom.** The far end is pushed out by 5% of the data span so the
6395
+ * tallest bar does not touch the top of the plot.
6396
+ * - **Round steps.** The major unit is the first "nice" number (1, 2, 2.5 or 5
6397
+ * times a power of ten) at or above `span / targetIntervals`, and the bounds
6398
+ * are then snapped outwards to whole multiples of it.
6399
+ *
6400
+ * The snap is what makes the interval count differ from `targetIntervals`:
6401
+ * rounding 0.544 up to a multiple of 0.2 lands on 0.6, which is three steps, not
6402
+ * four. That is the intent, and it is why PowerPoint's axes read in round
6403
+ * numbers while an evenly-divided one does not.
6404
+ */
6405
+ /**
6406
+ * Steps PowerPoint rounds a major unit to, as mantissas of a power of ten.
6407
+ * Anything between two entries rounds UP to the larger, so a unit is never so
6408
+ * fine that the axis grows more gridlines than asked for.
6409
+ */
6410
+ const NICE_STEPS = [1, 2, 2.5, 5, 10];
6411
+ /** The fraction of the data span added as headroom beyond the last data point. */
6412
+ const HEADROOM = 0.05;
6413
+ /**
6414
+ * How close the smaller end of the data has to be to the larger before the axis
6415
+ * stops anchoring at zero. Below 5/6, zero anchoring keeps the bars readable;
6416
+ * above it, the data is a narrow band and zero would squash it flat.
6417
+ */
6418
+ const ZERO_ANCHOR_RATIO = 5 / 6;
6419
+ /** The smallest nice step at or above `value`. */
6420
+ function niceAxisStep(value) {
6421
+ if (!Number.isFinite(value) || value <= 0) {
6422
+ return 1;
6423
+ }
6424
+ const exponent = Math.floor(Math.log10(value));
6425
+ const power = 10 ** exponent;
6426
+ const mantissa = value / power;
6427
+ const step = NICE_STEPS.find((candidate) => mantissa <= candidate + 1e-12) ?? 10;
6428
+ return step * power;
6429
+ }
6430
+ /** Round `value` outwards (away from zero-ward `toward`) to a multiple of `unit`. */
6431
+ function snap(value, unit, direction) {
6432
+ const scaled = value / unit;
6433
+ // Guard against float noise turning an exact multiple into the next one out.
6434
+ const rounded = direction === 'up' ? Math.ceil(scaled - 1e-9) : Math.floor(scaled + 1e-9);
6435
+ return rounded * unit;
6436
+ }
6437
+ /**
6438
+ * Resolve the automatic bounds and major unit for a value axis.
6439
+ *
6440
+ * @param dataMin Smallest plotted value.
6441
+ * @param dataMax Largest plotted value.
6442
+ * @param targetIntervals Roughly how many gridline steps to aim for. The snap to
6443
+ * a round unit means the result is usually one fewer. Four matches the axes
6444
+ * PowerPoint draws for a chart of ordinary height.
6445
+ */
6446
+ function niceValueAxisBounds(dataMin, dataMax, targetIntervals = 4) {
6447
+ if (!Number.isFinite(dataMin) || !Number.isFinite(dataMax)) {
6448
+ return { min: 0, max: 1, majorUnit: 0.5 };
6449
+ }
6450
+ const low = Math.min(dataMin, dataMax);
6451
+ const high = Math.max(dataMin, dataMax);
6452
+ const intervals = Math.max(1, Math.round(targetIntervals));
6453
+ // Flat data (every point identical) has no span to scale. Zero-anchor it
6454
+ // where that reads sensibly, and otherwise bracket the single value.
6455
+ if (low === high) {
6456
+ if (low === 0) {
6457
+ return { min: 0, max: 1, majorUnit: 1 / intervals };
6458
+ }
6459
+ const unit = niceAxisStep(Math.abs(low) / intervals);
6460
+ return low > 0
6461
+ ? { min: 0, max: snap(low, unit, 'up') || unit, majorUnit: unit }
6462
+ : { min: snap(low, unit, 'down'), max: 0, majorUnit: unit };
6463
+ }
6464
+ const dataSpan = high - low;
6465
+ const padding = dataSpan * HEADROOM;
6466
+ // Decide which end, if either, pins to zero.
6467
+ let paddedMin;
6468
+ let paddedMax;
6469
+ if (low >= 0) {
6470
+ paddedMin = low < high * ZERO_ANCHOR_RATIO ? 0 : low - padding;
6471
+ paddedMax = high + padding;
6472
+ }
6473
+ else if (high <= 0) {
6474
+ paddedMax = high > low * ZERO_ANCHOR_RATIO ? 0 : high + padding;
6475
+ paddedMin = low - padding;
6476
+ }
6477
+ else {
6478
+ // Straddles zero: both ends are data-driven, and zero falls inside.
6479
+ paddedMin = low - padding;
6480
+ paddedMax = high + padding;
6481
+ }
6482
+ const unit = niceAxisStep((paddedMax - paddedMin) / intervals);
6483
+ const min = paddedMin === 0 ? 0 : snap(paddedMin, unit, 'down');
6484
+ let max = paddedMax === 0 ? 0 : snap(paddedMax, unit, 'up');
6485
+ if (max <= min) {
6486
+ max = min + unit;
6487
+ }
6488
+ return { min, max, majorUnit: unit };
6489
+ }
6490
+
6491
+ /**
6492
+ * ECMA-376 / SpreadsheetML number-format codes, applied to chart labels.
6493
+ *
6494
+ * A chart's value axis and its data labels both carry a `c:numFmt/@formatCode`
6495
+ * (or inherit one from the series' `c:numCache/c:formatCode` when
6496
+ * `@sourceLinked="1"`). Ignoring it is what made a percentage chart render its
6497
+ * axis as `0.1 0.2 0.3` and its bars as `0.52` where PowerPoint shows `10% 20%
6498
+ * 30%` and `52%`: the cached values ARE fractions, and the format code is the
6499
+ * only thing that says so.
6500
+ *
6501
+ * This covers the codes charts actually use - literal text, digit placeholders,
6502
+ * thousands separators, percent, scientific notation, and the
6503
+ * `positive;negative;zero` section split - and deliberately stops short of a
6504
+ * full Excel format engine. Anything it does not recognise falls back to the
6505
+ * caller's default rendering, so an exotic code degrades to today's output
6506
+ * rather than to a wrong number.
6507
+ */
6508
+ /** Format codes that mean "no explicit format"; the caller's default wins. */
6509
+ const GENERAL = new Set(['', 'general', '@']);
6510
+ /** Split a format code into its `positive;negative;zero;text` sections. */
6511
+ function sections(code) {
6512
+ const out = [];
6513
+ let current = '';
6514
+ let inQuotes = false;
6515
+ for (let i = 0; i < code.length; i += 1) {
6516
+ const ch = code[i];
6517
+ if (ch === '"') {
6518
+ inQuotes = !inQuotes;
6519
+ current += ch;
6520
+ continue;
6521
+ }
6522
+ if (ch === '\\') {
6523
+ current += ch + (code[i + 1] ?? '');
6524
+ i += 1;
6525
+ continue;
6526
+ }
6527
+ if (ch === ';' && !inQuotes) {
6528
+ out.push(current);
6529
+ current = '';
6530
+ continue;
6531
+ }
6532
+ current += ch;
6533
+ }
6534
+ out.push(current);
6535
+ return out;
6536
+ }
6537
+ /** Pick the section that applies to `value` (positive / negative / zero). */
6538
+ function sectionFor(value, parts) {
6539
+ if (parts.length === 1) {
6540
+ return { code: parts[0], forcePositive: false };
6541
+ }
6542
+ if (value < 0 && parts[1] !== undefined) {
6543
+ // A dedicated negative section supplies its own sign (often none, or
6544
+ // parentheses), so the magnitude is formatted unsigned.
6545
+ return { code: parts[1], forcePositive: true };
6546
+ }
6547
+ if (value === 0 && parts[2] !== undefined) {
6548
+ return { code: parts[2], forcePositive: false };
6549
+ }
6550
+ return { code: parts[0], forcePositive: false };
6551
+ }
6552
+ const PLACEHOLDER = /[0#?]/u;
6553
+ /**
6554
+ * Parse one format section. Returns `undefined` when the section contains no
6555
+ * numeric placeholder at all, which means the caller should fall back.
6556
+ */
6557
+ function parsePattern(code) {
6558
+ let scale = 1;
6559
+ let scientific = false;
6560
+ const chars = [];
6561
+ let inQuotes = false;
6562
+ for (let i = 0; i < code.length; i += 1) {
6563
+ const ch = code[i];
6564
+ if (ch === '"') {
6565
+ inQuotes = !inQuotes;
6566
+ continue;
6567
+ }
6568
+ if (inQuotes) {
6569
+ chars.push({ ch, literal: true });
6570
+ continue;
6571
+ }
6572
+ if (ch === '\\' || ch === '*' || ch === '_') {
6573
+ // Escape / repeat / skip-width: the next char is a literal (and for
6574
+ // `_` the width padding is not reproducible in SVG text anyway).
6575
+ const next = code[i + 1];
6576
+ i += 1;
6577
+ if (ch === '\\' && next !== undefined) {
6578
+ chars.push({ ch: next, literal: true });
6579
+ }
6580
+ continue;
6581
+ }
6582
+ if (ch === '%') {
6583
+ scale *= 100;
6584
+ chars.push({ ch, literal: true });
6585
+ continue;
6586
+ }
6587
+ if (ch === 'E' && (code[i + 1] === '+' || code[i + 1] === '-')) {
6588
+ scientific = true;
6589
+ chars.push({ ch: 'E', literal: false });
6590
+ i += 1;
6591
+ continue;
6592
+ }
6593
+ if (ch === '[') {
6594
+ // Colour / condition / locale block: skipped wholesale.
6595
+ const close = code.indexOf(']', i);
6596
+ i = close === -1 ? code.length : close;
6597
+ continue;
6598
+ }
6599
+ chars.push({ ch, literal: false });
6600
+ }
6601
+ const firstDigit = chars.findIndex((c) => !c.literal && PLACEHOLDER.test(c.ch));
6602
+ if (firstDigit === -1) {
6603
+ return undefined;
6604
+ }
6605
+ let lastDigit = firstDigit;
6606
+ for (let i = chars.length - 1; i >= firstDigit; i -= 1) {
6607
+ const c = chars[i];
6608
+ if (!c.literal && (PLACEHOLDER.test(c.ch) || c.ch === '.' || c.ch === ',' || c.ch === 'E')) {
6609
+ lastDigit = i;
6610
+ break;
6611
+ }
6612
+ }
6613
+ const numeric = chars.slice(firstDigit, lastDigit + 1);
6614
+ const prefix = chars
6615
+ .slice(0, firstDigit)
6616
+ .map((c) => c.ch)
6617
+ .join('');
6618
+ const suffix = chars
6619
+ .slice(lastDigit + 1)
6620
+ .map((c) => c.ch)
6621
+ .join('');
6622
+ // Trailing commas immediately before the decimal point (or at the end of the
6623
+ // integer run) scale by thousands; a comma between placeholders groups.
6624
+ let grouped = false;
6625
+ let intDigits = 0;
6626
+ let required = 0;
6627
+ let optional = 0;
6628
+ let afterPoint = false;
6629
+ let inExponent = false;
6630
+ for (let i = 0; i < numeric.length; i += 1) {
6631
+ const { ch, literal } = numeric[i];
6632
+ if (literal) {
6633
+ continue;
6634
+ }
6635
+ if (ch === 'E') {
6636
+ inExponent = true;
6637
+ continue;
6638
+ }
6639
+ if (inExponent) {
6640
+ continue;
6641
+ }
6642
+ if (ch === '.') {
6643
+ afterPoint = true;
6644
+ continue;
6645
+ }
6646
+ if (ch === ',') {
6647
+ const next = numeric.slice(i + 1).find((c) => !c.literal);
6648
+ if (!next || next.ch === '.' || !PLACEHOLDER.test(next.ch)) {
6649
+ scale /= 1000;
6650
+ }
6651
+ else {
6652
+ grouped = true;
6653
+ }
6654
+ continue;
6655
+ }
6656
+ if (!PLACEHOLDER.test(ch)) {
6657
+ continue;
6658
+ }
6659
+ if (afterPoint) {
6660
+ if (ch === '0') {
6661
+ required += 1;
6662
+ }
6663
+ else {
6664
+ optional += 1;
6665
+ }
6666
+ }
6667
+ else if (ch === '0') {
6668
+ intDigits += 1;
6669
+ }
6670
+ }
6671
+ return {
6672
+ prefix,
6673
+ suffix,
6674
+ intDigits,
6675
+ decimals: { required, optional },
6676
+ grouped,
6677
+ scale,
6678
+ scientific,
6679
+ };
6680
+ }
6681
+ /** Apply `#,###` grouping to the integer part of an already-rounded string. */
6682
+ function group(intPart) {
6683
+ return intPart.replace(/\B(?=(\d{3})+(?!\d))/gu, ',');
6684
+ }
6685
+ /** Render `value` through a parsed pattern. */
6686
+ function applyPattern(value, pattern, forcePositive) {
6687
+ const scaled = value * pattern.scale;
6688
+ const magnitude = forcePositive ? Math.abs(scaled) : scaled;
6689
+ if (pattern.scientific) {
6690
+ const digits = pattern.decimals.required + pattern.decimals.optional;
6691
+ const exponential = magnitude.toExponential(digits);
6692
+ const [mantissa, exponent] = exponential.split('e');
6693
+ const sign = exponent.startsWith('-') ? '-' : '+';
6694
+ const power = Math.abs(Number(exponent)).toString().padStart(2, '0');
6695
+ return `${pattern.prefix}${mantissa}E${sign}${power}${pattern.suffix}`;
6696
+ }
6697
+ const maxDecimals = pattern.decimals.required + pattern.decimals.optional;
6698
+ let body = magnitude.toFixed(maxDecimals);
6699
+ if (pattern.decimals.optional > 0) {
6700
+ // Optional (`#`/`?`) decimals are dropped when they are zero.
6701
+ body = body.replace(/(\.\d*?)0+$/u, '$1').replace(/\.$/u, '');
6702
+ const [, frac = ''] = body.split('.');
6703
+ if (frac.length < pattern.decimals.required) {
6704
+ body = Number(body).toFixed(pattern.decimals.required);
6705
+ }
6706
+ }
6707
+ const [rawIntPart, fracPart] = body.split('.');
6708
+ let intPart = rawIntPart;
6709
+ const negative = intPart.startsWith('-');
6710
+ if (negative) {
6711
+ intPart = intPart.slice(1);
6712
+ }
6713
+ if (intPart.length < pattern.intDigits) {
6714
+ intPart = intPart.padStart(pattern.intDigits, '0');
6715
+ }
6716
+ if (pattern.intDigits === 0 && intPart === '0' && fracPart !== undefined) {
6717
+ // `#.##` drops the leading zero; `0.##` keeps it.
6718
+ intPart = '';
6719
+ }
6720
+ if (pattern.grouped) {
6721
+ intPart = group(intPart);
6722
+ }
6723
+ const number = fracPart === undefined ? intPart : `${intPart}.${fracPart}`;
6724
+ return `${negative ? '-' : ''}${pattern.prefix}${number}${pattern.suffix}`;
6725
+ }
6726
+ /**
6727
+ * Format `value` with an ECMA-376 number-format code.
6728
+ *
6729
+ * Returns `undefined` when the code is absent, `General`, or beyond this
6730
+ * subset, which tells the caller to keep its own default formatting.
6731
+ */
6732
+ function formatChartNumber(value, formatCode) {
6733
+ if (!Number.isFinite(value)) {
6734
+ return undefined;
6735
+ }
6736
+ const code = (formatCode ?? '').trim();
6737
+ if (GENERAL.has(code.toLowerCase())) {
6738
+ return undefined;
6739
+ }
6740
+ // Date/time codes are the province of `chart-date-axis`; a value axis never
6741
+ // carries one, and mistaking `d` for a digit would corrupt the label.
6742
+ if (/(?:^|[^\\"])(?:yy|mm?m|dd?|hh|ss)/iu.test(code) && !/[0#?]/u.test(code)) {
6743
+ return undefined;
6744
+ }
6745
+ const { code: section, forcePositive } = sectionFor(value, sections(code));
6746
+ const pattern = parsePattern(section);
6747
+ if (!pattern) {
6748
+ return undefined;
6749
+ }
6750
+ try {
6751
+ return applyPattern(value, pattern, forcePositive);
6752
+ }
6753
+ catch {
6754
+ return undefined;
6755
+ }
6756
+ }
6757
+
6377
6758
  /**
6378
6759
  * Framework-agnostic chart helpers, a focused Vue port of the React package's
6379
6760
  * `viewer/utils/chart-helpers.ts`, `chart-layout.ts`, and
@@ -6528,18 +6909,19 @@ function paletteColor$1(index, styleId, colorPalette) {
6528
6909
  const palette = getChartStylePalette(styleId);
6529
6910
  return palette[index % palette.length];
6530
6911
  }
6531
- /** Compute a Y-axis range that always includes zero. */
6912
+ /**
6913
+ * Automatic Y-axis range, on PowerPoint's terms: zero-anchored where that reads
6914
+ * sensibly, padded, and rounded out to whole major units. See
6915
+ * `chart-axis-nice.ts` for the rules. Running the axis to the raw data maximum
6916
+ * instead put the top gridline on whatever the tallest bar happened to be.
6917
+ */
6532
6918
  function computeValueRange$1(series) {
6533
6919
  const allValues = series.flatMap((s) => s.values);
6534
6920
  if (allValues.length === 0) {
6535
6921
  return { min: 0, max: 1, span: 1 };
6536
6922
  }
6537
- const dataMin = Math.min(...allValues);
6538
- const dataMax = Math.max(...allValues);
6539
- const min = Math.min(dataMin, 0);
6540
- const max = Math.max(dataMax, 0);
6541
- const span = Math.max(max - min, 1);
6542
- return { min, max, span };
6923
+ const { min, max, majorUnit } = niceValueAxisBounds(Math.min(...allValues), Math.max(...allValues));
6924
+ return { min, max, span: Math.max(max - min, Number.EPSILON), majorUnit };
6543
6925
  }
6544
6926
  /**
6545
6927
  * Map a data value to a Y pixel coordinate (top = max, bottom = min).
@@ -6562,8 +6944,20 @@ function valueToY$1(val, range, topY, bottomY) {
6562
6944
  }
6563
6945
  return range.reverseOrder ? topY + ratio * usable : bottomY - ratio * usable;
6564
6946
  }
6565
- /** Compact axis-value formatting (1.2K / 3.4M / integer / one-decimal). */
6566
- function formatAxisValue$1(val) {
6947
+ /**
6948
+ * Compact axis-value formatting (1.2K / 3.4M / integer / one-decimal).
6949
+ *
6950
+ * `formatCode` is the chart's own `c:numFmt/@formatCode`. When the source
6951
+ * declares one - `0%`, `#,##0`, `$#,##0.00` - it WINS: the cached values behind
6952
+ * a percentage chart are fractions, and the compact fallback renders them as
6953
+ * `0.5` where PowerPoint shows `50%`. Codes outside the supported subset fall
6954
+ * through to the compact form, which is what every caller did before.
6955
+ */
6956
+ function formatAxisValue$1(val, formatCode) {
6957
+ const formatted = formatChartNumber(val, formatCode);
6958
+ if (formatted !== undefined) {
6959
+ return formatted;
6960
+ }
6567
6961
  if (Math.abs(val) >= 1_000_000) {
6568
6962
  return `${(val / 1_000_000).toFixed(1)}M`;
6569
6963
  }
@@ -6650,6 +7044,45 @@ function resolveCategoryLabels(chartData) {
6650
7044
  return Array.from({ length: longest }, (_, i) => String(i + 1));
6651
7045
  }
6652
7046
 
7047
+ /**
7048
+ * The chart-area (and plot-area) background, resolved once for every binding.
7049
+ *
7050
+ * All five viewers painted a hardcoded `#0f172a11` wash across the whole chart
7051
+ * SVG. PowerPoint decks routinely write `<c:spPr><a:noFill/></c:spPr>` on
7052
+ * `c:chartSpace` and `c:plotArea` precisely so the chart floats on the slide;
7053
+ * the wash boxed every such chart into a visible grey panel that is not in the
7054
+ * source. Honouring the authored fill is the fix, and keeping the wash as the
7055
+ * fallback leaves charts that say nothing looking exactly as they did.
7056
+ */
7057
+ /**
7058
+ * The wash the bindings have always painted when a chart declares no fill of
7059
+ * its own. Kept as the default so an unstyled chart still reads as a distinct
7060
+ * surface against the slide.
7061
+ */
7062
+ const DEFAULT_CHART_AREA_FILL = '#0f172a11';
7063
+ /** Resolve one recorded fill value to an SVG `fill`, or `undefined` to skip. */
7064
+ function resolve(fill, fallback) {
7065
+ if (fill === 'none') {
7066
+ return undefined;
7067
+ }
7068
+ return fill ?? fallback;
7069
+ }
7070
+ /**
7071
+ * SVG `fill` for the chart-area rect, or `undefined` when nothing should be
7072
+ * painted (the source declared `a:noFill`).
7073
+ */
7074
+ function chartAreaFill(chartData) {
7075
+ return resolve(chartData?.style?.chartAreaFill, DEFAULT_CHART_AREA_FILL);
7076
+ }
7077
+ /**
7078
+ * SVG `fill` for the plot-area rect, or `undefined` when nothing should be
7079
+ * painted. Unlike the chart area there is no default wash: a plot area that
7080
+ * says nothing inherits the chart area behind it.
7081
+ */
7082
+ function plotAreaFill(chartData) {
7083
+ return resolve(chartData?.style?.plotAreaFill, undefined);
7084
+ }
7085
+
6653
7086
  /**
6654
7087
  * Map a (possibly fractional / extrapolated) category index to an X pixel.
6655
7088
  * `mode === 'bar'` centres on category slots; `'line'` anchors at points.
@@ -6954,6 +7387,9 @@ function computeValueRangeForAxis(series, axis) {
6954
7387
  : undefined;
6955
7388
  const min = validMin ?? automatic.min;
6956
7389
  let max = validMax ?? automatic.max;
7390
+ // An explicit bound breaks the automatic scale's guarantee that the span is a
7391
+ // whole number of major units, so the unit no longer describes these bounds.
7392
+ const majorUnit = validMin === undefined && validMax === undefined ? automatic.majorUnit : undefined;
6957
7393
  if (max <= min) {
6958
7394
  max = logBase ? min * logBase : min + 1;
6959
7395
  }
@@ -6964,6 +7400,7 @@ function computeValueRangeForAxis(series, axis) {
6964
7400
  min,
6965
7401
  max,
6966
7402
  span: Math.max(span, Number.EPSILON),
7403
+ ...(majorUnit !== undefined ? { majorUnit } : {}),
6967
7404
  ...(logBase ? { logScale: true, logBase } : {}),
6968
7405
  ...(axis?.orientation === 'maxMin' ? { reverseOrder: true } : {}),
6969
7406
  };
@@ -7010,6 +7447,14 @@ function generateAxisTicks(range, axis, defaultIntervals) {
7010
7447
  if (explicit.length > 0) {
7011
7448
  return explicit;
7012
7449
  }
7450
+ // The automatic scale already picked a round step and snapped the bounds to
7451
+ // whole multiples of it, so stepping by that unit reproduces PowerPoint's
7452
+ // labels exactly. Dividing the span evenly instead is what put ticks on
7453
+ // 10.4 / 20.8 / 31.2 where PowerPoint writes 20 / 40 / 60.
7454
+ const automatic = generateIntervalTicks(range, range.majorUnit ?? 0);
7455
+ if (automatic.length > 0) {
7456
+ return automatic;
7457
+ }
7013
7458
  return Array.from({ length: defaultIntervals + 1 }, (_, index) => range.min + (range.span / defaultIntervals) * index);
7014
7459
  }
7015
7460
  /** Generate minor gridline values, excluding positions occupied by major ticks. */
@@ -8354,12 +8799,16 @@ function valueAxisLabelPlacement(layout, position, defaultSide, axisX) {
8354
8799
  ? { x: layout.plotLeft - 4, textAnchor: 'end' }
8355
8800
  : { x: layout.plotRight + 4, textAnchor: 'start' };
8356
8801
  }
8357
- /** Format a value-axis tick: display-unit scaled when the axis declares units. */
8802
+ /**
8803
+ * Format a value-axis tick: display-unit scaled when the axis declares units,
8804
+ * otherwise through the axis's own `c:numFmt/@formatCode`. Without the format
8805
+ * code a percentage axis renders its cached fractions raw (`0.2` for `20%`).
8806
+ */
8358
8807
  function formatTick(val, axis) {
8359
8808
  if (axis?.displayUnits) {
8360
8809
  return formatAxisValueWithUnits(val, axis);
8361
8810
  }
8362
- return formatAxisValue(val);
8811
+ return formatAxisValue(val, axis?.numFmt?.formatCode);
8363
8812
  }
8364
8813
  /**
8365
8814
  * Build primary value-axis gridlines + left-side labels, honouring log scale and
@@ -8515,6 +8964,11 @@ function hasRicherAxisFeatures(chartData) {
8515
8964
  (axis.axisType === 'valAx' &&
8516
8965
  Boolean(axis.logScale ||
8517
8966
  axis.displayUnits ||
8967
+ // A `c:numFmt` is only honoured on the `buildPrimaryAxis`
8968
+ // path; the fast path has no axis to read it from. The two
8969
+ // agree tick-for-tick when no log scale or display unit is
8970
+ // in play, so routing a formatted axis here costs nothing.
8971
+ axis.numFmt?.formatCode ||
8518
8972
  axis.axPos === 'r' ||
8519
8973
  axis.orientation === 'maxMin' ||
8520
8974
  axis.majorUnit !== undefined ||
@@ -8641,7 +9095,7 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
8641
9095
  kind: 'text',
8642
9096
  x: x + singleBarWidth / 2,
8643
9097
  y: val >= 0 ? y - 4 : y + h + 10,
8644
- text: formatAxisValue(val),
9098
+ text: formatAxisValue(val, series[si].numberFormat),
8645
9099
  fontSize: 7,
8646
9100
  fill: '#334155',
8647
9101
  textAnchor: 'middle',
@@ -8763,7 +9217,7 @@ function pushClusteredStackedLabels(series, sourceIndices, catCount, layout, ran
8763
9217
  kind: 'text',
8764
9218
  x,
8765
9219
  y: labelY,
8766
- text: formatAxisValue(val),
9220
+ text: formatAxisValue(val, series[si].numberFormat),
8767
9221
  fontSize: 7,
8768
9222
  fill: '#334155',
8769
9223
  textAnchor: 'middle',
@@ -9119,7 +9573,7 @@ function buildLines(chartData, catCount, layout, primaryRange, secondaryRange, s
9119
9573
  kind: 'text',
9120
9574
  x: pt.x,
9121
9575
  y: pt.y - 7,
9122
- text: formatAxisValue(val),
9576
+ text: formatAxisValue(val, series.numberFormat),
9123
9577
  fontSize: 7,
9124
9578
  fill: '#334155',
9125
9579
  textAnchor: 'middle',
@@ -9183,7 +9637,7 @@ function buildAreas(chartData, catCount, layout, range, sourceIndices, xPosition
9183
9637
  kind: 'text',
9184
9638
  x: pt.x,
9185
9639
  y: pt.y - 6,
9186
- text: formatAxisValue(val),
9640
+ text: formatAxisValue(val, series.numberFormat),
9187
9641
  fontSize: 7,
9188
9642
  fill: '#334155',
9189
9643
  textAnchor: 'middle',
@@ -9219,7 +9673,7 @@ function buildScatter(chartData, layout, range) {
9219
9673
  kind: 'text',
9220
9674
  x: dot.cx,
9221
9675
  y: dot.cy - 6,
9222
- text: formatAxisValue(val),
9676
+ text: formatAxisValue(val, series.numberFormat),
9223
9677
  fontSize: 7,
9224
9678
  fill: '#334155',
9225
9679
  textAnchor: 'middle',
@@ -9267,7 +9721,7 @@ function buildBubbles(chartData, layout, range) {
9267
9721
  kind: 'text',
9268
9722
  x: dot.cx,
9269
9723
  y: dot.cy - 10,
9270
- text: formatAxisValue(val),
9724
+ text: formatAxisValue(val, series.numberFormat),
9271
9725
  fontSize: 7,
9272
9726
  fill: '#334155',
9273
9727
  textAnchor: 'middle',
@@ -10770,7 +11224,7 @@ function buildComboViewModel(element, chartData, categoryLabels) {
10770
11224
  kind: 'text',
10771
11225
  x: point.x,
10772
11226
  y: point.y - 7,
10773
- text: formatAxisValue(point.value),
11227
+ text: formatAxisValue(point.value, series.numberFormat),
10774
11228
  fontSize: 7,
10775
11229
  fill: '#334155',
10776
11230
  textAnchor: 'middle',
@@ -10822,7 +11276,7 @@ function appendBarLabels(series, chartData, layout, catCount, range, sourceIndic
10822
11276
  x: xPositions?.[displayIndex] ??
10823
11277
  layout.plotLeft + groupWidth * displayIndex + offset + barWidth / 2,
10824
11278
  y: value >= 0 ? Math.min(zeroY, valueY) - 4 : Math.max(zeroY, valueY) + 10,
10825
- text: formatAxisValue(value),
11279
+ text: formatAxisValue(value, series.numberFormat),
10826
11280
  fontSize: 7,
10827
11281
  fill: '#334155',
10828
11282
  textAnchor: 'middle',
@@ -11952,7 +12406,7 @@ function isOutsidePosition(position) {
11952
12406
  * leader line from the rim point to the label anchor.
11953
12407
  */
11954
12408
  function buildPieDataLabels(params) {
11955
- const { slices, values, cx, cy, outerR, position, showLeaderLines } = params;
12409
+ const { slices, values, cx, cy, outerR, position, showLeaderLines, numberFormat } = params;
11956
12410
  const outside = isOutsidePosition(position);
11957
12411
  const labels = [];
11958
12412
  const leaderLines = [];
@@ -11966,7 +12420,7 @@ function buildPieDataLabels(params) {
11966
12420
  kind: 'text',
11967
12421
  x: slice.labelX,
11968
12422
  y: slice.labelY,
11969
- text: formatAxisValue(val),
12423
+ text: formatAxisValue(val, numberFormat),
11970
12424
  fontSize: 8,
11971
12425
  fill: '#ffffff',
11972
12426
  textAnchor: 'middle',
@@ -11986,7 +12440,7 @@ function buildPieDataLabels(params) {
11986
12440
  kind: 'text',
11987
12441
  x: labelX + (cos >= 0 ? 2 : -2),
11988
12442
  y: labelY,
11989
- text: formatAxisValue(val),
12443
+ text: formatAxisValue(val, numberFormat),
11990
12444
  fontSize: 8,
11991
12445
  fill: '#334155',
11992
12446
  textAnchor: anchor,
@@ -13444,7 +13898,10 @@ function paletteColor(index, colorPalette) {
13444
13898
  function seriesColor(series, index, colorPalette) {
13445
13899
  return series.color ?? paletteColor(index, colorPalette);
13446
13900
  }
13447
- /** Compute a Y-axis range that always includes zero. */
13901
+ /**
13902
+ * Automatic Y-axis range, on PowerPoint's terms. See `chart-axis-nice.ts`; this
13903
+ * mirrors `computeValueRange` in `chart-helpers.ts`.
13904
+ */
13448
13905
  function computeValueRange(series) {
13449
13906
  let dataMin = Number.POSITIVE_INFINITY;
13450
13907
  let dataMax = Number.NEGATIVE_INFINITY;
@@ -13461,12 +13918,13 @@ function computeValueRange(series) {
13461
13918
  if (dataMin === Number.POSITIVE_INFINITY) {
13462
13919
  return { min: 0, max: 1, span: 1 };
13463
13920
  }
13464
- const min = Math.min(dataMin, 0);
13465
- const max = Math.max(dataMax, 0);
13466
- const span = Math.max(max - min, 1);
13467
- return { min, max, span };
13921
+ const { min, max, majorUnit } = niceValueAxisBounds(dataMin, dataMax);
13922
+ return { min, max, span: Math.max(max - min, Number.EPSILON), majorUnit };
13468
13923
  }
13469
- /** Compute the value range for a stacked bar (sum of positive values per category). */
13924
+ /**
13925
+ * Value range for a stacked bar: the per-category sums, then the same automatic
13926
+ * scale as any other value axis.
13927
+ */
13470
13928
  function computeStackedValueRange(series, catCount) {
13471
13929
  let maxSum = 0;
13472
13930
  let minSum = 0;
@@ -13485,10 +13943,8 @@ function computeStackedValueRange(series, catCount) {
13485
13943
  maxSum = Math.max(maxSum, pos);
13486
13944
  minSum = Math.min(minSum, neg);
13487
13945
  }
13488
- const min = Math.min(minSum, 0);
13489
- const max = Math.max(maxSum, 0);
13490
- const span = Math.max(max - min, 1);
13491
- return { min, max, span };
13946
+ const { min, max, majorUnit } = niceValueAxisBounds(Math.min(minSum, 0), Math.max(maxSum, 0));
13947
+ return { min, max, span: Math.max(max - min, Number.EPSILON), majorUnit };
13492
13948
  }
13493
13949
  /**
13494
13950
  * Map a data value to a Y pixel coordinate (top = max, bottom = min).
@@ -13514,8 +13970,16 @@ function valueToY(val, range, topY, bottomY) {
13514
13970
  // ─────────────────────────────────────────────────────────────────────────────
13515
13971
  // Formatting
13516
13972
  // ─────────────────────────────────────────────────────────────────────────────
13517
- /** Format a numeric axis label to a short human-readable string. */
13518
- function formatAxisValue(val) {
13973
+ /**
13974
+ * Format a numeric axis or data label to a short human-readable string, or
13975
+ * through the chart's own `c:numFmt/@formatCode` when it declares one. See
13976
+ * `formatAxisValue` in `chart-helpers.ts`, which this mirrors.
13977
+ */
13978
+ function formatAxisValue(val, formatCode) {
13979
+ const formatted = formatChartNumber(val, formatCode);
13980
+ if (formatted !== undefined) {
13981
+ return formatted;
13982
+ }
13519
13983
  if (Math.abs(val) >= 1_000_000) {
13520
13984
  return `${(val / 1_000_000).toFixed(1)}M`;
13521
13985
  }
@@ -13593,11 +14057,25 @@ const GRIDLINE_COLOR = '#e2e8f0';
13593
14057
  const AXIS_LABEL_COLOR = '#64748b';
13594
14058
  const ZERO_LINE_COLOR = '#94a3b8';
13595
14059
  const TICK_COUNT = 5;
14060
+ /**
14061
+ * Tick values for a range: one per major unit when the automatic scale supplied
14062
+ * one (it snapped the bounds to whole multiples, so this lands on round numbers
14063
+ * exactly as PowerPoint does), otherwise an even division of the span.
14064
+ */
14065
+ function axisTickValues(range) {
14066
+ const unit = range.majorUnit;
14067
+ if (unit !== undefined && Number.isFinite(unit) && unit > 0 && !range.logScale) {
14068
+ const steps = Math.round((range.max - range.min) / unit);
14069
+ if (steps >= 1 && steps <= 100) {
14070
+ return Array.from({ length: steps + 1 }, (_unused, index) => range.min + unit * index);
14071
+ }
14072
+ }
14073
+ return Array.from({ length: TICK_COUNT + 1 }, (_unused, index) => range.min + (range.span / TICK_COUNT) * index);
14074
+ }
13596
14075
  function buildGridlinesAndLabels(range, layout) {
13597
14076
  const gridlines = [];
13598
14077
  const axisLabels = [];
13599
- for (let i = 0; i <= TICK_COUNT; i++) {
13600
- const val = range.min + (range.span / TICK_COUNT) * i;
14078
+ for (const val of axisTickValues(range)) {
13601
14079
  const y = valueToY(val, range, layout.plotTop, layout.plotBottom);
13602
14080
  gridlines.push({
13603
14081
  kind: 'line',
@@ -13940,15 +14418,27 @@ function buildChartViewModel(element) {
13940
14418
  : Array.from({ length: longestLen }, (_, i) => String(i + 1));
13941
14419
  // Pie-of-pie / bar-of-pie splits one series across a primary + secondary plot.
13942
14420
  if (chartType === 'ofPie') {
13943
- return withUserShapeOverlay(buildOfPieViewModel(element, chartData, categoryLabels), chartData);
14421
+ return withChartAreaFill(withUserShapeOverlay(buildOfPieViewModel(element, chartData, categoryLabels), chartData), chartData);
13944
14422
  }
13945
14423
  // 3D chart kinds keep their flat geometry but get an oblique depth pass driven
13946
14424
  // by c:view3D so they read as 3D instead of collapsing to a flat plot.
13947
14425
  const flat = buildFlatViewModel(element, chartData, categoryLabels, kind);
13948
14426
  if (is3DChartType(chartType)) {
13949
- return withUserShapeOverlay(applyChart3DDepth(flat, chartType, chartData.view3D), chartData);
14427
+ return withChartAreaFill(withUserShapeOverlay(applyChart3DDepth(flat, chartType, chartData.view3D), chartData), chartData);
13950
14428
  }
13951
- return withUserShapeOverlay(flat, chartData);
14429
+ return withChartAreaFill(withUserShapeOverlay(flat, chartData), chartData);
14430
+ }
14431
+ /**
14432
+ * Stamp the resolved chart-area / plot-area fills onto a finished view-model so
14433
+ * every binding paints (or skips) the same background rect. A chart that
14434
+ * declares `<a:noFill/>` gets `areaFill: undefined` and no rect at all.
14435
+ */
14436
+ function withChartAreaFill(vm, chartData) {
14437
+ return {
14438
+ ...vm,
14439
+ areaFill: chartAreaFill(chartData),
14440
+ plotFill: plotAreaFill(chartData),
14441
+ };
13952
14442
  }
13953
14443
  /**
13954
14444
  * Append the chart's `c:userShapes` drawing overlay to a finished view-model.
@@ -14021,6 +14511,8 @@ function buildFallbackViewModel(width, height, label) {
14021
14511
  return {
14022
14512
  svgWidth,
14023
14513
  svgHeight,
14514
+ // No chart data to read a fill from, so the historical wash stands.
14515
+ areaFill: DEFAULT_CHART_AREA_FILL,
14024
14516
  title: undefined,
14025
14517
  titleX: svgWidth / 2,
14026
14518
  titleY: 14,
@@ -14093,6 +14585,7 @@ function buildPieViewModel(element, chartData, categoryLabels, isDoughnut) {
14093
14585
  outerR,
14094
14586
  position: chartData.style.dataLabels?.position,
14095
14587
  showLeaderLines: chartData.style.dataLabels?.showLeaderLines,
14588
+ numberFormat: chartData.series[0]?.numberFormat,
14096
14589
  });
14097
14590
  dataLabels.push(...labelResult.labels);
14098
14591
  primitives.push(...labelResult.leaderLines);
@@ -14220,7 +14713,7 @@ function buildRadarViewModel(element, chartData, categoryLabels) {
14220
14713
  kind: 'text',
14221
14714
  x: p.x,
14222
14715
  y: p.y - 8,
14223
- text: formatAxisValue(val),
14716
+ text: formatAxisValue(val, series.numberFormat),
14224
14717
  fontSize: 7,
14225
14718
  fill: '#334155',
14226
14719
  textAnchor: 'middle',
@@ -16832,6 +17325,10 @@ function normalizeDelay(delay) {
16832
17325
  }
16833
17326
  return { ms: delay, indefinite: false };
16834
17327
  }
17328
+ /** Triggers that only fire once the viewer does something. */
17329
+ function isInteractiveTrigger(trigger) {
17330
+ return trigger === 'onClick' || trigger === 'onShapeClick' || trigger === 'onHover';
17331
+ }
16835
17332
  function classifyCondition(cond) {
16836
17333
  const evt = cond.event;
16837
17334
  if (evt && CLICK_EVENTS.has(evt)) {
@@ -16869,6 +17366,7 @@ function resolveEffectiveStartCondition(conditions, fallbackTrigger) {
16869
17366
  hasClickAlternative: false,
16870
17367
  hasHoverAlternative: false,
16871
17368
  indefinite: false,
17369
+ requiresInteraction: isInteractiveTrigger(fallbackTrigger),
16872
17370
  };
16873
17371
  if (!conditions || conditions.length === 0) {
16874
17372
  return base;
@@ -16917,6 +17415,8 @@ function resolveEffectiveStartCondition(conditions, fallbackTrigger) {
16917
17415
  base.indefinite = indefinite;
16918
17416
  base.dependsOnTimeNodeId = timenode.targetTimeNodeId;
16919
17417
  base.dependsOnEvent = timenode.event;
17418
+ // Chained off another node's begin/end: the timeline starts it, not a click.
17419
+ base.requiresInteraction = false;
16920
17420
  return base;
16921
17421
  }
16922
17422
  // 2. Pure delay governs.
@@ -16926,15 +17426,21 @@ function resolveEffectiveStartCondition(conditions, fallbackTrigger) {
16926
17426
  base.trigger = clickCond ? 'onClick' : 'afterDelay';
16927
17427
  base.delayMs = ms;
16928
17428
  base.indefinite = indefinite;
17429
+ // The delay fires on its own; a coexisting click only brings it forward,
17430
+ // so this node does not WAIT for the viewer even when `trigger` says
17431
+ // `onClick` (that spelling exists so the engine offers the early-out).
17432
+ base.requiresInteraction = indefinite;
16929
17433
  return base;
16930
17434
  }
16931
17435
  // 3. Interactive trigger governs.
16932
17436
  if (clickCond) {
16933
17437
  base.trigger = clickCond.targetShapeId ? 'onShapeClick' : 'onClick';
17438
+ base.requiresInteraction = true;
16934
17439
  return base;
16935
17440
  }
16936
17441
  if (hoverCond) {
16937
17442
  base.trigger = 'onHover';
17443
+ base.requiresInteraction = true;
16938
17444
  return base;
16939
17445
  }
16940
17446
  // 4. Zero-delay-only condition (onBegin / @_delay="0"): keep fallback but
@@ -16942,6 +17448,7 @@ function resolveEffectiveStartCondition(conditions, fallbackTrigger) {
16942
17448
  if (delayCond) {
16943
17449
  base.trigger = fallbackTrigger;
16944
17450
  base.delayMs = 0;
17451
+ base.requiresInteraction = false;
16945
17452
  }
16946
17453
  return base;
16947
17454
  }
@@ -17654,7 +18161,12 @@ function parsePlayFrom(raw) {
17654
18161
  * - `play` / `resume` -> resume playback.
17655
18162
  * - `pause` -> pause playback.
17656
18163
  * - `stop` / `stopMedia` -> pause and rewind to the start.
17657
- * - `togglePlay` -> pause if playing, else play.
18164
+ * - `togglePlay` / `togglePause` -> pause if playing, else play.
18165
+ *
18166
+ * `togglePause` is PowerPoint's own spelling: a video authored with "click to
18167
+ * pause" emits `<p:cmd type="call" cmd="togglePause"/>` in its interactiveSeq,
18168
+ * never `togglePlay`. Both name the same two-way toggle, so they map to one
18169
+ * verb; missing the alias made clicking such a video silently do nothing.
17658
18170
  *
17659
18171
  * Any other command string (including non-media `evt` / `verb` commands, or a
17660
18172
  * numeric verb code with no browser analogue) returns `undefined` so the caller
@@ -17679,7 +18191,7 @@ function parseMediaCommand(commandString) {
17679
18191
  if (lower === 'stop' || lower === 'stopmedia') {
17680
18192
  return { verb: 'stop' };
17681
18193
  }
17682
- if (lower === 'toggleplay') {
18194
+ if (lower === 'toggleplay' || lower === 'togglepause') {
17683
18195
  return { verb: 'togglePlay' };
17684
18196
  }
17685
18197
  return undefined;
@@ -17922,7 +18434,16 @@ function buildTimeline(nativeAnimations) {
17922
18434
  // is how PowerPoint renders a deck whose opening effects are "With
17923
18435
  // Previous"; without it the first group was always click-gated and
17924
18436
  // the slide showed nothing until the viewer clicked.
17925
- currentGroupAutoStart = !isOnClick && singleAnim.groupAutoStart === true;
18437
+ // `isOnClick` alone is the wrong test: `resolveAnimationStart` keeps
18438
+ // the node's fallback trigger (`onClick`) whenever its condition list
18439
+ // carries no actionable semantics, so a step the deck marks as
18440
+ // auto-starting AND whose only condition is `<p:cond delay="0"/>`
18441
+ // looked click-gated. `requiresInteraction` distinguishes the two,
18442
+ // which is what makes a slide-entry `p:cmd` media command (the
18443
+ // `playFrom(0.0)` on solution-explorer slide 2) start with the slide
18444
+ // instead of waiting for a Next press.
18445
+ currentGroupAutoStart =
18446
+ singleAnim.groupAutoStart === true && !effective.requiresInteraction;
17926
18447
  subGroupIndex = singleAnim.parGroupIndex;
17927
18448
  subGroupStartMs = 0;
17928
18449
  }
@@ -18057,27 +18578,35 @@ function buildSequenceGroups(animsByKey, entranceIds, neededKeyframes, dynamicBl
18057
18578
  for (const anim of anims) {
18058
18579
  let effect = resolveEffect(anim);
18059
18580
  const dynamic = effect ? undefined : buildDynamicKeyframe(anim, dynamicUid++);
18060
- if (!effect && !dynamic) {
18581
+ // An interactive sequence can hold a `p:cmd` media command just as the
18582
+ // main sequence can (PowerPoint's "click the video to pause it" is
18583
+ // authored exactly that way). Without this branch the step fell through
18584
+ // to the unmapped-preset safety net, and because a `mediacall` effect
18585
+ // carries no whitelisted `presetClass` the net returned nothing and the
18586
+ // command was dropped in silence.
18587
+ const isCommand = !effect && !dynamic && isMediaCommandAnimation(anim);
18588
+ if (!effect && !dynamic && !isCommand) {
18061
18589
  // Same unmapped-preset safety net as the main timeline loop.
18062
18590
  effect = fallbackEffectForClass(anim.presetClass);
18063
18591
  if (!effect) {
18064
18592
  continue;
18065
18593
  }
18066
18594
  }
18067
- const keyframe = effect ? cssKeyframeName(effect) : dynamic.keyframeName;
18595
+ const keyframe = isCommand ? '' : effect ? cssKeyframeName(effect) : dynamic.keyframeName;
18068
18596
  if (effect) {
18069
18597
  neededKeyframes.add(effect);
18070
18598
  }
18071
18599
  if (dynamic) {
18072
18600
  dynamicBlocks.push(dynamic.css);
18073
18601
  }
18074
- const elementId = anim.targetId ?? '';
18602
+ // Command steps carry no element visibility semantics; see the main loop.
18603
+ const elementId = isCommand ? '' : (anim.targetId ?? '');
18075
18604
  const seqTrigger = anim.trigger ?? 'onShapeClick';
18076
- const duration = anim.durationMs ?? defaultDuration(anim.presetClass);
18605
+ const duration = isCommand ? 0 : (anim.durationMs ?? defaultDuration(anim.presetClass));
18077
18606
  // Same single-quantity rule as the main sequence: never sum the two.
18078
18607
  const animDelay = Math.max(anim.delayMs ?? 0, anim.triggerDelayMs ?? 0);
18079
18608
  const triggerDelay = 0;
18080
- const presetClass = anim.presetClass ?? 'entr';
18609
+ const presetClass = isCommand ? 'emph' : (anim.presetClass ?? 'entr');
18081
18610
  const fill = fillModeForClass(anim.presetClass);
18082
18611
  const iterCount = anim.repeatCount ?? 1;
18083
18612
  const direction = anim.autoReverse ? 'alternate' : 'normal';
@@ -18104,7 +18633,9 @@ function buildSequenceGroups(animsByKey, entranceIds, neededKeyframes, dynamicBl
18104
18633
  }
18105
18634
  const iterStr = iterCount === Infinity ? 'infinite' : String(iterCount);
18106
18635
  const easing = cssEasingForAnimation(anim);
18107
- const cssAnimation = `${keyframe} ${duration}ms ${easing} ${delayMs}ms ${iterStr} ${direction} ${fill}`;
18636
+ const cssAnimation = isCommand
18637
+ ? ''
18638
+ : `${keyframe} ${duration}ms ${easing} ${delayMs}ms ${iterStr} ${direction} ${fill}`;
18108
18639
  seqGroup.push({
18109
18640
  elementId,
18110
18641
  cssAnimation,
@@ -18116,8 +18647,9 @@ function buildSequenceGroups(animsByKey, entranceIds, neededKeyframes, dynamicBl
18116
18647
  presetClass: presetClass,
18117
18648
  soundPath: anim.soundPath,
18118
18649
  stopSound: anim.stopSound,
18119
- build: resolveStepBuildDescriptor(anim),
18120
- colorTargets: stepColorTargets(anim),
18650
+ command: isCommand ? buildStepCommand(anim) : undefined,
18651
+ build: isCommand ? undefined : resolveStepBuildDescriptor(anim),
18652
+ colorTargets: isCommand ? undefined : stepColorTargets(anim),
18121
18653
  });
18122
18654
  }
18123
18655
  if (seqGroup.length > 0) {
@@ -27268,7 +27800,18 @@ function buildTextBlockStyle(element, options = {}) {
27268
27800
  // Always declare a size and a family: see DEFAULT_FONT_FAMILY for why an
27269
27801
  // omitted declaration is a real rendering difference, not a no-op.
27270
27802
  style.fontSize = ts?.fontSize || DEFAULT_TEXT_FONT_SIZE;
27271
- style.fontFamily = ts?.fontFamily || DEFAULT_FONT_FAMILY$1;
27803
+ // Through PANOSE substitution, never the bare authored name: the fallback
27804
+ // chain is what supplies the stand-in when the font is absent. Emitting
27805
+ // `font-family: "思源黑体 CN Light"` on its own leaves the browser to pick its
27806
+ // own default, which for CJK text is a SERIF - PowerPoint substitutes a sans,
27807
+ // so the whole deck rendered in the wrong typeface class. Runs already went
27808
+ // through `getSubstituteFontFamily` in `segmentStyleToCss`; the body did not,
27809
+ // and the body is what plain (unsegmented) text inherits.
27810
+ // The authored PANOSE picks the right generic (a serif font with no entry in
27811
+ // the substitution map must not fall back to sans, and vice versa).
27812
+ style.fontFamily = ts?.fontFamily
27813
+ ? getSubstituteFontFamily(ts.fontFamily, parsePanoseString(ts.latinFontPanose))
27814
+ : DEFAULT_FONT_FAMILY$1;
27272
27815
  style.fontWeight = ts?.bold ? 700 : 400;
27273
27816
  style.fontStyle = ts?.italic ? 'italic' : 'normal';
27274
27817
  style.textDecorationLine = decorations.length > 0 ? decorations.join(' ') : 'none';
@@ -31906,97 +32449,14 @@ function applyReroutedConnectors(elements, rerouted) {
31906
32449
  }
31907
32450
 
31908
32451
  /**
31909
- * Pure, framework-agnostic connector-geometry helpers shared across bindings.
31910
- *
31911
- * Derives the full set of SVG rendering values for a connector `PptxElement`:
31912
- * stroke style, flip-adjusted endpoints, bent/curved path data (with optional
31913
- * obstacle-avoiding A* routing), and arrow `<marker>` shapes. No framework
31914
- * imports; the actual `<svg>`/`<marker>`/`<path>` emission stays in each
31915
- * binding's view layer.
32452
+ * Dash patterns for connector lines.
31916
32453
  *
31917
- * Connector-family classification (`connectorKind` / `ConnectorKind`) lives in
31918
- * `connector-style.ts` and is re-used here rather than re-declared.
31919
- */
31920
- /**
31921
- * Derive all rendering geometry for a connector element.
32454
+ * Split out of `connector-path.ts` alongside the arrow markers: a connector's
32455
+ * stroke decoration is a separate concern from where the line is routed, and
32456
+ * both are configured independently in the inspector.
31922
32457
  *
31923
- * This is a pure function: no side-effects, no framework imports. The component
31924
- * calls this once per change-detection cycle inside a `computed()`.
32458
+ * Pure and framework-agnostic.
31925
32459
  */
31926
- function buildConnectorGeometry(element, zIndex, routing) {
31927
- const ss = hasShapeProperties(element) ? element.shapeStyle : undefined;
31928
- const strokeWidth = Math.max(0, ss?.strokeWidth ?? 2);
31929
- const strokeColor = ss?.strokeColor ?? DEFAULT_STROKE_COLOR;
31930
- const strokeOpacity = ss?.strokeOpacity ?? 1;
31931
- const dashArray = buildDashArray(ss?.strokeDash, strokeWidth, ss?.customDashSegments);
31932
- const strokeLinecap = svgLineCap(ss?.lineCap);
31933
- const compoundOffsets = getCompoundLineOffsets(ss?.compoundLine, strokeWidth);
31934
- const compoundWidths = getCompoundLineWidths(ss?.compoundLine, strokeWidth);
31935
- const svgW = Math.max(element.width, 1);
31936
- const svgH = Math.max(element.height, 1);
31937
- const x1 = element.flipHorizontal ? svgW : 0;
31938
- const y1 = element.flipVertical ? svgH : 0;
31939
- const x2 = element.flipHorizontal ? 0 : svgW;
31940
- const y2 = element.flipVertical ? 0 : svgH;
31941
- const shapeType = element.shapeType;
31942
- let pathD = buildConnectorPathD(shapeType, x1, y1, x2, y2, connectorBendFraction(element));
31943
- // Obstacle-avoiding A* routing for bent connectors. Routes in absolute slide
31944
- // coordinates (so it can detour outside the connector's own bounding box;
31945
- // the SVG uses `overflow: visible`), then translates waypoints back to
31946
- // element-local space for the path data.
31947
- if (routing &&
31948
- routing.obstacles.length > 0 &&
31949
- connectorKind(shapeType) === 'bent' &&
31950
- (element.width > 0 || element.height > 0)) {
31951
- const start = { x: element.x + x1, y: element.y + y1 };
31952
- const end = { x: element.x + x2, y: element.y + y2 };
31953
- const waypoints = routeOrthogonalConnector(start, end, routing.obstacles, {
31954
- canvasWidth: routing.canvasWidth,
31955
- canvasHeight: routing.canvasHeight,
31956
- });
31957
- if (waypoints.length > 2) {
31958
- const local = waypoints.map((p) => ({ x: p.x - element.x, y: p.y - element.y }));
31959
- pathD = waypointsToPathD(local);
31960
- }
31961
- }
31962
- const markerSeed = element.id.replace(/[^a-zA-Z0-9_-]/gu, '_');
31963
- const startMarkerId = `${markerSeed}-start`;
31964
- const endMarkerId = `${markerSeed}-end`;
31965
- const startArrow = normalizeArrow(ss?.connectorStartArrow);
31966
- const endArrow = normalizeArrow(ss?.connectorEndArrow);
31967
- const startMarker = startArrow
31968
- ? markerPath(startArrow, ss?.connectorStartArrowWidth, ss?.connectorStartArrowLength)
31969
- : null;
31970
- const endMarker = endArrow
31971
- ? markerPath(endArrow, ss?.connectorEndArrowWidth, ss?.connectorEndArrowLength)
31972
- : null;
31973
- const startMarkerRef = startMarker ? `url(#${startMarkerId})` : null;
31974
- const endMarkerRef = endMarker ? `url(#${endMarkerId})` : null;
31975
- const wrapperStyle = buildWrapperStyle(element, zIndex);
31976
- return {
31977
- strokeWidth,
31978
- strokeColor,
31979
- strokeOpacity,
31980
- dashArray,
31981
- strokeLinecap,
31982
- compoundOffsets,
31983
- compoundWidths,
31984
- svgW,
31985
- svgH,
31986
- x1,
31987
- y1,
31988
- x2,
31989
- y2,
31990
- pathD,
31991
- startMarkerId,
31992
- endMarkerId,
31993
- startMarker,
31994
- endMarker,
31995
- startMarkerRef,
31996
- endMarkerRef,
31997
- wrapperStyle,
31998
- };
31999
- }
32000
32460
  /**
32001
32461
  * Return the SVG `stroke-dasharray` string for a given OOXML stroke dash preset
32002
32462
  * and width, or `undefined` for solid lines (no attribute needed).
@@ -32021,6 +32481,195 @@ function buildDashArray(dash, strokeWidth, customDashSegments) {
32021
32481
  const dashType = segments ? 'custom' : normalizeStrokeDashType(dash);
32022
32482
  return getSvgStrokeDasharray(dashType, strokeWidth, segments);
32023
32483
  }
32484
+
32485
+ /**
32486
+ * connector-hit-target.ts: how wide a connector's pointer target has to be.
32487
+ *
32488
+ * WHY this exists at all: a connector's wrapper is `pointer-events: none`, so
32489
+ * an empty bounding box (connectors are usually large and mostly empty) never
32490
+ * swallows clicks meant for the shapes it spans. That also left the LINE
32491
+ * unclickable, so no pointer route reached a connector and the inspector's
32492
+ * connector card could only be opened from the Elements list. Every binding
32493
+ * therefore paints a transparent stroke along the path that opts back INTO hit
32494
+ * testing with `pointer-events: stroke`, keeping the target on the line and off
32495
+ * the box.
32496
+ *
32497
+ * WHY its own module: `buildConnectorGeometry` hands the width to the three
32498
+ * bindings that consume the geometry object, but Vanilla renders a connector
32499
+ * without ever building one, so it needs the rule and nothing else. Four
32500
+ * bindings agreeing on a magic number by copying it is how they drifted the
32501
+ * first time.
32502
+ *
32503
+ * @module render/connector-hit-target
32504
+ */
32505
+ /**
32506
+ * Narrowest a connector's pointer target may be, in px.
32507
+ *
32508
+ * WHY a floor: a hairline connector is one or two px of ink, and a target that
32509
+ * thin cannot be hit with a mouse, never mind a finger. PowerPoint shows the
32510
+ * same generosity around a line.
32511
+ */
32512
+ const CONNECTOR_HIT_MIN_WIDTH = 14;
32513
+ /**
32514
+ * Width of the transparent stroke that makes a connector clickable.
32515
+ *
32516
+ * WHY 3x: the target has to be comfortably wider than the ink so a near miss
32517
+ * still lands, while staying proportional for a thick line.
32518
+ */
32519
+ function connectorHitStrokeWidth(strokeWidth) {
32520
+ return Math.max(strokeWidth * 3, CONNECTOR_HIT_MIN_WIDTH);
32521
+ }
32522
+
32523
+ /**
32524
+ * Arrow-head marker shapes for connectors.
32525
+ *
32526
+ * A connector's line geometry and its end decorations are independent concerns:
32527
+ * routing answers "where does the line go", this module answers "what is drawn
32528
+ * at each end and how big is it". Splitting them keeps `connector-path.ts`
32529
+ * within the file-size rule and gives the arrow-size mapping a home of its own,
32530
+ * since it is the part users actually configure (the inspector's six arrowhead
32531
+ * controls all resolve to values consumed here).
32532
+ *
32533
+ * Pure and framework-agnostic: the `<marker>` element itself is emitted by each
32534
+ * binding's view layer from the {@link MarkerShape} returned here.
32535
+ */
32536
+ /**
32537
+ * Base `markerWidth`/`markerHeight` (in `strokeWidth` units) for a `med` arrow.
32538
+ * `sm`/`lg` scale relative to this, mirroring PowerPoint's discrete sizes.
32539
+ */
32540
+ const ARROW_BASE_MARKER_SIZE = 4;
32541
+ const ARROW_SIZE_SCALE = { sm: 0.6, med: 1, lg: 1.5 };
32542
+ /**
32543
+ * Map a `ConnectorArrowType` value to its SVG marker shape, scaling the marker
32544
+ * box by the arrow's width (`@w`) and length (`@len`) size tokens.
32545
+ *
32546
+ * The `<marker>` viewBox stays `0 0 10 10`; the returned {@link MarkerShape}
32547
+ * carries `markerWidth` (length, along the line) and `markerHeight` (width,
32548
+ * perpendicular) so bindings render `sm`/`med`/`lg` arrows at the right size
32549
+ * instead of a single fixed dimension.
32550
+ *
32551
+ * @param type Arrow head shape.
32552
+ * @param arrowWidth `@w` size token (perpendicular thickness). Defaults `med`.
32553
+ * @param arrowLength `@len` size token (length along the line). Defaults `med`.
32554
+ */
32555
+ function markerPath(type, arrowWidth, arrowLength) {
32556
+ const markerWidth = ARROW_BASE_MARKER_SIZE * (ARROW_SIZE_SCALE[arrowLength ?? 'med'] ?? 1);
32557
+ const markerHeight = ARROW_BASE_MARKER_SIZE * (ARROW_SIZE_SCALE[arrowWidth ?? 'med'] ?? 1);
32558
+ const box = { markerWidth, markerHeight };
32559
+ switch (type) {
32560
+ case 'diamond':
32561
+ return { shape: 'path', d: 'M5 0 L10 5 L5 10 L0 5 Z', ...box };
32562
+ case 'oval':
32563
+ return { shape: 'circle', ...box };
32564
+ case 'stealth':
32565
+ return { shape: 'path', d: 'M0 0 L10 5 L0 10 L3 5 Z', ...box };
32566
+ // triangle / arrow / fallback
32567
+ default:
32568
+ return { shape: 'path', d: 'M0 0 L10 5 L0 10 Z', ...box };
32569
+ }
32570
+ }
32571
+ /** Normalise a raw arrow type value: coerce `"none"` / `undefined` to `undefined`. */
32572
+ function normalizeArrow(a) {
32573
+ return a && a !== 'none' ? a : undefined;
32574
+ }
32575
+
32576
+ /**
32577
+ * Pure, framework-agnostic connector-geometry helpers shared across bindings.
32578
+ *
32579
+ * Derives the full set of SVG rendering values for a connector `PptxElement`:
32580
+ * stroke style, flip-adjusted endpoints, bent/curved path data (with optional
32581
+ * obstacle-avoiding A* routing), and arrow `<marker>` shapes. No framework
32582
+ * imports; the actual `<svg>`/`<marker>`/`<path>` emission stays in each
32583
+ * binding's view layer.
32584
+ *
32585
+ * Connector-family classification (`connectorKind` / `ConnectorKind`) lives in
32586
+ * `connector-style.ts` and is re-used here rather than re-declared.
32587
+ */
32588
+ /**
32589
+ * Derive all rendering geometry for a connector element.
32590
+ *
32591
+ * This is a pure function: no side-effects, no framework imports. The component
32592
+ * calls this once per change-detection cycle inside a `computed()`.
32593
+ */
32594
+ function buildConnectorGeometry(element, zIndex, routing) {
32595
+ const ss = hasShapeProperties(element) ? element.shapeStyle : undefined;
32596
+ const strokeWidth = Math.max(0, ss?.strokeWidth ?? 2);
32597
+ const strokeColor = ss?.strokeColor ?? DEFAULT_STROKE_COLOR;
32598
+ const strokeOpacity = ss?.strokeOpacity ?? 1;
32599
+ const dashArray = buildDashArray(ss?.strokeDash, strokeWidth, ss?.customDashSegments);
32600
+ const strokeLinecap = svgLineCap(ss?.lineCap);
32601
+ const compoundOffsets = getCompoundLineOffsets(ss?.compoundLine, strokeWidth);
32602
+ const compoundWidths = getCompoundLineWidths(ss?.compoundLine, strokeWidth);
32603
+ const svgW = Math.max(element.width, 1);
32604
+ const svgH = Math.max(element.height, 1);
32605
+ const x1 = element.flipHorizontal ? svgW : 0;
32606
+ const y1 = element.flipVertical ? svgH : 0;
32607
+ const x2 = element.flipHorizontal ? 0 : svgW;
32608
+ const y2 = element.flipVertical ? 0 : svgH;
32609
+ const shapeType = element.shapeType;
32610
+ let pathD = buildConnectorPathD(shapeType, x1, y1, x2, y2, connectorBendFraction(element));
32611
+ // Obstacle-avoiding A* routing for bent connectors. Routes in absolute slide
32612
+ // coordinates (so it can detour outside the connector's own bounding box;
32613
+ // the SVG uses `overflow: visible`), then translates waypoints back to
32614
+ // element-local space for the path data.
32615
+ if (routing &&
32616
+ routing.obstacles.length > 0 &&
32617
+ connectorKind(shapeType) === 'bent' &&
32618
+ (element.width > 0 || element.height > 0)) {
32619
+ const start = { x: element.x + x1, y: element.y + y1 };
32620
+ const end = { x: element.x + x2, y: element.y + y2 };
32621
+ const waypoints = routeOrthogonalConnector(start, end, routing.obstacles, {
32622
+ canvasWidth: routing.canvasWidth,
32623
+ canvasHeight: routing.canvasHeight,
32624
+ });
32625
+ if (waypoints.length > 2) {
32626
+ const local = waypoints.map((p) => ({ x: p.x - element.x, y: p.y - element.y }));
32627
+ pathD = waypointsToPathD(local);
32628
+ }
32629
+ }
32630
+ const markerSeed = element.id.replace(/[^a-zA-Z0-9_-]/gu, '_');
32631
+ const startMarkerId = `${markerSeed}-start`;
32632
+ const endMarkerId = `${markerSeed}-end`;
32633
+ const startArrow = normalizeArrow(ss?.connectorStartArrow);
32634
+ const endArrow = normalizeArrow(ss?.connectorEndArrow);
32635
+ const startMarker = startArrow
32636
+ ? markerPath(startArrow, ss?.connectorStartArrowWidth, ss?.connectorStartArrowLength)
32637
+ : null;
32638
+ const endMarker = endArrow
32639
+ ? markerPath(endArrow, ss?.connectorEndArrowWidth, ss?.connectorEndArrowLength)
32640
+ : null;
32641
+ const startMarkerRef = startMarker ? `url(#${startMarkerId})` : null;
32642
+ const endMarkerRef = endMarker ? `url(#${endMarkerId})` : null;
32643
+ const wrapperStyle = buildWrapperStyle(element, zIndex);
32644
+ return {
32645
+ strokeWidth,
32646
+ strokeColor,
32647
+ strokeOpacity,
32648
+ dashArray,
32649
+ strokeLinecap,
32650
+ compoundOffsets,
32651
+ compoundWidths,
32652
+ svgW,
32653
+ svgH,
32654
+ x1,
32655
+ y1,
32656
+ x2,
32657
+ y2,
32658
+ pathD,
32659
+ startMarkerId,
32660
+ endMarkerId,
32661
+ startMarker,
32662
+ endMarker,
32663
+ startMarkerRef,
32664
+ endMarkerRef,
32665
+ // A straight connector has no `pathD`, so the hit target falls back to its
32666
+ // endpoints; both forms are a `path`, which keeps the binding templates to
32667
+ // a single node instead of a line/path branch of their own.
32668
+ hitPathD: pathD ?? `M${x1},${y1} L${x2},${y2}`,
32669
+ hitStrokeWidth: connectorHitStrokeWidth(strokeWidth),
32670
+ wrapperStyle,
32671
+ };
32672
+ }
32024
32673
  /**
32025
32674
  * Normalise a connector's first adjustment value (`adj1`/`adj`) to a 0..1
32026
32675
  * fraction that positions the elbow / curve mid-axis. OOXML stores these in
@@ -32067,46 +32716,6 @@ function buildConnectorPathD(shapeType, x1, y1, x2, y2, bend) {
32067
32716
  }
32068
32717
  return `M${x1},${y1} C${mx},${y1} ${mx},${y2} ${x2},${y2}`;
32069
32718
  }
32070
- /**
32071
- * Base `markerWidth`/`markerHeight` (in `strokeWidth` units) for a `med` arrow.
32072
- * `sm`/`lg` scale relative to this, mirroring PowerPoint's discrete sizes and
32073
- * the React binding's `ARROW_SIZE_SCALE`.
32074
- */
32075
- const ARROW_BASE_MARKER_SIZE = 4;
32076
- const ARROW_SIZE_SCALE = { sm: 0.6, med: 1, lg: 1.5 };
32077
- /**
32078
- * Map a `ConnectorArrowType` value to its SVG marker shape, scaling the marker
32079
- * box by the arrow's width (`@w`) and length (`@len`) size tokens.
32080
- *
32081
- * The `<marker>` viewBox stays `0 0 10 10`; the returned {@link MarkerShape}
32082
- * carries `markerWidth` (length, along the line) and `markerHeight` (width,
32083
- * perpendicular) so bindings render `sm`/`med`/`lg` arrows at the right size
32084
- * instead of a single fixed dimension.
32085
- *
32086
- * @param type Arrow head shape.
32087
- * @param arrowWidth `@w` size token (perpendicular thickness). Defaults `med`.
32088
- * @param arrowLength `@len` size token (length along the line). Defaults `med`.
32089
- */
32090
- function markerPath(type, arrowWidth, arrowLength) {
32091
- const markerWidth = ARROW_BASE_MARKER_SIZE * (ARROW_SIZE_SCALE[arrowLength ?? 'med'] ?? 1);
32092
- const markerHeight = ARROW_BASE_MARKER_SIZE * (ARROW_SIZE_SCALE[arrowWidth ?? 'med'] ?? 1);
32093
- const box = { markerWidth, markerHeight };
32094
- switch (type) {
32095
- case 'diamond':
32096
- return { shape: 'path', d: 'M5 0 L10 5 L5 10 L0 5 Z', ...box };
32097
- case 'oval':
32098
- return { shape: 'circle', ...box };
32099
- case 'stealth':
32100
- return { shape: 'path', d: 'M0 0 L10 5 L0 10 L3 5 Z', ...box };
32101
- // triangle / arrow / fallback
32102
- default:
32103
- return { shape: 'path', d: 'M0 0 L10 5 L0 10 Z', ...box };
32104
- }
32105
- }
32106
- /** Normalise a raw arrow type value: coerce `"none"` / `undefined` → `undefined`. */
32107
- function normalizeArrow(a) {
32108
- return a && a !== 'none' ? a : undefined;
32109
- }
32110
32719
  /**
32111
32720
  * Build the inline `style` string for the connector wrapper `<div>`.
32112
32721
  * Position, size, z-index, rotation, opacity, and visibility.
@@ -40864,25 +41473,21 @@ function mobileSlideCounter(currentIndex, total) {
40864
41473
  }
40865
41474
  return `${currentIndex + 1} / ${total}`;
40866
41475
  }
40867
- /**
40868
- * Whether the deck is on its first slide (used to disable the "previous"
40869
- * control). Negative indices are treated as the first slide.
40870
- */
40871
- function isFirstSlide(currentIndex) {
40872
- return currentIndex <= 0;
40873
- }
40874
- /**
40875
- * Whether the deck is on its last slide (used to disable the "next" control).
40876
- * An out-of-range index past the end also counts as the last slide.
40877
- */
40878
- function isLastSlide(currentIndex, total) {
40879
- return currentIndex >= total - 1;
40880
- }
41476
+ // The phone layout deliberately owns NO first/last predicates of its own.
41477
+ //
41478
+ // It used to: an `isFirstSlide` / `isLastSlide` pair lived here, and all three
41479
+ // phone consoles wired Next's `disabled` to `isLastSlide`. The desktop console
41480
+ // answers the same question with `presenterNextDisabled` in `presenter-chrome`,
41481
+ // which is always false because PowerPoint advances from the last slide to the
41482
+ // end-of-show screen and then out of the show. So the same deck stranded a
41483
+ // presenter on a phone and let them finish on a laptop. Both consoles now read
41484
+ // `presenterPrevDisabled` / `presenterNextDisabled` from `presenter-chrome`, and
41485
+ // there is no near-miss duplicate here for a fourth port to reach for.
40881
41486
  // ---------------------------------------------------------------------------
40882
41487
  // Elapsed-time formatting
40883
41488
  // ---------------------------------------------------------------------------
40884
41489
  /** Zero-pad a non-negative integer to at least two digits. */
40885
- function pad2$1(value) {
41490
+ function pad2(value) {
40886
41491
  return String(value).padStart(2, '0');
40887
41492
  }
40888
41493
  /**
@@ -40910,9 +41515,9 @@ function formatMobileElapsed(elapsedMs) {
40910
41515
  const minutes = Math.floor((totalSeconds % 3600) / 60);
40911
41516
  const seconds = totalSeconds % 60;
40912
41517
  if (hours > 0) {
40913
- return `${pad2$1(hours)}:${pad2$1(minutes)}:${pad2$1(seconds)}`;
41518
+ return `${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}`;
40914
41519
  }
40915
- return `${pad2$1(minutes)}:${pad2$1(seconds)}`;
41520
+ return `${pad2(minutes)}:${pad2(seconds)}`;
40916
41521
  }
40917
41522
 
40918
41523
  /**
@@ -41382,7 +41987,7 @@ function formatTime(date) {
41382
41987
  * Format a millisecond duration as `MM:SS`, or `HH:MM:SS` once the elapsed time
41383
41988
  * reaches one hour.
41384
41989
  */
41385
- function formatElapsed$1(elapsedMs) {
41990
+ function formatElapsed(elapsedMs) {
41386
41991
  const totalSeconds = Math.floor(elapsedMs / 1000);
41387
41992
  const hours = Math.floor(totalSeconds / 3600);
41388
41993
  const minutes = Math.floor((totalSeconds % 3600) / 60);
@@ -42560,6 +43165,414 @@ function presentToolbarStyleAttr() {
42560
43165
  .join(';');
42561
43166
  }
42562
43167
 
43168
+ /**
43169
+ * The presenter view (PowerPoint's "presenter console"), described once for all
43170
+ * five bindings.
43171
+ *
43172
+ * `presenter-view.ts` already shares the presenter console's ARITHMETIC (elapsed
43173
+ * formatting, notes font-size clamping, rich-notes -> span spec) and
43174
+ * `presenter-console.ts` its STATE helpers (timer, zoom, pointer, ink). What was
43175
+ * never shared is the console's CONTENT, and that is what drifted, in exactly
43176
+ * the way `present-chrome.ts` documents for the show toolbar:
43177
+ *
43178
+ * - React ships a 16-slot strip whose accessible names are hard-coded English
43179
+ * `title` attributes, so the console is untranslatable in every locale.
43180
+ * - Vue renders its strip ONLY in the empty-deck branch, so with a real deck
43181
+ * the console has no timer, zoom, annotation, blackout, captions or end
43182
+ * control at all.
43183
+ * - Angular re-labels the same strip in a component with no `translate` pipe,
43184
+ * orders zoom `-` before `+` (React orders `+` first), and calls reset-zoom
43185
+ * "Fit".
43186
+ * - Vanilla has no presenter view whatsoever: `mountPresenterConsole` lays a
43187
+ * button strip over the live show, with no current slide, next-slide
43188
+ * preview, notes, clock or timer.
43189
+ * - Svelte drops the timer progress bar, renders notes as plain text (losing
43190
+ * every `notesSegments` run style) and clamps the notes font size to its own
43191
+ * 12..36 instead of the shared 10..32.
43192
+ *
43193
+ * So the inventory, the order, the label KEYS and the measurements live here,
43194
+ * and every binding derives its console from these constants:
43195
+ *
43196
+ * - React, Vue and Angular apply {@link PRESENTER_CONSOLE_CLASSES} (Tailwind).
43197
+ * - Vanilla interpolates {@link PRESENTER_LAYOUT_METRICS} into its CSS-in-TS.
43198
+ * - Svelte reads {@link presenterConsoleCssVars} off an inline `style`
43199
+ * attribute, because its scoped `<style>` is compiled and cannot see a
43200
+ * TypeScript value.
43201
+ *
43202
+ * `presenter-chrome.test.ts` asserts the Tailwind tokens still encode exactly
43203
+ * the numbers here, which is what stops the two representations drifting.
43204
+ *
43205
+ * @module render/presenter-chrome
43206
+ */
43207
+ /**
43208
+ * The presenter console strip, in order.
43209
+ *
43210
+ * Order and ids follow React, which is the binding the parity specs measure
43211
+ * against. The label KEYS are new: React's strings were hard-coded English, so
43212
+ * adopting a key per slot is what makes the console translatable at all. Where
43213
+ * an existing key already carried the exact English React rendered, it is
43214
+ * reused rather than duplicated (`pptx.presentation.pen` and friends), because
43215
+ * a near-miss duplicate is precisely how `pptx.presenter.elapsed` and
43216
+ * `pptx.mpresenter.elapsed` came to coexist.
43217
+ */
43218
+ const PRESENTER_CONSOLE_CONTROLS = [
43219
+ {
43220
+ id: 'timer-toggle',
43221
+ kind: 'button',
43222
+ labelKey: 'pptx.presenter.toggleTimer',
43223
+ icon: 'circle-pause',
43224
+ activeIcon: 'circle-play',
43225
+ },
43226
+ { id: 'timer-reset', kind: 'button', labelKey: 'pptx.presenter.resetTimer', icon: 'rotate-ccw' },
43227
+ { id: 'divider-timer', kind: 'divider' },
43228
+ {
43229
+ id: 'all-slides',
43230
+ kind: 'button',
43231
+ labelKey: 'pptx.presenter.seeAllSlides',
43232
+ icon: 'grid-2x2',
43233
+ },
43234
+ { id: 'zoom-in', kind: 'button', labelKey: 'pptx.presenter.zoomIn', icon: 'zoom-in' },
43235
+ { id: 'zoom-out', kind: 'button', labelKey: 'pptx.presenter.zoomOut', icon: 'zoom-out' },
43236
+ { id: 'zoom-reset', kind: 'button', labelKey: 'pptx.presenter.resetZoom', icon: 'scan' },
43237
+ { id: 'divider-zoom', kind: 'divider' },
43238
+ {
43239
+ id: 'laser',
43240
+ kind: 'toggle',
43241
+ labelKey: 'pptx.presentation.laserPointer',
43242
+ icon: 'mouse-pointer-2',
43243
+ },
43244
+ { id: 'pen', kind: 'toggle', labelKey: 'pptx.presentation.pen', icon: 'pen-tool' },
43245
+ {
43246
+ id: 'highlighter',
43247
+ kind: 'toggle',
43248
+ labelKey: 'pptx.presentation.highlighter',
43249
+ icon: 'highlighter',
43250
+ },
43251
+ { id: 'eraser', kind: 'toggle', labelKey: 'pptx.presentation.eraser', icon: 'eraser' },
43252
+ { id: 'divider-tools', kind: 'divider' },
43253
+ { id: 'blackout-black', kind: 'toggle', labelKey: 'pptx.presenter.blackScreen', glyph: 'B' },
43254
+ { id: 'blackout-white', kind: 'toggle', labelKey: 'pptx.presenter.whiteScreen', glyph: 'W' },
43255
+ {
43256
+ id: 'captions',
43257
+ kind: 'toggle',
43258
+ labelKey: 'pptx.slideShow.subtitlesTooltip',
43259
+ icon: 'captions',
43260
+ },
43261
+ { id: 'spacer', kind: 'spacer' },
43262
+ {
43263
+ id: 'audience',
43264
+ kind: 'toggle',
43265
+ labelKey: 'pptx.presenter.openAudienceWindow',
43266
+ activeLabelKey: 'pptx.presenter.closeAudienceWindow',
43267
+ icon: 'monitor',
43268
+ activeIcon: 'monitor-off',
43269
+ },
43270
+ {
43271
+ id: 'swap-displays',
43272
+ kind: 'button',
43273
+ labelKey: 'pptx.presenter.swapDisplays',
43274
+ icon: 'arrow-left-right',
43275
+ },
43276
+ { id: 'end', kind: 'button', labelKey: 'pptx.presenter.endPresentation', icon: 'x' },
43277
+ ];
43278
+ /** The strip's control ids in render order, for tests and parity specs. */
43279
+ const PRESENTER_CONSOLE_ORDER = PRESENTER_CONSOLE_CONTROLS.map((control) => control.id);
43280
+ /**
43281
+ * The strip's accessible-name i18n keys, in order, skipping the slots that have
43282
+ * none. A parity spec resolves these through the dictionary rather than
43283
+ * hard-coding English, so the inventory survives a translation change.
43284
+ */
43285
+ const PRESENTER_CONSOLE_LABEL_KEYS = PRESENTER_CONSOLE_CONTROLS.flatMap((control) => (control.labelKey === undefined ? [] : [control.labelKey]));
43286
+ /**
43287
+ * The rail's navigation and notes controls, in order.
43288
+ *
43289
+ * `next` is deliberately absent from any "disabled on the last slide" rule; see
43290
+ * {@link presenterNextDisabled}.
43291
+ */
43292
+ const PRESENTER_RAIL_CONTROLS = [
43293
+ { id: 'prev', labelKey: 'pptx.presenter.prev', icon: 'chevron-left' },
43294
+ { id: 'next', labelKey: 'pptx.presenter.next', icon: 'chevron-right' },
43295
+ { id: 'notes-font-decrease', labelKey: 'pptx.presenter.decreaseFontSize', icon: 'minus' },
43296
+ { id: 'notes-font-increase', labelKey: 'pptx.presenter.increaseFontSize', icon: 'plus' },
43297
+ ];
43298
+ /** Static headings and read-only readouts in the rail, keyed for translation. */
43299
+ const PRESENTER_RAIL_LABEL_KEYS = {
43300
+ currentTime: 'pptx.presenter.currentTime',
43301
+ elapsed: 'pptx.presenter.elapsed',
43302
+ nextSlidePreview: 'pptx.presenter.nextSlidePreview',
43303
+ endOfPresentation: 'pptx.presenter.endOfPresentation',
43304
+ speakerNotes: 'pptx.presenter.speakerNotes',
43305
+ noNotes: 'pptx.presenter.noNotes',
43306
+ slideLabel: 'pptx.presenter.slideLabel',
43307
+ timerProgress: 'pptx.presenter.timerProgress',
43308
+ noSlides: 'pptx.presenter.noSlides',
43309
+ };
43310
+ /** Headings and controls of the "all slides" navigator overlay. */
43311
+ const PRESENTER_NAVIGATOR_LABEL_KEYS = {
43312
+ title: 'pptx.presenter.slideNavigator',
43313
+ subtitle: 'pptx.presenter.seeAllSlides',
43314
+ close: 'pptx.presenter.closeNavigator',
43315
+ };
43316
+ /**
43317
+ * Whether the rail's Next button should be disabled.
43318
+ *
43319
+ * Always `false`, and it is a function rather than a constant so the rule has
43320
+ * one place to be read and cited. PowerPoint's console advances from the last
43321
+ * slide to the end-of-show screen and then out of the show. Vue, Angular and
43322
+ * Svelte each independently added `disabled={current >= slides.length - 1}`,
43323
+ * which strands the presenter on the final slide with no way to finish, so the
43324
+ * audience display never closes either. React's `PresenterNotesRail` carries a
43325
+ * comment saying exactly this; the comment was not enough to stop three ports
43326
+ * making the same call, so the rule is now code.
43327
+ */
43328
+ function presenterNextDisabled() {
43329
+ return false;
43330
+ }
43331
+ /**
43332
+ * Whether the rail's Previous button should be disabled: only on the first
43333
+ * slide, where there is genuinely nowhere to go back to.
43334
+ */
43335
+ function presenterPrevDisabled(currentIndex) {
43336
+ return currentIndex <= 0;
43337
+ }
43338
+ /**
43339
+ * How much elapsed time one fill of the console's progress bar represents.
43340
+ *
43341
+ * Five minutes, the interval PowerPoint's own console paces a talk in. It was
43342
+ * inlined in React, re-derived in Vue and given a helper of its own in Angular,
43343
+ * while Vanilla and Svelte had no bar at all.
43344
+ */
43345
+ const PRESENTER_TIMER_SEGMENT_MS = 5 * 60 * 1000;
43346
+ /**
43347
+ * Split an elapsed duration into the console's progress-bar reading.
43348
+ *
43349
+ * Negative input is clamped: a snapshot restored from a peer can arrive with a
43350
+ * start time in the future, and a negative `aria-valuenow` is invalid ARIA.
43351
+ */
43352
+ function presenterTimerProgress(elapsedMs) {
43353
+ const elapsed = Math.max(0, elapsedMs);
43354
+ return {
43355
+ percent: Math.min(100, ((elapsed % PRESENTER_TIMER_SEGMENT_MS) / PRESENTER_TIMER_SEGMENT_MS) * 100),
43356
+ segment: Math.floor(elapsed / PRESENTER_TIMER_SEGMENT_MS),
43357
+ };
43358
+ }
43359
+
43360
+ /**
43361
+ * `presenter-chrome-metrics` - the presenter console's measurements, in the two
43362
+ * representations the five bindings can consume.
43363
+ *
43364
+ * Split out of `presenter-chrome.ts` purely to keep both files inside the
43365
+ * repo's 300-line ceiling; the inventory lives there, the geometry lives here,
43366
+ * and `presenter-chrome.test.ts` asserts the Tailwind strings below still
43367
+ * encode the numbers in {@link PRESENTER_LAYOUT_METRICS}.
43368
+ *
43369
+ * Every number here was a magic constant in at least three bindings before this
43370
+ * module: the rail was 260..440 wide in React and Vue but 300..460 in Svelte,
43371
+ * the navigator grid used three different track sizes and gaps, and the console
43372
+ * root sat at `z-index` 50, 100 and 120 depending on which file you opened.
43373
+ *
43374
+ * @module render/presenter-chrome-metrics
43375
+ */
43376
+ /** Presenter-console measurements, in CSS pixels unless the name says otherwise. */
43377
+ const PRESENTER_LAYOUT_METRICS = {
43378
+ /**
43379
+ * Flex growth of the current-slide pane against the rail (`flex-[7]` /
43380
+ * `flex-[3]`), i.e. PowerPoint's roughly 70/30 console split.
43381
+ */
43382
+ mainFlex: 7,
43383
+ railFlex: 3,
43384
+ /** Rail width bounds (`min-w-[260px] max-w-[440px]`). */
43385
+ railMinWidth: 260,
43386
+ railMaxWidth: 440,
43387
+ /** Padding around the current-slide pane (`p-6`). */
43388
+ mainPadding: 24,
43389
+ /** Stacking order of the console over the show stage (`z-50`). */
43390
+ zIndex: 50,
43391
+ /** Stacking order of the "all slides" navigator over the console (`z-[120]`). */
43392
+ navigatorZIndex: 120,
43393
+ /** Console strip control edge length (`h-9 min-w-9`). */
43394
+ controlSize: 36,
43395
+ /** Icon edge length inside a strip control. */
43396
+ controlIconSize: 16,
43397
+ /** Flex gap between strip children (`gap-1`). */
43398
+ stripGap: 4,
43399
+ /** Strip padding (`px-3 py-2`). */
43400
+ stripPaddingX: 12,
43401
+ stripPaddingY: 8,
43402
+ /** Control corner radius (`rounded-md`). */
43403
+ controlRadius: 6,
43404
+ /** Divider width / height / margin (`w-px h-6 mx-1`). */
43405
+ dividerWidth: 1,
43406
+ dividerHeight: 24,
43407
+ dividerMarginX: 4,
43408
+ /** Timer progress bar height (`h-1.5`). */
43409
+ progressHeight: 6,
43410
+ /** Next-slide preview target width, before aspect-ratio scaling. */
43411
+ nextPreviewWidth: 240,
43412
+ /** Navigator grid track minimum and gap (`minmax(220px,1fr)`, `gap-5`). */
43413
+ navigatorTrackMin: 220,
43414
+ navigatorGap: 20,
43415
+ /** Navigator tile preview width. */
43416
+ navigatorTileWidth: 200,
43417
+ /** Opacity of a hidden slide's navigator tile: dimmed, never omitted. */
43418
+ hiddenSlideOpacity: 0.45,
43419
+ };
43420
+ /**
43421
+ * Tailwind tokens for the presenter console, applied by React, Vue and Angular.
43422
+ *
43423
+ * Whole class strings rather than per-call-site assembly, for the reason
43424
+ * `present-chrome.ts` gives: three bindings hand-writing `h-9 min-w-9 rounded-md
43425
+ * ...` is how one of them ended up with a differently sized strip.
43426
+ */
43427
+ const PRESENTER_CONSOLE_CLASSES = {
43428
+ /** The console root, laid over the show stage. */
43429
+ root: 'absolute inset-0 z-50 flex flex-col bg-card text-foreground',
43430
+ /** The control strip across the top. */
43431
+ strip: 'flex flex-wrap items-center gap-1 border-b border-border bg-card px-3 py-2',
43432
+ /** A strip control, inactive. */
43433
+ control: 'inline-flex h-9 min-w-9 items-center justify-center gap-2 rounded-md px-2 text-xs transition-colors bg-muted text-foreground hover:bg-accent',
43434
+ /** A strip control, active. */
43435
+ controlActive: 'inline-flex h-9 min-w-9 items-center justify-center gap-2 rounded-md px-2 text-xs transition-colors bg-primary text-primary-foreground',
43436
+ /** Vertical hairline between strip groups. */
43437
+ divider: 'mx-1 h-6 w-px bg-border',
43438
+ /** Flexible gap pushing the trailing group right. */
43439
+ spacer: 'flex-1',
43440
+ /** The body row: current-slide pane plus rail. */
43441
+ body: 'flex flex-1 min-h-0',
43442
+ /** The current-slide pane. */
43443
+ main: 'relative flex-[7] flex flex-col items-center justify-center bg-black p-6 min-w-0 overflow-hidden',
43444
+ /** The right-hand rail. */
43445
+ rail: 'flex flex-[3] min-w-[260px] max-w-[440px] flex-col border-l border-border bg-background',
43446
+ /** Rail section heading (Current Time / Next Slide / Speaker Notes). */
43447
+ railHeading: 'text-[10px] uppercase tracking-wider text-muted-foreground',
43448
+ /** Timer progress bar track. */
43449
+ progressTrack: 'h-1.5 w-full bg-muted/60 flex-shrink-0',
43450
+ /** Timer progress bar fill. */
43451
+ progressFill: 'h-full bg-primary transition-[width] duration-1000 ease-linear',
43452
+ /** The "all slides" navigator overlay. */
43453
+ navigator: 'absolute inset-0 z-[120] overflow-auto bg-card p-6',
43454
+ /** The navigator's tile grid. */
43455
+ navigatorGrid: 'grid gap-5 [grid-template-columns:repeat(auto-fill,minmax(220px,1fr))]',
43456
+ };
43457
+ /**
43458
+ * The console metrics as CSS custom properties, for the two bindings that
43459
+ * cannot read a Tailwind class (Vanilla's CSS-in-TS, Svelte's scoped style).
43460
+ */
43461
+ function presenterConsoleCssVars() {
43462
+ const m = PRESENTER_LAYOUT_METRICS;
43463
+ return {
43464
+ '--pptx-pv-main-flex': String(m.mainFlex),
43465
+ '--pptx-pv-rail-flex': String(m.railFlex),
43466
+ '--pptx-pv-rail-min': `${String(m.railMinWidth)}px`,
43467
+ '--pptx-pv-rail-max': `${String(m.railMaxWidth)}px`,
43468
+ '--pptx-pv-main-pad': `${String(m.mainPadding)}px`,
43469
+ '--pptx-pv-z': String(m.zIndex),
43470
+ '--pptx-pv-nav-z': String(m.navigatorZIndex),
43471
+ '--pptx-pv-control': `${String(m.controlSize)}px`,
43472
+ '--pptx-pv-control-icon': `${String(m.controlIconSize)}px`,
43473
+ '--pptx-pv-strip-gap': `${String(m.stripGap)}px`,
43474
+ '--pptx-pv-strip-pad-x': `${String(m.stripPaddingX)}px`,
43475
+ '--pptx-pv-strip-pad-y': `${String(m.stripPaddingY)}px`,
43476
+ '--pptx-pv-control-radius': `${String(m.controlRadius)}px`,
43477
+ '--pptx-pv-divider-w': `${String(m.dividerWidth)}px`,
43478
+ '--pptx-pv-divider-h': `${String(m.dividerHeight)}px`,
43479
+ '--pptx-pv-divider-mx': `${String(m.dividerMarginX)}px`,
43480
+ '--pptx-pv-progress-h': `${String(m.progressHeight)}px`,
43481
+ '--pptx-pv-next-preview-w': `${String(m.nextPreviewWidth)}px`,
43482
+ '--pptx-pv-nav-track-min': `${String(m.navigatorTrackMin)}px`,
43483
+ '--pptx-pv-nav-gap': `${String(m.navigatorGap)}px`,
43484
+ '--pptx-pv-nav-tile-w': `${String(m.navigatorTileWidth)}px`,
43485
+ '--pptx-pv-hidden-opacity': String(m.hiddenSlideOpacity),
43486
+ };
43487
+ }
43488
+ /** {@link presenterConsoleCssVars} flattened into an inline `style` attribute. */
43489
+ function presenterConsoleStyleAttr() {
43490
+ return Object.entries(presenterConsoleCssVars())
43491
+ .map(([name, value]) => `${name}:${value}`)
43492
+ .join(';');
43493
+ }
43494
+
43495
+ /**
43496
+ * `presenter-show-lifecycle` - keeping a slide show alive across the fullscreen
43497
+ * bounce that opening the audience display causes.
43498
+ *
43499
+ * WHY this exists at all. A running show holds the Fullscreen API on the
43500
+ * presenter's own screen. Opening the audience display is a `window.open` with
43501
+ * `popup=yes`, and every engine drops the opener out of fullscreen when a popup
43502
+ * takes focus. The `fullscreenchange` that announces it is dispatched on a
43503
+ * later task, which puts a binding in a bind:
43504
+ *
43505
+ * - Reading its own "am I presenting" state right after `window.open` reads a
43506
+ * value the pending event has not updated yet, so the code concludes the
43507
+ * show is still running and does not re-assert it. The event then lands,
43508
+ * the generic "the user left fullscreen, so end the show" handler fires,
43509
+ * and the presenter's deck collapses back to the editor the instant they
43510
+ * ask for presenter view. That is exactly the Vanilla defect: the console
43511
+ * mounts correctly and the show underneath it is torn down.
43512
+ * - Reading `document.fullscreenElement` instead is no better. It is
43513
+ * authoritative but it is sampled at a moment when the browser may or may
43514
+ * not have released fullscreen yet, so the answer is a coin toss.
43515
+ *
43516
+ * Neither reading is wrong; the mistake is asking a state question at all. The
43517
+ * only thing that distinguishes "the popup stole fullscreen" from "the
43518
+ * presenter pressed Escape" is INTENT, and intent is known at the call site
43519
+ * that opened the popup. So the opener arms a one-shot latch here and the
43520
+ * fullscreen handler asks this module what the exit meant.
43521
+ *
43522
+ * React already had a private version of this (`switchingToPresenterRef` in
43523
+ * `usePresentationMode`), which is why React alone never lost its show. This
43524
+ * module is that latch, made shared, time-bounded and testable, so all five
43525
+ * bindings answer the question the same way.
43526
+ *
43527
+ * DOM-free by design: it holds no window, registers no listener, and only ever
43528
+ * answers a question. Each binding keeps its own fullscreen plumbing.
43529
+ *
43530
+ * @module render/presenter-show-lifecycle
43531
+ */
43532
+ /**
43533
+ * How long after opening the audience display a fullscreen exit is still
43534
+ * attributed to the popup rather than to the presenter.
43535
+ *
43536
+ * The latch is one-shot, so this bound only matters when the popup never
43537
+ * triggers a bounce at all (a popup blocker, or an engine that keeps the opener
43538
+ * fullscreen). Without it the latch would sit armed forever and silently
43539
+ * swallow the presenter's next real Escape. A second is far longer than the
43540
+ * event needs and far shorter than any deliberate follow-up action.
43541
+ */
43542
+ const AUDIENCE_FULLSCREEN_BOUNCE_MS = 1000;
43543
+ /**
43544
+ * Create a {@link PresenterShowGuard}.
43545
+ *
43546
+ * @param options.graceMs - Override {@link AUDIENCE_FULLSCREEN_BOUNCE_MS}; tests
43547
+ * pass a tiny value to prove the latch expires.
43548
+ */
43549
+ function createPresenterShowGuard(options) {
43550
+ const graceMs = options?.graceMs ?? AUDIENCE_FULLSCREEN_BOUNCE_MS;
43551
+ let armedAt;
43552
+ const stillArmed = (now) => armedAt !== undefined && now - armedAt >= 0 && now - armedAt <= graceMs;
43553
+ return {
43554
+ expectAudienceBounce(now = Date.now()) {
43555
+ armedAt = now;
43556
+ },
43557
+ classifyFullscreenExit(now = Date.now()) {
43558
+ if (!stillArmed(now)) {
43559
+ armedAt = undefined;
43560
+ return 'end-show';
43561
+ }
43562
+ // One-shot: a popup causes exactly one bounce, and a second exit in the
43563
+ // same window is the presenter reacting to what they now see.
43564
+ armedAt = undefined;
43565
+ return 'restore-show';
43566
+ },
43567
+ isExpectingBounce(now = Date.now()) {
43568
+ return stillArmed(now);
43569
+ },
43570
+ disarm() {
43571
+ armedAt = undefined;
43572
+ },
43573
+ };
43574
+ }
43575
+
42563
43576
  /**
42564
43577
  * PowerPoint-accurate slide-show keyboard map.
42565
43578
  *
@@ -45902,6 +46915,361 @@ function isElementActionable(element, options = {}) {
45902
46915
  return element.type === 'zoom' && hasZoomHandler;
45903
46916
  }
45904
46917
 
46918
+ /**
46919
+ * On-canvas affordances for an element that carries a PowerPoint Action Setting.
46920
+ *
46921
+ * PowerPoint's editing view tells the author, without a click, which shapes are
46922
+ * wired up: an action shape gets a marker, and hovering one shows where it goes.
46923
+ * The viewer mirrors that with two affordances:
46924
+ *
46925
+ * - the **action indicator**, a small amber lightning badge pinned to the
46926
+ * element's top-right corner whenever it owns an `a:hlinkClick` (click) or
46927
+ * `a:hlinkHover` (mouse-over) action;
46928
+ * - the **link tooltip**, a popover under the element naming the destination
46929
+ * plus how to follow it.
46930
+ *
46931
+ * Both are AUTHORING chrome, so both are gated on the live editing canvas.
46932
+ * Painting them during a slide show would be wrong twice over: the audience
46933
+ * would see editor furniture, and the badge would sit on top of the very shape
46934
+ * the presenter is trying to click. `presenting` is therefore a hard veto, not
46935
+ * merely the absence of `canInteract`, because the four non-React bindings run
46936
+ * their presentation stage through the same renderer as the editing one.
46937
+ *
46938
+ * Only React drew either of them; the tooltip additionally existed in Vue. That
46939
+ * divergence was also the last residual noise in the cross-binding render
46940
+ * fingerprints, since the badge contributes an `<svg>` and the tooltip
46941
+ * contributes text to an otherwise textless element. The rule and the styling
46942
+ * therefore both live here, so the five bindings cannot drift apart again: a
46943
+ * binding supplies only the markup, never the decision or the look.
46944
+ *
46945
+ * @module render/element-action-affordance
46946
+ */
46947
+ /** Class on the element box that hosts a link tooltip (drives `:hover`). */
46948
+ const LINK_TOOLTIP_HOST_CLASS = 'pptx-link-host';
46949
+ /** Class on the tooltip's outer positioning box. */
46950
+ const LINK_TOOLTIP_CLASS = 'pptx-link-tooltip';
46951
+ /** Class on the tooltip's bordered panel. */
46952
+ const LINK_TOOLTIP_PANEL_CLASS = 'pptx-link-tooltip-panel';
46953
+ /** Class on the tooltip's destination line. */
46954
+ const LINK_TOOLTIP_LABEL_CLASS = 'pptx-link-tooltip-label';
46955
+ /** Class on the tooltip's "how to follow this" hint line. */
46956
+ const LINK_TOOLTIP_HINT_CLASS = 'pptx-link-tooltip-hint';
46957
+ /** Class on the amber action badge. */
46958
+ const ACTION_INDICATOR_CLASS = 'pptx-action-indicator';
46959
+ /**
46960
+ * `d` of the lightning bolt inside the badge, on a `0 0 24 24` viewBox.
46961
+ *
46962
+ * Shared rather than restated per binding so the glyph cannot drift; it is the
46963
+ * same path React has drawn since the badge was introduced.
46964
+ */
46965
+ const ACTION_INDICATOR_ICON_PATH = 'M13 2L3 14h9l-1 8 10-12h-9l1-8z';
46966
+ /**
46967
+ * Styling for both affordances, injected once per document by each binding.
46968
+ *
46969
+ * Written as plain CSS against the `--pptx-*` theme tokens (rather than as
46970
+ * Tailwind utilities, which only React and Vue can use) so Angular, Vanilla and
46971
+ * Svelte get the identical look for free, and so a theme override reaches all
46972
+ * five.
46973
+ *
46974
+ * `font-family` is stated explicitly and deliberately. Left to inherit, the
46975
+ * tooltip picks up whatever the host app happens to set, which is how React and
46976
+ * Vue ended up rendering the same tooltip in two different stacks and put 80
46977
+ * spurious entries into the render-parity fingerprints. Pinning it here makes
46978
+ * the affordance's typography a property of the affordance.
46979
+ */
46980
+ const ACTION_AFFORDANCE_CSS = `
46981
+ .${ACTION_INDICATOR_CLASS} {
46982
+ position: absolute;
46983
+ top: -4px;
46984
+ right: -4px;
46985
+ z-index: 20;
46986
+ display: flex;
46987
+ width: 16px;
46988
+ height: 16px;
46989
+ align-items: center;
46990
+ justify-content: center;
46991
+ border-radius: 9999px;
46992
+ background: #f59e0b;
46993
+ /* Left pointer-interactive on purpose: the badge's native title attribute is
46994
+ the only place the deck's screen tip is readable, and a title on a
46995
+ pointer-transparent node never fires. Clicks still reach the element
46996
+ underneath by bubbling, so nothing is swallowed. */
46997
+ box-shadow: 0 1px 3px 0 rgb(0 0 0 / 10%), 0 1px 2px -1px rgb(0 0 0 / 10%);
46998
+ }
46999
+ .${ACTION_INDICATOR_CLASS} svg {
47000
+ width: 10px;
47001
+ height: 10px;
47002
+ color: #fff;
47003
+ }
47004
+ .${LINK_TOOLTIP_CLASS} {
47005
+ position: absolute;
47006
+ top: 100%;
47007
+ left: 4px;
47008
+ z-index: 9999;
47009
+ max-width: 16rem;
47010
+ margin-top: 4px;
47011
+ opacity: 0;
47012
+ transition: opacity 150ms;
47013
+ pointer-events: none;
47014
+ font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
47015
+ text-align: left;
47016
+ }
47017
+ .${LINK_TOOLTIP_HOST_CLASS}:hover > .${LINK_TOOLTIP_CLASS} {
47018
+ opacity: 1;
47019
+ }
47020
+ .${LINK_TOOLTIP_PANEL_CLASS} {
47021
+ border: 1px solid var(--pptx-border, #374151);
47022
+ border-radius: 0.25rem;
47023
+ background: var(--pptx-popover, #111827);
47024
+ padding: 6px 10px;
47025
+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 10%), 0 4px 6px -4px rgb(0 0 0 / 10%);
47026
+ }
47027
+ .${LINK_TOOLTIP_LABEL_CLASS} {
47028
+ overflow: hidden;
47029
+ color: var(--pptx-foreground, #f3f4f6);
47030
+ font-size: 12px;
47031
+ font-weight: 400;
47032
+ font-style: normal;
47033
+ line-height: 16px;
47034
+ letter-spacing: normal;
47035
+ text-decoration: none;
47036
+ text-overflow: ellipsis;
47037
+ text-transform: none;
47038
+ white-space: nowrap;
47039
+ }
47040
+ .${LINK_TOOLTIP_HINT_CLASS} {
47041
+ margin-top: 2px;
47042
+ color: var(--pptx-muted-foreground, #9ca3af);
47043
+ font-size: 10px;
47044
+ font-weight: 400;
47045
+ font-style: normal;
47046
+ line-height: 12px;
47047
+ letter-spacing: normal;
47048
+ text-decoration: none;
47049
+ text-transform: none;
47050
+ }
47051
+ `;
47052
+ /** `id` of the once-per-document `<style>` holding {@link ACTION_AFFORDANCE_CSS}. */
47053
+ const AFFORDANCE_STYLE_ELEMENT_ID = 'pptx-action-affordance-styles';
47054
+ /**
47055
+ * Inject {@link ACTION_AFFORDANCE_CSS} into a document exactly once.
47056
+ *
47057
+ * Every binding calls this from its viewer root rather than shipping the rules
47058
+ * in its own stylesheet, because a per-package copy is precisely how the two
47059
+ * existing implementations drifted. Idempotent, and a no-op under SSR.
47060
+ */
47061
+ function ensureActionAffordanceStyles(doc) {
47062
+ const target = doc ?? (typeof document === 'undefined' ? undefined : document);
47063
+ if (!target || target.getElementById(AFFORDANCE_STYLE_ELEMENT_ID)) {
47064
+ return;
47065
+ }
47066
+ const style = target.createElement('style');
47067
+ style.id = AFFORDANCE_STYLE_ELEMENT_ID;
47068
+ style.textContent = ACTION_AFFORDANCE_CSS;
47069
+ (target.head ?? target.documentElement).appendChild(style);
47070
+ }
47071
+ /**
47072
+ * Build the label set from a binding's translator.
47073
+ *
47074
+ * The four dictionary keys are named once, here, so a binding cannot quietly
47075
+ * hard-code English (which is how React's badge shipped an untranslated "Has
47076
+ * action" while the key for it already existed).
47077
+ */
47078
+ function actionAffordanceLabels(translate) {
47079
+ return {
47080
+ hasAction: translate('pptx.element.hasAction'),
47081
+ link: translate('pptx.element.linkFallback'),
47082
+ followLink: translate('pptx.linkTooltip.followLink'),
47083
+ presentationMode: translate('pptx.linkTooltip.presentationMode'),
47084
+ };
47085
+ }
47086
+ /** Nothing to draw; shared so the common case allocates one frozen object. */
47087
+ const NO_AFFORDANCE = Object.freeze({
47088
+ showIndicator: false,
47089
+ indicatorTitle: '',
47090
+ showLinkTooltip: false,
47091
+ linkTooltipLabel: '',
47092
+ linkTooltipHint: '',
47093
+ });
47094
+ /**
47095
+ * Decide which action affordances an element gets, and with what text.
47096
+ *
47097
+ * Kept a single call returning both affordances (rather than two predicates)
47098
+ * because they share the same gate and the same fallback chain, and the one
47099
+ * time they were derived separately per binding they immediately diverged.
47100
+ */
47101
+ function resolveElementActionAffordance(element, context) {
47102
+ const { canInteract, presenting = false, labels } = context;
47103
+ const click = element.actionClick;
47104
+ const hover = element.actionHover;
47105
+ if (!canInteract || presenting || (!click && !hover)) {
47106
+ return NO_AFFORDANCE;
47107
+ }
47108
+ const hasUrl = Boolean(click?.url);
47109
+ return {
47110
+ showIndicator: true,
47111
+ indicatorTitle: click?.tooltip || hover?.tooltip || labels.hasAction,
47112
+ // The tooltip names a CLICK destination, so a hover-only action gets the
47113
+ // badge but no popover: there is nothing to follow and no key to press.
47114
+ showLinkTooltip: Boolean(click),
47115
+ linkTooltipLabel: click?.tooltip || click?.url || click?.action || labels.link,
47116
+ linkTooltipHint: hasUrl ? labels.followLink : labels.presentationMode,
47117
+ };
47118
+ }
47119
+
47120
+ /**
47121
+ * Paint the on-canvas action affordances (see `element-action-affordance`) at a
47122
+ * rendered stage boundary, for the bindings that cannot paint them inline.
47123
+ *
47124
+ * React renders the badge and the tooltip as JSX children of its element
47125
+ * container, which wraps EVERY element type. The other four bindings do not
47126
+ * wrap: their element renderer dispatches straight to a per-type component
47127
+ * (image, chart, table, media, ole, zoom, ...) whose single root IS the element
47128
+ * node, so there is no shared parent to hang extra children off. Reproducing
47129
+ * the affordance inside all twelve per-type components in each of four bindings
47130
+ * would be forty-odd copies of the same markup, which is precisely the drift
47131
+ * this change exists to remove.
47132
+ *
47133
+ * So the four bindings decorate after render instead, exactly as they already
47134
+ * do for the role / name / `data-pptx-action` half of the element contract
47135
+ * (`applyRenderedElementAccessibility`), and call this alongside it. The pass is
47136
+ * idempotent and re-runs on every slide / element-list change, so a framework
47137
+ * re-render that discards the nodes simply gets them back.
47138
+ *
47139
+ * @module render/element-action-affordance-dom
47140
+ */
47141
+ /** Marks a node this pass owns, so it can be refreshed or removed cleanly. */
47142
+ const AFFORDANCE_ATTRIBUTE = 'data-pptx-affordance';
47143
+ const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
47144
+ /**
47145
+ * Element types this pass leaves alone.
47146
+ *
47147
+ * A connector's rendered node is its pointer-transparent bounding box, not the
47148
+ * line itself: only the stroke's hit target takes the pointer. A badge pinned
47149
+ * to that box's corner would float in empty space and could never be hovered,
47150
+ * so React does not draw one there either.
47151
+ */
47152
+ function skipsAffordance(element) {
47153
+ return element.type === 'connector';
47154
+ }
47155
+ /** Remove any affordance nodes this pass previously added to `node`. */
47156
+ function clearAffordances(node) {
47157
+ node.classList.remove(LINK_TOOLTIP_HOST_CLASS);
47158
+ for (const owned of [...node.children]) {
47159
+ if (owned.hasAttribute(AFFORDANCE_ATTRIBUTE)) {
47160
+ owned.remove();
47161
+ }
47162
+ }
47163
+ }
47164
+ /** The affordance child of `node` with this kind, or `undefined`. */
47165
+ function ownedChild(node, kind) {
47166
+ for (const child of [...node.children]) {
47167
+ if (child.getAttribute(AFFORDANCE_ATTRIBUTE) === kind) {
47168
+ return child;
47169
+ }
47170
+ }
47171
+ return undefined;
47172
+ }
47173
+ /** Build (once) the amber badge, whose only mutable part is its `title`. */
47174
+ function buildIndicator(doc) {
47175
+ const badge = doc.createElement('div');
47176
+ badge.setAttribute(AFFORDANCE_ATTRIBUTE, 'indicator');
47177
+ badge.className = ACTION_INDICATOR_CLASS;
47178
+ const svg = doc.createElementNS(SVG_NAMESPACE, 'svg');
47179
+ svg.setAttribute('viewBox', '0 0 24 24');
47180
+ svg.setAttribute('fill', 'currentColor');
47181
+ svg.setAttribute('aria-hidden', 'true');
47182
+ const path = doc.createElementNS(SVG_NAMESPACE, 'path');
47183
+ path.setAttribute('d', ACTION_INDICATOR_ICON_PATH);
47184
+ svg.appendChild(path);
47185
+ badge.appendChild(svg);
47186
+ return badge;
47187
+ }
47188
+ /** Build (once) the hover tooltip; its two text lines are refreshed in place. */
47189
+ function buildTooltip(doc) {
47190
+ const tooltip = doc.createElement('div');
47191
+ tooltip.setAttribute(AFFORDANCE_ATTRIBUTE, 'tooltip');
47192
+ tooltip.className = LINK_TOOLTIP_CLASS;
47193
+ const panel = doc.createElement('div');
47194
+ panel.className = LINK_TOOLTIP_PANEL_CLASS;
47195
+ const label = doc.createElement('div');
47196
+ label.className = LINK_TOOLTIP_LABEL_CLASS;
47197
+ const hint = doc.createElement('div');
47198
+ hint.className = LINK_TOOLTIP_HINT_CLASS;
47199
+ panel.appendChild(label);
47200
+ panel.appendChild(hint);
47201
+ tooltip.appendChild(panel);
47202
+ return tooltip;
47203
+ }
47204
+ /** Set `text` only when it changed, so a re-run never dirties the DOM. */
47205
+ function setText(node, text) {
47206
+ if (node.textContent !== text) {
47207
+ node.textContent = text;
47208
+ }
47209
+ }
47210
+ /**
47211
+ * Paint / refresh / remove the action affordances for every top-level element
47212
+ * on `stage`, returning how many elements ended up carrying one.
47213
+ *
47214
+ * Group CHILDREN are deliberately not decorated: PowerPoint treats a group as
47215
+ * one object, the click resolves up to it (`resolveTopLevelElementId`), and
47216
+ * React badges only its top-level container. A grouped child's own action is
47217
+ * still followed, and still announced, it just does not get its own badge.
47218
+ */
47219
+ function applyElementActionAffordances(stage, elements, options) {
47220
+ let painted = 0;
47221
+ for (const element of elements) {
47222
+ const escapedId = typeof CSS !== 'undefined' && typeof CSS.escape === 'function'
47223
+ ? CSS.escape(element.id)
47224
+ : element.id.replace(/["\\]/gu, '\\$&');
47225
+ const node = stage.querySelector(`[data-element-id="${escapedId}"]`);
47226
+ if (!node) {
47227
+ continue;
47228
+ }
47229
+ const affordance = skipsAffordance(element)
47230
+ ? undefined
47231
+ : resolveElementActionAffordance(element, options);
47232
+ if (!affordance || (!affordance.showIndicator && !affordance.showLinkTooltip)) {
47233
+ clearAffordances(node);
47234
+ continue;
47235
+ }
47236
+ const doc = node.ownerDocument;
47237
+ ensureActionAffordanceStyles(doc);
47238
+ if (affordance.showIndicator) {
47239
+ let badge = ownedChild(node, 'indicator');
47240
+ if (!badge) {
47241
+ badge = buildIndicator(doc);
47242
+ node.appendChild(badge);
47243
+ }
47244
+ if (badge.getAttribute('title') !== affordance.indicatorTitle) {
47245
+ badge.setAttribute('title', affordance.indicatorTitle);
47246
+ }
47247
+ }
47248
+ else {
47249
+ ownedChild(node, 'indicator')?.remove();
47250
+ }
47251
+ if (affordance.showLinkTooltip) {
47252
+ let tooltip = ownedChild(node, 'tooltip');
47253
+ if (!tooltip) {
47254
+ tooltip = buildTooltip(doc);
47255
+ node.appendChild(tooltip);
47256
+ }
47257
+ const panel = tooltip.firstElementChild;
47258
+ if (panel?.firstElementChild && panel.lastElementChild) {
47259
+ setText(panel.firstElementChild, affordance.linkTooltipLabel);
47260
+ setText(panel.lastElementChild, affordance.linkTooltipHint);
47261
+ }
47262
+ node.classList.add(LINK_TOOLTIP_HOST_CLASS);
47263
+ }
47264
+ else {
47265
+ ownedChild(node, 'tooltip')?.remove();
47266
+ node.classList.remove(LINK_TOOLTIP_HOST_CLASS);
47267
+ }
47268
+ painted += 1;
47269
+ }
47270
+ return painted;
47271
+ }
47272
+
45905
47273
  /** Action kinds selectable for an element's click / hover trigger. */
45906
47274
  const ELEMENT_ACTION_TYPE_OPTIONS = [
45907
47275
  { value: 'none', labelKey: 'pptx.hyperlink.actionNone' },
@@ -47489,6 +48857,55 @@ function startMediaAutoplay(el, options) {
47489
48857
  });
47490
48858
  }
47491
48859
  }
48860
+ /**
48861
+ * Resolve a media element's authored playback settings into DOM attributes.
48862
+ *
48863
+ * WHY this is shared: the settings live on the parsed element, the mapping is
48864
+ * two clamps and a default, and getting it wrong is invisible in a screenshot.
48865
+ * Vanilla and Svelte both omitted `loop`, so `solution-explorer.pptx` slide 2 -
48866
+ * a 2-second clip the deck marks `loop` and drives with a `playFrom(0.0)` on
48867
+ * slide entry - played once, hit its end and froze on the last frame, which
48868
+ * reads exactly like "the video never started". Volume was worse: only React
48869
+ * applied it, so a deck that says `vol="0"` blared out of the other four.
48870
+ */
48871
+ function mediaPlaybackAttributes(source) {
48872
+ return {
48873
+ loop: source.loop === true,
48874
+ volume: Math.max(0, Math.min(1, source.volume ?? 1)),
48875
+ playbackRate: Math.max(0.25, Math.min(4, source.playbackSpeed ?? 1)),
48876
+ };
48877
+ }
48878
+ /**
48879
+ * Apply {@link mediaPlaybackAttributes} to a live node.
48880
+ *
48881
+ * `volume` and `playbackRate` are IDL properties with no attribute form, so the
48882
+ * declarative bindings cannot set them from a template the way they set `loop`;
48883
+ * every binding therefore needs this imperative step, not just the DOM ones.
48884
+ */
48885
+ function applyMediaPlaybackAttributes(el, source) {
48886
+ const attributes = mediaPlaybackAttributes(source);
48887
+ el.loop = attributes.loop;
48888
+ el.volume = attributes.volume;
48889
+ el.playbackRate = attributes.playbackRate;
48890
+ }
48891
+ /**
48892
+ * Whether a media element should carry the browser's native transport.
48893
+ *
48894
+ * PowerPoint paints no control bar during a show, and none on a still of a
48895
+ * slide either. Four bindings got the still wrong: the presenter console's
48896
+ * panes render through a NON-presenting stage, so `controls = !presenting` (and
48897
+ * Angular's `controls = !interactive && !presenting`) turned Chrome's black
48898
+ * scrubber ON inside the console, across the bottom of a slide the presenter
48899
+ * cannot even play. React escaped only by accident - its preview renderer
48900
+ * passes no media map, so the video falls back to a poster image and there is
48901
+ * no `<video>` to put a transport on.
48902
+ */
48903
+ function mediaTransportVisible(surface) {
48904
+ if (surface.presenting || surface.preview) {
48905
+ return false;
48906
+ }
48907
+ return surface.canvasTransport;
48908
+ }
47492
48909
 
47493
48910
  function clamp(value, minimum, maximum) {
47494
48911
  return Math.min(maximum, Math.max(minimum, value));
@@ -55479,6 +56896,18 @@ const translationsEn = {
55479
56896
  'pptx.presenter.eraseAllInk': 'Erase All Ink on Slide',
55480
56897
  'pptx.presenter.slideLabel': 'Slide {{current}} of {{total}}',
55481
56898
  'pptx.presenter.timerProgress': 'Timer Progress',
56899
+ // Presenter console strip (see render/presenter-chrome.ts). React shipped
56900
+ // these as hard-coded English `title` attributes, which is why they arrive
56901
+ // as new keys rather than reused ones.
56902
+ 'pptx.presenter.toggleTimer': 'Pause or Resume Timer',
56903
+ 'pptx.presenter.resetTimer': 'Reset Timer',
56904
+ 'pptx.presenter.zoomIn': 'Zoom In',
56905
+ 'pptx.presenter.zoomOut': 'Zoom Out',
56906
+ 'pptx.presenter.resetZoom': 'Reset Zoom',
56907
+ 'pptx.presenter.swapDisplays': 'Swap Displays',
56908
+ 'pptx.presenter.slideNavigator': 'Slide Navigator',
56909
+ 'pptx.presenter.closeNavigator': 'Close',
56910
+ 'pptx.presenter.timerTitle': '{{elapsed}} (segment {{segment}})',
55482
56911
  // Print dialog
55483
56912
  'pptx.print.allSlides': 'All Slides',
55484
56913
  'pptx.print.blackAndWhite': 'Black and White',
@@ -56392,6 +57821,7 @@ const translationsEn = {
56392
57821
  'pptx.editorToolbar.resetZoom': 'Reset zoom',
56393
57822
  'pptx.editorToolbar.resetZoomTo100': 'Reset zoom to 100%',
56394
57823
  'pptx.effects.innerShadow': 'Inner Shadow',
57824
+ 'pptx.element.hasAction': 'Has action',
56395
57825
  'pptx.element.linkFallback': 'Link',
56396
57826
  'pptx.export.export': 'Export',
56397
57827
  'pptx.export.gifAnimated': 'Animated GIF',
@@ -59476,7 +60906,7 @@ function createLocalStorageBackend(namespace) {
59476
60906
  /** Try IndexedDB first; fall back to localStorage on any failure. */
59477
60907
  async function resolveBackend(dbName, namespace) {
59478
60908
  try {
59479
- const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-DdlrIrmU.mjs');
60909
+ const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-zvEiyBNU.mjs');
59480
60910
  const db = await openChatDb(dbName);
59481
60911
  return createIdbBackend(db);
59482
60912
  }
@@ -68591,14 +70021,16 @@ class ChartRendererComponent {
68591
70021
  class="pptx-ng-chart-svg"
68592
70022
  style="overflow: visible; display: block; pointer-events: none;"
68593
70023
  >
68594
- <!-- Background tint -->
68595
- <rect
68596
- x="0"
68597
- y="0"
68598
- [attr.width]="vm().svgWidth"
68599
- [attr.height]="vm().svgHeight"
68600
- fill="#0f172a0d"
68601
- />
70024
+ <!-- Chart-area fill; absent when the deck declares c:chartSpace/a:noFill -->
70025
+ @if (vm().areaFill) {
70026
+ <rect
70027
+ x="0"
70028
+ y="0"
70029
+ [attr.width]="vm().svgWidth"
70030
+ [attr.height]="vm().svgHeight"
70031
+ [attr.fill]="vm().areaFill"
70032
+ />
70033
+ }
68602
70034
 
68603
70035
  <!-- Chart title (data-chart-part enables in-place editing in edit mode) -->
68604
70036
  @if (vm().title) {
@@ -68752,14 +70184,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
68752
70184
  class="pptx-ng-chart-svg"
68753
70185
  style="overflow: visible; display: block; pointer-events: none;"
68754
70186
  >
68755
- <!-- Background tint -->
68756
- <rect
68757
- x="0"
68758
- y="0"
68759
- [attr.width]="vm().svgWidth"
68760
- [attr.height]="vm().svgHeight"
68761
- fill="#0f172a0d"
68762
- />
70187
+ <!-- Chart-area fill; absent when the deck declares c:chartSpace/a:noFill -->
70188
+ @if (vm().areaFill) {
70189
+ <rect
70190
+ x="0"
70191
+ y="0"
70192
+ [attr.width]="vm().svgWidth"
70193
+ [attr.height]="vm().svgHeight"
70194
+ [attr.fill]="vm().areaFill"
70195
+ />
70196
+ }
68763
70197
 
68764
70198
  <!-- Chart title (data-chart-part enables in-place editing in edit mode) -->
68765
70199
  @if (vm().title) {
@@ -69591,6 +71025,23 @@ class ConnectorRendererComponent {
69591
71025
  </filter>
69592
71026
  }
69593
71027
  </defs>
71028
+ <!--
71029
+ The only pointer-reachable part of a connector: a transparent stroke
71030
+ along the line that opts back into hit testing. The wrapper is
71031
+ pointer-events:none so a connector's mostly-empty bounding box never
71032
+ swallows clicks meant for the shapes it spans, which left the line
71033
+ itself unclickable until this path existed.
71034
+ -->
71035
+ <path
71036
+ class="pptx-ng-connector-hit"
71037
+ [attr.d]="geo().hitPathD"
71038
+ fill="none"
71039
+ stroke="transparent"
71040
+ [attr.stroke-width]="geo().hitStrokeWidth"
71041
+ stroke-linecap="round"
71042
+ stroke-linejoin="round"
71043
+ style="pointer-events: stroke"
71044
+ />
69594
71045
  @for (strand of strands(); track strand.key) {
69595
71046
  @if (geo().pathD) {
69596
71047
  <path
@@ -69703,6 +71154,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
69703
71154
  </filter>
69704
71155
  }
69705
71156
  </defs>
71157
+ <!--
71158
+ The only pointer-reachable part of a connector: a transparent stroke
71159
+ along the line that opts back into hit testing. The wrapper is
71160
+ pointer-events:none so a connector's mostly-empty bounding box never
71161
+ swallows clicks meant for the shapes it spans, which left the line
71162
+ itself unclickable until this path existed.
71163
+ -->
71164
+ <path
71165
+ class="pptx-ng-connector-hit"
71166
+ [attr.d]="geo().hitPathD"
71167
+ fill="none"
71168
+ stroke="transparent"
71169
+ [attr.stroke-width]="geo().hitStrokeWidth"
71170
+ stroke-linecap="round"
71171
+ stroke-linejoin="round"
71172
+ style="pointer-events: stroke"
71173
+ />
69706
71174
  @for (strand of strands(); track strand.key) {
69707
71175
  @if (geo().pathD) {
69708
71176
  <path
@@ -71198,6 +72666,13 @@ function resolveCaptionTracks(tracks) {
71198
72666
  * across the bottom of the slide, over the presentation toolbar. PowerPoint
71199
72667
  * shows no transport during a show either; React gates on the same condition
71200
72668
  * (`controls={!isPresentationMode}`).
72669
+ *
72670
+ * The same `interactive` gate turned it on for every STILL of a slide as well
72671
+ * (the presenter console's current-slide pane and next-slide preview, the
72672
+ * thumbnail rail), so the console painted a scrubber over a slide the speaker
72673
+ * cannot play. {@link showControls} routes the decision through the shared
72674
+ * `mediaTransportVisible`, which owns the show/still rules for all five
72675
+ * bindings and leaves the authoring canvas to each of them.
71201
72676
  */
71202
72677
  class MediaRendererComponent {
71203
72678
  /** The element to render. Playback only occurs when `type === 'media'`. */
@@ -71227,7 +72702,13 @@ class MediaRendererComponent {
71227
72702
  // Presentation autoplay: once the media node is in the DOM and this is the
71228
72703
  // live stage, start playback; pause again if it leaves present mode. Reads
71229
72704
  // mediaSrc so a source swap re-evaluates. The shared helper owns the
71230
- // `.play()` + blocked-autoplay handling so all three bindings match.
72705
+ // `.play()` + blocked-autoplay handling so all five bindings match.
72706
+ //
72707
+ // The deck's authored playback settings are applied on the same pass.
72708
+ // `volume` and `playbackRate` are IDL properties with no attribute form,
72709
+ // so the template cannot bind them the way it binds `loop`, and until this
72710
+ // they were simply dropped: `solution-explorer.pptx` slide 2 declares
72711
+ // `vol="0"` and Angular played it at full volume.
71231
72712
  effect(() => {
71232
72713
  const el = this.mediaElRef()?.nativeElement;
71233
72714
  const presenting = this.presenting();
@@ -71235,14 +72716,33 @@ class MediaRendererComponent {
71235
72716
  if (!el) {
71236
72717
  return;
71237
72718
  }
72719
+ const media = asMediaElement(this.element());
72720
+ if (media) {
72721
+ applyMediaPlaybackAttributes(el, media);
72722
+ }
71238
72723
  if (presenting) {
71239
- startMediaAutoplay(el, { trimStartMs: asMediaElement(this.element())?.trimStartMs });
72724
+ startMediaAutoplay(el, { trimStartMs: media?.trimStartMs });
71240
72725
  }
71241
72726
  else if (!el.paused) {
71242
72727
  el.pause();
71243
72728
  }
71244
72729
  });
71245
72730
  }
72731
+ /**
72732
+ * Whether to paint the browser's native transport.
72733
+ *
72734
+ * `canvasTransport: false` is this binding's own long-standing answer for its
72735
+ * authoring canvas: a click there selects or moves the picture, so a scrubber
72736
+ * would only steal the gesture (the element also carries `pptx-ng-media-inert`
72737
+ * for the same reason). React paints one on its canvas; that difference is
72738
+ * deliberate and is the only thing the shared rule leaves to the binding.
72739
+ */
72740
+ showControls = computed(() => mediaTransportVisible({
72741
+ presenting: this.presenting(),
72742
+ preview: !this.interactive() && !this.presenting(),
72743
+ canvasTransport: false,
72744
+ }), /* @ts-ignore */
72745
+ ...(ngDevMode ? [{ debugName: "showControls" }] : /* istanbul ignore next */ []));
71246
72746
  containerStyle = computed(() => getContainerStyle(this.element(), this.zIndex()), /* @ts-ignore */
71247
72747
  ...(ngDevMode ? [{ debugName: "containerStyle" }] : /* istanbul ignore next */ []));
71248
72748
  /** Poster / preview frame data-URL (also used as the `<video poster>`). */
@@ -71282,7 +72782,7 @@ class MediaRendererComponent {
71282
72782
  class="pptx-ng-media-el pptx-ng-media-audio"
71283
72783
  [class.pptx-ng-media-inert]="interactive()"
71284
72784
  [src]="src + trimFragment()"
71285
- [controls]="!interactive() && !presenting()"
72785
+ [controls]="showControls()"
71286
72786
  [loop]="loop()"
71287
72787
  preload="metadata"
71288
72788
  ></audio>
@@ -71293,7 +72793,7 @@ class MediaRendererComponent {
71293
72793
  [class.pptx-ng-media-inert]="interactive()"
71294
72794
  [src]="src + trimFragment()"
71295
72795
  [poster]="poster() ?? null"
71296
- [controls]="!interactive() && !presenting()"
72796
+ [controls]="showControls()"
71297
72797
  [loop]="loop()"
71298
72798
  preload="metadata"
71299
72799
  playsInline
@@ -71342,7 +72842,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
71342
72842
  class="pptx-ng-media-el pptx-ng-media-audio"
71343
72843
  [class.pptx-ng-media-inert]="interactive()"
71344
72844
  [src]="src + trimFragment()"
71345
- [controls]="!interactive() && !presenting()"
72845
+ [controls]="showControls()"
71346
72846
  [loop]="loop()"
71347
72847
  preload="metadata"
71348
72848
  ></audio>
@@ -71353,7 +72853,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
71353
72853
  [class.pptx-ng-media-inert]="interactive()"
71354
72854
  [src]="src + trimFragment()"
71355
72855
  [poster]="poster() ?? null"
71356
- [controls]="!interactive() && !presenting()"
72856
+ [controls]="showControls()"
71357
72857
  [loop]="loop()"
71358
72858
  preload="metadata"
71359
72859
  playsInline
@@ -74231,6 +75731,19 @@ function resolveInteractiveElementId(target, allElements, editTemplateMode) {
74231
75731
  function isViewportBackgroundPressTarget(target, currentTarget) {
74232
75732
  return target === currentTarget;
74233
75733
  }
75734
+ /**
75735
+ * Which elements the on-canvas action affordances (amber "has action" badge +
75736
+ * hover link tooltip) may decorate.
75737
+ *
75738
+ * An inherited master/layout shape is inert until edit-template mode is on, so
75739
+ * it must not advertise an action the user cannot reach yet; that mirrors
75740
+ * React's `canInteract` gate, which is off for the template layer until the
75741
+ * mode is enabled. Split out of the component's post-render effect so it is
75742
+ * testable without a TestBed, like the rest of this package.
75743
+ */
75744
+ function affordanceElements(elements, editTemplateMode, isTemplate) {
75745
+ return editTemplateMode ? elements : elements.filter((element) => !isTemplate(element));
75746
+ }
74234
75747
 
74235
75748
  /**
74236
75749
  * Thin re-export shim → vendored `pptx-viewer-shared`.
@@ -74560,8 +76073,23 @@ class SlideCanvasComponent {
74560
76073
  const elements = this.allElements();
74561
76074
  const interactive = this.interactive();
74562
76075
  const presenting = this.presenting();
76076
+ // An inherited master/layout shape only gets the authoring chrome while
76077
+ // it is actually editable, matching React's `canInteract` gate.
76078
+ const decorated = affordanceElements(elements, this.editTemplateMode(), isTemplateElement);
74563
76079
  if (stage && (interactive || presenting)) {
74564
- queueMicrotask(() => applyRenderedElementAccessibility(stage, elements, { presenting }));
76080
+ queueMicrotask(() => {
76081
+ applyRenderedElementAccessibility(stage, elements, { presenting });
76082
+ // The on-canvas action affordances (amber badge + hover link
76083
+ // tooltip) ride the same post-render pass: `ElementRendererComponent`
76084
+ // dispatches every non-shape type straight to a per-type component
76085
+ // whose root IS the element node, leaving no wrapper in the template
76086
+ // to hang the chrome off.
76087
+ applyElementActionAffordances(stage, decorated, {
76088
+ canInteract: interactive,
76089
+ presenting,
76090
+ labels: actionAffordanceLabels((key) => this.translate.instant(key)),
76091
+ });
76092
+ });
74565
76093
  }
74566
76094
  });
74567
76095
  // Observe the viewport so the slide re-fits on container resize / rotation.
@@ -76211,45 +77739,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
76211
77739
  `, styles: [":host{display:contents}.pptx-ng-mmenu-list{list-style:none;margin:0;padding:.5rem 0}.pptx-ng-mmenu-row{display:flex;align-items:center;gap:.875rem;width:100%;padding:.75rem 1.25rem;border:none;background:transparent;color:#e5e5e5;text-align:left;cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent;transition:background .1s}.pptx-ng-mmenu-row:hover:not([disabled]){background:#ffffff0f}.pptx-ng-mmenu-row:active:not([disabled]){background:#ffffff1a}.pptx-ng-mmenu-row.is-active{color:#3b82f6}.pptx-ng-mmenu-row.is-danger{color:#ef4444}.pptx-ng-mmenu-row[disabled]{opacity:.35;cursor:not-allowed}.pptx-ng-mmenu-icon{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:1.5rem;opacity:.8}.pptx-ng-mmenu-text{display:flex;flex-direction:column;gap:.125rem;flex:1;min-width:0}.pptx-ng-mmenu-label{font-size:.9375rem;font-weight:500;line-height:1.3}.pptx-ng-mmenu-sublabel{font-size:.75rem;color:#ffffff73;line-height:1.3}.pptx-ng-mmenu-check{color:#3b82f6;font-size:1rem;flex-shrink:0}.pptx-ng-mmenu-divider{height:1px;background:#ffffff14;margin:.375rem 0}\n"] }]
76212
77740
  }], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], slideCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideCount", required: false }] }], exporting: [{ type: i0.Input, args: [{ isSignal: true, alias: "exporting", required: false }] }], showNotes: [{ type: i0.Input, args: [{ isSignal: true, alias: "showNotes", required: false }] }], canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], hiddenActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "hiddenActions", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], openFind: [{ type: i0.Output, args: ["openFind"] }], openSorter: [{ type: i0.Output, args: ["openSorter"] }], toggleNotes: [{ type: i0.Output, args: ["toggleNotes"] }], insertText: [{ type: i0.Output, args: ["insertText"] }], present: [{ type: i0.Output, args: ["present"] }], openFile: [{ type: i0.Output, args: ["openFile"] }], savePptx: [{ type: i0.Output, args: ["savePptx"] }], exportPng: [{ type: i0.Output, args: ["exportPng"] }], exportPdf: [{ type: i0.Output, args: ["exportPdf"] }], exportGif: [{ type: i0.Output, args: ["exportGif"] }], exportVideo: [{ type: i0.Output, args: ["exportVideo"] }], print: [{ type: i0.Output, args: ["print"] }] } });
76213
77741
 
76214
- // ---------------------------------------------------------------------------
76215
- // Time formatting (local: negative-clamping differs from shared)
76216
- // ---------------------------------------------------------------------------
76217
- /**
76218
- * Format a millisecond duration as MM:SS, or HH:MM:SS when the elapsed
76219
- * time is one hour or longer. Sub-second values are floored; negative inputs
76220
- * are treated as zero.
76221
- */
76222
- function formatElapsed(elapsedMs) {
76223
- const safeMs = elapsedMs > 0 ? elapsedMs : 0;
76224
- const totalSeconds = Math.floor(safeMs / 1000);
76225
- const hours = Math.floor(totalSeconds / 3600);
76226
- const minutes = Math.floor((totalSeconds % 3600) / 60);
76227
- const seconds = totalSeconds % 60;
76228
- if (hours > 0) {
76229
- return `${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}`;
76230
- }
76231
- return `${pad2(minutes)}:${pad2(seconds)}`;
76232
- }
76233
- /** Zero-pad a non-negative integer to at least two digits. */
76234
- function pad2(value) {
76235
- return String(value).padStart(2, '0');
76236
- }
76237
- // ---------------------------------------------------------------------------
76238
- // Timer progress (5-minute segments)
76239
- // ---------------------------------------------------------------------------
76240
- /** Milliseconds per timer progress-bar fill (one 5-minute segment). */
76241
- const TIMER_SEGMENT_MS = 5 * 60 * 1000;
76242
- /**
76243
- * Derive the timer progress-bar fill (percent within the current 5-minute
76244
- * segment) and the segment index from an elapsed duration. Mirrors the React
76245
- * PresenterView `timerProgress` / `timerSegment` computation.
76246
- */
76247
- function computeTimerProgress(elapsedMs) {
76248
- const safeMs = elapsedMs > 0 ? elapsedMs : 0;
76249
- const percent = Math.min(100, ((safeMs % TIMER_SEGMENT_MS) / TIMER_SEGMENT_MS) * 100);
76250
- const segment = Math.floor(safeMs / TIMER_SEGMENT_MS);
76251
- return { percent, segment };
76252
- }
76253
77742
  // ---------------------------------------------------------------------------
76254
77743
  // Elapsed time from a start timestamp
76255
77744
  // ---------------------------------------------------------------------------
@@ -76257,6 +77746,9 @@ function computeTimerProgress(elapsedMs) {
76257
77746
  * Compute the elapsed milliseconds between `startTime` (epoch ms, or null when
76258
77747
  * the presentation has not started) and `now`. Returns 0 when no start time is
76259
77748
  * set or the clock has not advanced past the start.
77749
+ *
77750
+ * This clamp is why shared's `formatElapsed` needs none: a duration handed to
77751
+ * the formatter has already been resolved against a wall clock here.
76260
77752
  */
76261
77753
  function elapsedSince(startTime, now) {
76262
77754
  if (startTime === null || startTime === undefined) {
@@ -76278,8 +77770,13 @@ function currentSlideAt(slides, index) {
76278
77770
  return slides[index];
76279
77771
  }
76280
77772
  /**
76281
- * Return the next slide (index + 1), or undefined when `index` is the last
76282
- * slide, skipping hidden slides just as the live show does.
77773
+ * Return the slide the show would advance to from `index`, or undefined at the
77774
+ * end of the deck.
77775
+ *
77776
+ * Delegates to the shared `nextPresentedSlide`, which is what makes the
77777
+ * next-slide preview agree with the show itself: it skips hidden slides and
77778
+ * honours custom-show membership, neither of which a naive `slides[index + 1]`
77779
+ * can do.
76283
77780
  */
76284
77781
  function nextSlideAfter(slides, index) {
76285
77782
  return nextPresentedSlide(slides, index);
@@ -76296,44 +77793,21 @@ function slideCounter(index, total) {
76296
77793
  return `${index + 1} / ${total}`;
76297
77794
  }
76298
77795
  /**
76299
- * Derive a list of notes view-model tokens from rich-text `TextSegment`s,
76300
- * mirroring the React `renderNotesSegments` styling rules. Paragraph breaks
76301
- * become break tokens; styled runs carry an inline `StyleMap`.
77796
+ * Derive notes view-model tokens from rich-text `TextSegment`s.
77797
+ *
77798
+ * The styling rules (which properties, in which units) belong to shared's
77799
+ * `notesSegmentsToSpans`; all this adds is the camelCase -> kebab-case rewrite
77800
+ * the Angular `ngStyle` binding is written against.
76302
77801
  */
76303
77802
  function buildNotesSegments(segments) {
76304
- return segments.map((segment, index) => {
76305
- if (segment.isParagraphBreak) {
76306
- return { key: `br-${index}`, isBreak: true, text: '', style: {} };
76307
- }
76308
- const style = {};
76309
- const s = segment.style;
76310
- if (s.bold) {
76311
- style['font-weight'] = 'bold';
76312
- }
76313
- if (s.italic) {
76314
- style['font-style'] = 'italic';
76315
- }
76316
- const decorations = [];
76317
- if (s.underline) {
76318
- decorations.push('underline');
76319
- }
76320
- if (s.strikethrough) {
76321
- decorations.push('line-through');
76322
- }
76323
- if (decorations.length > 0) {
76324
- style['text-decoration'] = decorations.join(' ');
76325
- }
76326
- if (s.color) {
76327
- style['color'] = s.color;
76328
- }
76329
- if (s.fontSize) {
76330
- style['font-size'] = `${s.fontSize}px`;
76331
- }
76332
- if (s.fontFamily) {
76333
- style['font-family'] = s.fontFamily;
76334
- }
76335
- return { key: `seg-${index}`, isBreak: false, text: segment.text, style };
76336
- });
77803
+ return notesSegmentsToSpans([...segments]).map((span) => span.kind === 'break'
77804
+ ? { key: span.key, isBreak: true, text: '', style: {} }
77805
+ : {
77806
+ key: span.key,
77807
+ isBreak: false,
77808
+ text: span.text,
77809
+ style: cssObjectToStyleMap(span.style),
77810
+ });
76337
77811
  }
76338
77812
  /**
76339
77813
  * Resolve the presenter notes for a slide: prefer rich `notesSegments`, else
@@ -76422,10 +77896,13 @@ class MobilePresenterViewComponent {
76422
77896
  ...(ngDevMode ? [{ debugName: "elapsedLabel" }] : /* istanbul ignore next */ []));
76423
77897
  counterLabel = computed(() => mobileSlideCounter(this.currentSlideIndex(), this.slides().length), /* @ts-ignore */
76424
77898
  ...(ngDevMode ? [{ debugName: "counterLabel" }] : /* istanbul ignore next */ []));
76425
- atFirst = computed(() => isFirstSlide(this.currentSlideIndex()), /* @ts-ignore */
76426
- ...(ngDevMode ? [{ debugName: "atFirst" }] : /* istanbul ignore next */ []));
76427
- atLast = computed(() => isLastSlide(this.currentSlideIndex(), this.slides().length), /* @ts-ignore */
76428
- ...(ngDevMode ? [{ debugName: "atLast" }] : /* istanbul ignore next */ []));
77899
+ // The desktop console's rules, not a phone-sized copy of them: Next stays live
77900
+ // on the last slide so the presenter can reach the end-of-show screen and
77901
+ // finish, exactly as the split-screen console does.
77902
+ prevDisabled = computed(() => presenterPrevDisabled(this.currentSlideIndex()), /* @ts-ignore */
77903
+ ...(ngDevMode ? [{ debugName: "prevDisabled" }] : /* istanbul ignore next */ []));
77904
+ nextDisabled = computed(() => presenterNextDisabled(), /* @ts-ignore */
77905
+ ...(ngDevMode ? [{ debugName: "nextDisabled" }] : /* istanbul ignore next */ []));
76429
77906
  /** Next-slide thumbnail box (CSS px); width drives the slide-canvas autoFit. */
76430
77907
  thumbStyle = computed(() => {
76431
77908
  const size = this.canvasSize();
@@ -76447,11 +77924,11 @@ class MobilePresenterViewComponent {
76447
77924
  return { ...slide, elements: [...template, ...slide.elements] };
76448
77925
  }
76449
77926
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MobilePresenterViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
76450
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: MobilePresenterViewComponent, isStandalone: true, selector: "pptx-mobile-presenter-view", inputs: { slides: { classPropertyName: "slides", publicName: "slides", isSignal: true, isRequired: true, transformFunction: null }, currentSlideIndex: { classPropertyName: "currentSlideIndex", publicName: "currentSlideIndex", isSignal: true, isRequired: true, transformFunction: null }, canvasSize: { classPropertyName: "canvasSize", publicName: "canvasSize", isSignal: true, isRequired: true, transformFunction: null }, templateElements: { classPropertyName: "templateElements", publicName: "templateElements", isSignal: true, isRequired: false, transformFunction: null }, mediaDataUrls: { classPropertyName: "mediaDataUrls", publicName: "mediaDataUrls", isSignal: true, isRequired: false, transformFunction: null }, presentationStartTime: { classPropertyName: "presentationStartTime", publicName: "presentationStartTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { movePresentationSlide: "movePresentationSlide", exit: "exit" }, ngImport: i0, template: "@if (currentSlide(); as current) {\n\t<!-- Header: elapsed + counter + exit -->\n\t<div class=\"pptx-ng-mpresenter-header\">\n\t\t<div>\n\t\t\t<div class=\"pptx-ng-mpresenter-label\">{{ 'pptx.presenter.elapsed' | translate }}</div>\n\t\t\t<div class=\"pptx-ng-mpresenter-elapsed\">{{ elapsedLabel() }}</div>\n\t\t</div>\n\t\t<span class=\"pptx-ng-mpresenter-counter\">{{ counterLabel() }}</span>\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"pptx-ng-mpresenter-exit\"\n\t\t\t(click)=\"exit.emit()\"\n\t\t\t[attr.aria-label]=\"'pptx.presenter.endPresentation' | translate\"\n\t\t\t[title]=\"'pptx.presenter.endPresentation' | translate\"\n\t\t>\n\t\t\t&#x2715;\n\t\t</button>\n\t</div>\n\n\t<!-- Current slide (large) -->\n\t<div class=\"pptx-ng-mpresenter-main\">\n\t\t<div class=\"pptx-ng-mpresenter-main-stage\">\n\t\t\t<pptx-slide-canvas\n\t\t\t\t[slide]=\"currentPreviewSlide()\"\n\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t[zoom]=\"1\"\n\t\t\t\t[interactive]=\"false\"\n\t\t\t/>\n\t\t</div>\n\t</div>\n\n\t<!-- Next thumbnail -->\n\t<div class=\"pptx-ng-mpresenter-next\">\n\t\t<span class=\"pptx-ng-mpresenter-label\">{{\n\t\t\t'pptx.presenter.nextSlidePreview' | translate\n\t\t}}</span>\n\t\t@if (nextPreviewSlide(); as next) {\n\t\t\t<div class=\"pptx-ng-mpresenter-thumb\" [ngStyle]=\"thumbStyle()\">\n\t\t\t\t<pptx-slide-canvas\n\t\t\t\t\t[slide]=\"next\"\n\t\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zoom]=\"1\"\n\t\t\t\t\t[interactive]=\"false\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t} @else {\n\t\t\t<div class=\"pptx-ng-mpresenter-next-empty\">\n\t\t\t\t{{ 'pptx.presenter.endOfPresentation' | translate }}\n\t\t\t</div>\n\t\t}\n\t</div>\n\n\t<!-- Speaker notes (scrollable) -->\n\t<div class=\"pptx-ng-mpresenter-notes\">\n\t\t<div class=\"pptx-ng-mpresenter-label\">{{ 'pptx.presenter.speakerNotes' | translate }}</div>\n\t\t<div class=\"pptx-ng-mpresenter-notes-body\">\n\t\t\t@if (notes().hasRichNotes) {\n\t\t\t\t@for (seg of notes().segments; track seg.key) {\n\t\t\t\t\t@if (seg.isBreak) {\n\t\t\t\t\t\t<br />\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<span [ngStyle]=\"seg.style\">{{ seg.text }}</span>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} @else if (notes().hasAnyNotes) {\n\t\t\t\t{{ notes().plainText }}\n\t\t\t} @else {\n\t\t\t\t<span class=\"pptx-ng-mpresenter-notes-empty\">{{\n\t\t\t\t\t'pptx.presenter.noNotes' | translate\n\t\t\t\t}}</span>\n\t\t\t}\n\t\t</div>\n\t</div>\n\n\t<!-- Prev / Next controls -->\n\t<div class=\"pptx-ng-mpresenter-ctl\">\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"pptx-ng-mpresenter-navbtn\"\n\t\t\t(click)=\"movePresentationSlide.emit(-1)\"\n\t\t\t[disabled]=\"atFirst()\"\n\t\t\t[attr.aria-label]=\"'pptx.presenter.previousSlide' | translate\"\n\t\t\t[title]=\"'pptx.presenter.previousSlide' | translate\"\n\t\t>\n\t\t\t&#x2039; {{ 'pptx.presenter.prev' | translate }}\n\t\t</button>\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"pptx-ng-mpresenter-navbtn\"\n\t\t\t(click)=\"movePresentationSlide.emit(1)\"\n\t\t\t[disabled]=\"atLast()\"\n\t\t\t[attr.aria-label]=\"'pptx.presenter.nextSlidePreview' | translate\"\n\t\t\t[title]=\"'pptx.presenter.nextSlidePreview' | translate\"\n\t\t>\n\t\t\t{{ 'pptx.presenter.next' | translate }} &#x203A;\n\t\t</button>\n\t</div>\n} @else {\n\t<div class=\"pptx-ng-mpresenter-empty\">{{ 'pptx.presenter.noSlides' | translate }}</div>\n}\n", styles: [":host{position:absolute;inset:0;z-index:50;display:flex;flex-direction:column;background:#0b0b0c;color:#f5f5f5;font-family:system-ui,sans-serif;padding-top:env(safe-area-inset-top,0px);padding-bottom:env(safe-area-inset-bottom,0px);padding-left:env(safe-area-inset-left,0px);padding-right:env(safe-area-inset-right,0px)}.pptx-ng-mpresenter-header,.pptx-ng-mpresenter-next,.pptx-ng-mpresenter-ctl{display:flex;align-items:center;gap:.75rem;padding:.5rem 1rem}.pptx-ng-mpresenter-header{justify-content:space-between;border-bottom:1px solid rgba(255,255,255,.08)}.pptx-ng-mpresenter-label{font-size:.625rem;text-transform:uppercase;letter-spacing:.06em;color:#ffffff8c}.pptx-ng-mpresenter-elapsed{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.125rem;color:#6ea8fe}.pptx-ng-mpresenter-counter{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:.875rem}.pptx-ng-mpresenter-exit{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;min-width:44px;min-height:44px;border:none;border-radius:6px;background:transparent;color:#ffffffbf;cursor:pointer;font-size:1.25rem;line-height:1}.pptx-ng-mpresenter-exit:hover{background:#ffffff1f;color:#fff}.pptx-ng-mpresenter-main{display:flex;align-items:center;justify-content:center;background:#000;padding:.75rem}.pptx-ng-mpresenter-main-stage{width:100%;max-width:640px}.pptx-ng-mpresenter-next{border-bottom:1px solid rgba(255,255,255,.08)}.pptx-ng-mpresenter-thumb{flex:0 0 auto;overflow:hidden;border:1px solid rgba(255,255,255,.15);border-radius:4px}.pptx-ng-mpresenter-next-empty{display:flex;flex:1 1 auto;align-items:center;justify-content:center;height:3rem;border:1px solid rgba(255,255,255,.15);border-radius:4px;background:#ffffff0a;font-size:.625rem;font-style:italic;color:#ffffff80}.pptx-ng-mpresenter-notes{flex:1 1 auto;display:flex;flex-direction:column;min-height:0;padding:.5rem 1rem}.pptx-ng-mpresenter-notes-body{flex:1 1 auto;overflow-y:auto;margin-top:.25rem;border:1px solid rgba(255,255,255,.15);border-radius:6px;background:#ffffff0a;padding:.5rem .75rem;white-space:pre-wrap;line-height:1.5;font-size:15px}.pptx-ng-mpresenter-notes-empty{font-style:italic;color:#ffffff80}.pptx-ng-mpresenter-ctl{justify-content:space-between;border-top:1px solid rgba(255,255,255,.08)}.pptx-ng-mpresenter-navbtn{flex:1 1 0;display:inline-flex;align-items:center;justify-content:center;gap:.375rem;height:44px;border:none;border-radius:6px;background:#ffffff14;color:#f5f5f5;cursor:pointer;font-size:.9rem}.pptx-ng-mpresenter-navbtn:hover:not(:disabled){background:#ffffff29}.pptx-ng-mpresenter-navbtn:disabled{opacity:.4;cursor:not-allowed}.pptx-ng-mpresenter-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#fff9}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: SlideCanvasComponent, selector: "pptx-slide-canvas", inputs: ["slide", "canvasSize", "mediaDataUrls", "zoom", "editable", "showGrid", "showRulers", "rulerUnit", "showGuides", "snapToGrid", "snapToShape", "guideCommand", "spellCheck", "snapToGuides", "autoFit", "transparentBackground", "interactive", "presenting", "selectedIds", "editingId", "editTemplateMode", "templateElements", "aiHighlights", "aiActive", "aiActiveSlideIndex", "aiChangeBatch", "aiPickMode", "drawTool", "drawColor", "drawWidth"], outputs: ["elementSelect", "backgroundClick", "transformStart", "transformUpdate", "contextMenu", "textEditStart", "textCommit", "textInput", "textCancel", "textFormat", "rotateUpdate", "marqueeSelect", "inkStrokeComplete", "eraserHit", "cellCommit", "tableChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
77927
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: MobilePresenterViewComponent, isStandalone: true, selector: "pptx-mobile-presenter-view", inputs: { slides: { classPropertyName: "slides", publicName: "slides", isSignal: true, isRequired: true, transformFunction: null }, currentSlideIndex: { classPropertyName: "currentSlideIndex", publicName: "currentSlideIndex", isSignal: true, isRequired: true, transformFunction: null }, canvasSize: { classPropertyName: "canvasSize", publicName: "canvasSize", isSignal: true, isRequired: true, transformFunction: null }, templateElements: { classPropertyName: "templateElements", publicName: "templateElements", isSignal: true, isRequired: false, transformFunction: null }, mediaDataUrls: { classPropertyName: "mediaDataUrls", publicName: "mediaDataUrls", isSignal: true, isRequired: false, transformFunction: null }, presentationStartTime: { classPropertyName: "presentationStartTime", publicName: "presentationStartTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { movePresentationSlide: "movePresentationSlide", exit: "exit" }, ngImport: i0, template: "@if (currentSlide(); as current) {\n\t<!-- Header: elapsed + counter + exit -->\n\t<div class=\"pptx-ng-mpresenter-header\">\n\t\t<div>\n\t\t\t<div class=\"pptx-ng-mpresenter-label\">{{ 'pptx.presenter.elapsed' | translate }}</div>\n\t\t\t<div class=\"pptx-ng-mpresenter-elapsed\">{{ elapsedLabel() }}</div>\n\t\t</div>\n\t\t<span class=\"pptx-ng-mpresenter-counter\">{{ counterLabel() }}</span>\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"pptx-ng-mpresenter-exit\"\n\t\t\t(click)=\"exit.emit()\"\n\t\t\t[attr.aria-label]=\"'pptx.presenter.endPresentation' | translate\"\n\t\t\t[title]=\"'pptx.presenter.endPresentation' | translate\"\n\t\t>\n\t\t\t&#x2715;\n\t\t</button>\n\t</div>\n\n\t<!-- Current slide (large) -->\n\t<div class=\"pptx-ng-mpresenter-main\">\n\t\t<div class=\"pptx-ng-mpresenter-main-stage\">\n\t\t\t<pptx-slide-canvas\n\t\t\t\t[slide]=\"currentPreviewSlide()\"\n\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t[zoom]=\"1\"\n\t\t\t\t[interactive]=\"false\"\n\t\t\t/>\n\t\t</div>\n\t</div>\n\n\t<!-- Next thumbnail -->\n\t<div class=\"pptx-ng-mpresenter-next\">\n\t\t<span class=\"pptx-ng-mpresenter-label\">{{\n\t\t\t'pptx.presenter.nextSlidePreview' | translate\n\t\t}}</span>\n\t\t@if (nextPreviewSlide(); as next) {\n\t\t\t<div class=\"pptx-ng-mpresenter-thumb\" [ngStyle]=\"thumbStyle()\">\n\t\t\t\t<pptx-slide-canvas\n\t\t\t\t\t[slide]=\"next\"\n\t\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zoom]=\"1\"\n\t\t\t\t\t[interactive]=\"false\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t} @else {\n\t\t\t<div class=\"pptx-ng-mpresenter-next-empty\">\n\t\t\t\t{{ 'pptx.presenter.endOfPresentation' | translate }}\n\t\t\t</div>\n\t\t}\n\t</div>\n\n\t<!-- Speaker notes (scrollable) -->\n\t<div class=\"pptx-ng-mpresenter-notes\">\n\t\t<div class=\"pptx-ng-mpresenter-label\">{{ 'pptx.presenter.speakerNotes' | translate }}</div>\n\t\t<div class=\"pptx-ng-mpresenter-notes-body\">\n\t\t\t@if (notes().hasRichNotes) {\n\t\t\t\t@for (seg of notes().segments; track seg.key) {\n\t\t\t\t\t@if (seg.isBreak) {\n\t\t\t\t\t\t<br />\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<span [ngStyle]=\"seg.style\">{{ seg.text }}</span>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} @else if (notes().hasAnyNotes) {\n\t\t\t\t{{ notes().plainText }}\n\t\t\t} @else {\n\t\t\t\t<span class=\"pptx-ng-mpresenter-notes-empty\">{{\n\t\t\t\t\t'pptx.presenter.noNotes' | translate\n\t\t\t\t}}</span>\n\t\t\t}\n\t\t</div>\n\t</div>\n\n\t<!-- Prev / Next controls -->\n\t<div class=\"pptx-ng-mpresenter-ctl\">\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"pptx-ng-mpresenter-navbtn\"\n\t\t\tdata-pptx-presenter-control=\"prev\"\n\t\t\t(click)=\"movePresentationSlide.emit(-1)\"\n\t\t\t[disabled]=\"prevDisabled()\"\n\t\t\t[attr.aria-label]=\"'pptx.presenter.previousSlide' | translate\"\n\t\t\t[title]=\"'pptx.presenter.previousSlide' | translate\"\n\t\t>\n\t\t\t&#x2039; {{ 'pptx.presenter.prev' | translate }}\n\t\t</button>\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"pptx-ng-mpresenter-navbtn\"\n\t\t\tdata-pptx-presenter-control=\"next\"\n\t\t\t(click)=\"movePresentationSlide.emit(1)\"\n\t\t\t[disabled]=\"nextDisabled()\"\n\t\t\t[attr.aria-label]=\"'pptx.presenter.nextSlide' | translate\"\n\t\t\t[title]=\"'pptx.presenter.nextSlide' | translate\"\n\t\t>\n\t\t\t{{ 'pptx.presenter.next' | translate }} &#x203A;\n\t\t</button>\n\t</div>\n} @else {\n\t<div class=\"pptx-ng-mpresenter-empty\">{{ 'pptx.presenter.noSlides' | translate }}</div>\n}\n", styles: [":host{position:absolute;inset:0;z-index:50;display:flex;flex-direction:column;background:#0b0b0c;color:#f5f5f5;font-family:system-ui,sans-serif;padding-top:env(safe-area-inset-top,0px);padding-bottom:env(safe-area-inset-bottom,0px);padding-left:env(safe-area-inset-left,0px);padding-right:env(safe-area-inset-right,0px)}.pptx-ng-mpresenter-header,.pptx-ng-mpresenter-next,.pptx-ng-mpresenter-ctl{display:flex;align-items:center;gap:.75rem;padding:.5rem 1rem}.pptx-ng-mpresenter-header{justify-content:space-between;border-bottom:1px solid rgba(255,255,255,.08)}.pptx-ng-mpresenter-label{font-size:.625rem;text-transform:uppercase;letter-spacing:.06em;color:#ffffff8c}.pptx-ng-mpresenter-elapsed{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.125rem;color:#6ea8fe}.pptx-ng-mpresenter-counter{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:.875rem}.pptx-ng-mpresenter-exit{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;min-width:44px;min-height:44px;border:none;border-radius:6px;background:transparent;color:#ffffffbf;cursor:pointer;font-size:1.25rem;line-height:1}.pptx-ng-mpresenter-exit:hover{background:#ffffff1f;color:#fff}.pptx-ng-mpresenter-main{display:flex;align-items:center;justify-content:center;background:#000;padding:.75rem}.pptx-ng-mpresenter-main-stage{width:100%;max-width:640px}.pptx-ng-mpresenter-next{border-bottom:1px solid rgba(255,255,255,.08)}.pptx-ng-mpresenter-thumb{flex:0 0 auto;overflow:hidden;border:1px solid rgba(255,255,255,.15);border-radius:4px}.pptx-ng-mpresenter-next-empty{display:flex;flex:1 1 auto;align-items:center;justify-content:center;height:3rem;border:1px solid rgba(255,255,255,.15);border-radius:4px;background:#ffffff0a;font-size:.625rem;font-style:italic;color:#ffffff80}.pptx-ng-mpresenter-notes{flex:1 1 auto;display:flex;flex-direction:column;min-height:0;padding:.5rem 1rem}.pptx-ng-mpresenter-notes-body{flex:1 1 auto;overflow-y:auto;margin-top:.25rem;border:1px solid rgba(255,255,255,.15);border-radius:6px;background:#ffffff0a;padding:.5rem .75rem;white-space:pre-wrap;line-height:1.5;font-size:15px}.pptx-ng-mpresenter-notes-empty{font-style:italic;color:#ffffff80}.pptx-ng-mpresenter-ctl{justify-content:space-between;border-top:1px solid rgba(255,255,255,.08)}.pptx-ng-mpresenter-navbtn{flex:1 1 0;display:inline-flex;align-items:center;justify-content:center;gap:.375rem;height:44px;border:none;border-radius:6px;background:#ffffff14;color:#f5f5f5;cursor:pointer;font-size:.9rem}.pptx-ng-mpresenter-navbtn:hover:not(:disabled){background:#ffffff29}.pptx-ng-mpresenter-navbtn:disabled{opacity:.4;cursor:not-allowed}.pptx-ng-mpresenter-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#fff9}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: SlideCanvasComponent, selector: "pptx-slide-canvas", inputs: ["slide", "canvasSize", "mediaDataUrls", "zoom", "editable", "showGrid", "showRulers", "rulerUnit", "showGuides", "snapToGrid", "snapToShape", "guideCommand", "spellCheck", "snapToGuides", "autoFit", "transparentBackground", "interactive", "presenting", "selectedIds", "editingId", "editTemplateMode", "templateElements", "aiHighlights", "aiActive", "aiActiveSlideIndex", "aiChangeBatch", "aiPickMode", "drawTool", "drawColor", "drawWidth"], outputs: ["elementSelect", "backgroundClick", "transformStart", "transformUpdate", "contextMenu", "textEditStart", "textCommit", "textInput", "textCancel", "textFormat", "rotateUpdate", "marqueeSelect", "inkStrokeComplete", "eraserHit", "cellCommit", "tableChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
76451
77928
  }
76452
77929
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MobilePresenterViewComponent, decorators: [{
76453
77930
  type: Component,
76454
- args: [{ selector: 'pptx-mobile-presenter-view', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgStyle, SlideCanvasComponent, TranslatePipe], template: "@if (currentSlide(); as current) {\n\t<!-- Header: elapsed + counter + exit -->\n\t<div class=\"pptx-ng-mpresenter-header\">\n\t\t<div>\n\t\t\t<div class=\"pptx-ng-mpresenter-label\">{{ 'pptx.presenter.elapsed' | translate }}</div>\n\t\t\t<div class=\"pptx-ng-mpresenter-elapsed\">{{ elapsedLabel() }}</div>\n\t\t</div>\n\t\t<span class=\"pptx-ng-mpresenter-counter\">{{ counterLabel() }}</span>\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"pptx-ng-mpresenter-exit\"\n\t\t\t(click)=\"exit.emit()\"\n\t\t\t[attr.aria-label]=\"'pptx.presenter.endPresentation' | translate\"\n\t\t\t[title]=\"'pptx.presenter.endPresentation' | translate\"\n\t\t>\n\t\t\t&#x2715;\n\t\t</button>\n\t</div>\n\n\t<!-- Current slide (large) -->\n\t<div class=\"pptx-ng-mpresenter-main\">\n\t\t<div class=\"pptx-ng-mpresenter-main-stage\">\n\t\t\t<pptx-slide-canvas\n\t\t\t\t[slide]=\"currentPreviewSlide()\"\n\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t[zoom]=\"1\"\n\t\t\t\t[interactive]=\"false\"\n\t\t\t/>\n\t\t</div>\n\t</div>\n\n\t<!-- Next thumbnail -->\n\t<div class=\"pptx-ng-mpresenter-next\">\n\t\t<span class=\"pptx-ng-mpresenter-label\">{{\n\t\t\t'pptx.presenter.nextSlidePreview' | translate\n\t\t}}</span>\n\t\t@if (nextPreviewSlide(); as next) {\n\t\t\t<div class=\"pptx-ng-mpresenter-thumb\" [ngStyle]=\"thumbStyle()\">\n\t\t\t\t<pptx-slide-canvas\n\t\t\t\t\t[slide]=\"next\"\n\t\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zoom]=\"1\"\n\t\t\t\t\t[interactive]=\"false\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t} @else {\n\t\t\t<div class=\"pptx-ng-mpresenter-next-empty\">\n\t\t\t\t{{ 'pptx.presenter.endOfPresentation' | translate }}\n\t\t\t</div>\n\t\t}\n\t</div>\n\n\t<!-- Speaker notes (scrollable) -->\n\t<div class=\"pptx-ng-mpresenter-notes\">\n\t\t<div class=\"pptx-ng-mpresenter-label\">{{ 'pptx.presenter.speakerNotes' | translate }}</div>\n\t\t<div class=\"pptx-ng-mpresenter-notes-body\">\n\t\t\t@if (notes().hasRichNotes) {\n\t\t\t\t@for (seg of notes().segments; track seg.key) {\n\t\t\t\t\t@if (seg.isBreak) {\n\t\t\t\t\t\t<br />\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<span [ngStyle]=\"seg.style\">{{ seg.text }}</span>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} @else if (notes().hasAnyNotes) {\n\t\t\t\t{{ notes().plainText }}\n\t\t\t} @else {\n\t\t\t\t<span class=\"pptx-ng-mpresenter-notes-empty\">{{\n\t\t\t\t\t'pptx.presenter.noNotes' | translate\n\t\t\t\t}}</span>\n\t\t\t}\n\t\t</div>\n\t</div>\n\n\t<!-- Prev / Next controls -->\n\t<div class=\"pptx-ng-mpresenter-ctl\">\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"pptx-ng-mpresenter-navbtn\"\n\t\t\t(click)=\"movePresentationSlide.emit(-1)\"\n\t\t\t[disabled]=\"atFirst()\"\n\t\t\t[attr.aria-label]=\"'pptx.presenter.previousSlide' | translate\"\n\t\t\t[title]=\"'pptx.presenter.previousSlide' | translate\"\n\t\t>\n\t\t\t&#x2039; {{ 'pptx.presenter.prev' | translate }}\n\t\t</button>\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"pptx-ng-mpresenter-navbtn\"\n\t\t\t(click)=\"movePresentationSlide.emit(1)\"\n\t\t\t[disabled]=\"atLast()\"\n\t\t\t[attr.aria-label]=\"'pptx.presenter.nextSlidePreview' | translate\"\n\t\t\t[title]=\"'pptx.presenter.nextSlidePreview' | translate\"\n\t\t>\n\t\t\t{{ 'pptx.presenter.next' | translate }} &#x203A;\n\t\t</button>\n\t</div>\n} @else {\n\t<div class=\"pptx-ng-mpresenter-empty\">{{ 'pptx.presenter.noSlides' | translate }}</div>\n}\n", styles: [":host{position:absolute;inset:0;z-index:50;display:flex;flex-direction:column;background:#0b0b0c;color:#f5f5f5;font-family:system-ui,sans-serif;padding-top:env(safe-area-inset-top,0px);padding-bottom:env(safe-area-inset-bottom,0px);padding-left:env(safe-area-inset-left,0px);padding-right:env(safe-area-inset-right,0px)}.pptx-ng-mpresenter-header,.pptx-ng-mpresenter-next,.pptx-ng-mpresenter-ctl{display:flex;align-items:center;gap:.75rem;padding:.5rem 1rem}.pptx-ng-mpresenter-header{justify-content:space-between;border-bottom:1px solid rgba(255,255,255,.08)}.pptx-ng-mpresenter-label{font-size:.625rem;text-transform:uppercase;letter-spacing:.06em;color:#ffffff8c}.pptx-ng-mpresenter-elapsed{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.125rem;color:#6ea8fe}.pptx-ng-mpresenter-counter{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:.875rem}.pptx-ng-mpresenter-exit{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;min-width:44px;min-height:44px;border:none;border-radius:6px;background:transparent;color:#ffffffbf;cursor:pointer;font-size:1.25rem;line-height:1}.pptx-ng-mpresenter-exit:hover{background:#ffffff1f;color:#fff}.pptx-ng-mpresenter-main{display:flex;align-items:center;justify-content:center;background:#000;padding:.75rem}.pptx-ng-mpresenter-main-stage{width:100%;max-width:640px}.pptx-ng-mpresenter-next{border-bottom:1px solid rgba(255,255,255,.08)}.pptx-ng-mpresenter-thumb{flex:0 0 auto;overflow:hidden;border:1px solid rgba(255,255,255,.15);border-radius:4px}.pptx-ng-mpresenter-next-empty{display:flex;flex:1 1 auto;align-items:center;justify-content:center;height:3rem;border:1px solid rgba(255,255,255,.15);border-radius:4px;background:#ffffff0a;font-size:.625rem;font-style:italic;color:#ffffff80}.pptx-ng-mpresenter-notes{flex:1 1 auto;display:flex;flex-direction:column;min-height:0;padding:.5rem 1rem}.pptx-ng-mpresenter-notes-body{flex:1 1 auto;overflow-y:auto;margin-top:.25rem;border:1px solid rgba(255,255,255,.15);border-radius:6px;background:#ffffff0a;padding:.5rem .75rem;white-space:pre-wrap;line-height:1.5;font-size:15px}.pptx-ng-mpresenter-notes-empty{font-style:italic;color:#ffffff80}.pptx-ng-mpresenter-ctl{justify-content:space-between;border-top:1px solid rgba(255,255,255,.08)}.pptx-ng-mpresenter-navbtn{flex:1 1 0;display:inline-flex;align-items:center;justify-content:center;gap:.375rem;height:44px;border:none;border-radius:6px;background:#ffffff14;color:#f5f5f5;cursor:pointer;font-size:.9rem}.pptx-ng-mpresenter-navbtn:hover:not(:disabled){background:#ffffff29}.pptx-ng-mpresenter-navbtn:disabled{opacity:.4;cursor:not-allowed}.pptx-ng-mpresenter-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#fff9}\n"] }]
77931
+ args: [{ selector: 'pptx-mobile-presenter-view', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgStyle, SlideCanvasComponent, TranslatePipe], template: "@if (currentSlide(); as current) {\n\t<!-- Header: elapsed + counter + exit -->\n\t<div class=\"pptx-ng-mpresenter-header\">\n\t\t<div>\n\t\t\t<div class=\"pptx-ng-mpresenter-label\">{{ 'pptx.presenter.elapsed' | translate }}</div>\n\t\t\t<div class=\"pptx-ng-mpresenter-elapsed\">{{ elapsedLabel() }}</div>\n\t\t</div>\n\t\t<span class=\"pptx-ng-mpresenter-counter\">{{ counterLabel() }}</span>\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"pptx-ng-mpresenter-exit\"\n\t\t\t(click)=\"exit.emit()\"\n\t\t\t[attr.aria-label]=\"'pptx.presenter.endPresentation' | translate\"\n\t\t\t[title]=\"'pptx.presenter.endPresentation' | translate\"\n\t\t>\n\t\t\t&#x2715;\n\t\t</button>\n\t</div>\n\n\t<!-- Current slide (large) -->\n\t<div class=\"pptx-ng-mpresenter-main\">\n\t\t<div class=\"pptx-ng-mpresenter-main-stage\">\n\t\t\t<pptx-slide-canvas\n\t\t\t\t[slide]=\"currentPreviewSlide()\"\n\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t[zoom]=\"1\"\n\t\t\t\t[interactive]=\"false\"\n\t\t\t/>\n\t\t</div>\n\t</div>\n\n\t<!-- Next thumbnail -->\n\t<div class=\"pptx-ng-mpresenter-next\">\n\t\t<span class=\"pptx-ng-mpresenter-label\">{{\n\t\t\t'pptx.presenter.nextSlidePreview' | translate\n\t\t}}</span>\n\t\t@if (nextPreviewSlide(); as next) {\n\t\t\t<div class=\"pptx-ng-mpresenter-thumb\" [ngStyle]=\"thumbStyle()\">\n\t\t\t\t<pptx-slide-canvas\n\t\t\t\t\t[slide]=\"next\"\n\t\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zoom]=\"1\"\n\t\t\t\t\t[interactive]=\"false\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t} @else {\n\t\t\t<div class=\"pptx-ng-mpresenter-next-empty\">\n\t\t\t\t{{ 'pptx.presenter.endOfPresentation' | translate }}\n\t\t\t</div>\n\t\t}\n\t</div>\n\n\t<!-- Speaker notes (scrollable) -->\n\t<div class=\"pptx-ng-mpresenter-notes\">\n\t\t<div class=\"pptx-ng-mpresenter-label\">{{ 'pptx.presenter.speakerNotes' | translate }}</div>\n\t\t<div class=\"pptx-ng-mpresenter-notes-body\">\n\t\t\t@if (notes().hasRichNotes) {\n\t\t\t\t@for (seg of notes().segments; track seg.key) {\n\t\t\t\t\t@if (seg.isBreak) {\n\t\t\t\t\t\t<br />\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<span [ngStyle]=\"seg.style\">{{ seg.text }}</span>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} @else if (notes().hasAnyNotes) {\n\t\t\t\t{{ notes().plainText }}\n\t\t\t} @else {\n\t\t\t\t<span class=\"pptx-ng-mpresenter-notes-empty\">{{\n\t\t\t\t\t'pptx.presenter.noNotes' | translate\n\t\t\t\t}}</span>\n\t\t\t}\n\t\t</div>\n\t</div>\n\n\t<!-- Prev / Next controls -->\n\t<div class=\"pptx-ng-mpresenter-ctl\">\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"pptx-ng-mpresenter-navbtn\"\n\t\t\tdata-pptx-presenter-control=\"prev\"\n\t\t\t(click)=\"movePresentationSlide.emit(-1)\"\n\t\t\t[disabled]=\"prevDisabled()\"\n\t\t\t[attr.aria-label]=\"'pptx.presenter.previousSlide' | translate\"\n\t\t\t[title]=\"'pptx.presenter.previousSlide' | translate\"\n\t\t>\n\t\t\t&#x2039; {{ 'pptx.presenter.prev' | translate }}\n\t\t</button>\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"pptx-ng-mpresenter-navbtn\"\n\t\t\tdata-pptx-presenter-control=\"next\"\n\t\t\t(click)=\"movePresentationSlide.emit(1)\"\n\t\t\t[disabled]=\"nextDisabled()\"\n\t\t\t[attr.aria-label]=\"'pptx.presenter.nextSlide' | translate\"\n\t\t\t[title]=\"'pptx.presenter.nextSlide' | translate\"\n\t\t>\n\t\t\t{{ 'pptx.presenter.next' | translate }} &#x203A;\n\t\t</button>\n\t</div>\n} @else {\n\t<div class=\"pptx-ng-mpresenter-empty\">{{ 'pptx.presenter.noSlides' | translate }}</div>\n}\n", styles: [":host{position:absolute;inset:0;z-index:50;display:flex;flex-direction:column;background:#0b0b0c;color:#f5f5f5;font-family:system-ui,sans-serif;padding-top:env(safe-area-inset-top,0px);padding-bottom:env(safe-area-inset-bottom,0px);padding-left:env(safe-area-inset-left,0px);padding-right:env(safe-area-inset-right,0px)}.pptx-ng-mpresenter-header,.pptx-ng-mpresenter-next,.pptx-ng-mpresenter-ctl{display:flex;align-items:center;gap:.75rem;padding:.5rem 1rem}.pptx-ng-mpresenter-header{justify-content:space-between;border-bottom:1px solid rgba(255,255,255,.08)}.pptx-ng-mpresenter-label{font-size:.625rem;text-transform:uppercase;letter-spacing:.06em;color:#ffffff8c}.pptx-ng-mpresenter-elapsed{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.125rem;color:#6ea8fe}.pptx-ng-mpresenter-counter{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:.875rem}.pptx-ng-mpresenter-exit{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;min-width:44px;min-height:44px;border:none;border-radius:6px;background:transparent;color:#ffffffbf;cursor:pointer;font-size:1.25rem;line-height:1}.pptx-ng-mpresenter-exit:hover{background:#ffffff1f;color:#fff}.pptx-ng-mpresenter-main{display:flex;align-items:center;justify-content:center;background:#000;padding:.75rem}.pptx-ng-mpresenter-main-stage{width:100%;max-width:640px}.pptx-ng-mpresenter-next{border-bottom:1px solid rgba(255,255,255,.08)}.pptx-ng-mpresenter-thumb{flex:0 0 auto;overflow:hidden;border:1px solid rgba(255,255,255,.15);border-radius:4px}.pptx-ng-mpresenter-next-empty{display:flex;flex:1 1 auto;align-items:center;justify-content:center;height:3rem;border:1px solid rgba(255,255,255,.15);border-radius:4px;background:#ffffff0a;font-size:.625rem;font-style:italic;color:#ffffff80}.pptx-ng-mpresenter-notes{flex:1 1 auto;display:flex;flex-direction:column;min-height:0;padding:.5rem 1rem}.pptx-ng-mpresenter-notes-body{flex:1 1 auto;overflow-y:auto;margin-top:.25rem;border:1px solid rgba(255,255,255,.15);border-radius:6px;background:#ffffff0a;padding:.5rem .75rem;white-space:pre-wrap;line-height:1.5;font-size:15px}.pptx-ng-mpresenter-notes-empty{font-style:italic;color:#ffffff80}.pptx-ng-mpresenter-ctl{justify-content:space-between;border-top:1px solid rgba(255,255,255,.08)}.pptx-ng-mpresenter-navbtn{flex:1 1 0;display:inline-flex;align-items:center;justify-content:center;gap:.375rem;height:44px;border:none;border-radius:6px;background:#ffffff14;color:#f5f5f5;cursor:pointer;font-size:.9rem}.pptx-ng-mpresenter-navbtn:hover:not(:disabled){background:#ffffff29}.pptx-ng-mpresenter-navbtn:disabled{opacity:.4;cursor:not-allowed}.pptx-ng-mpresenter-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#fff9}\n"] }]
76455
77932
  }], ctorParameters: () => [], propDecorators: { slides: [{ type: i0.Input, args: [{ isSignal: true, alias: "slides", required: true }] }], currentSlideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentSlideIndex", required: true }] }], canvasSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "canvasSize", required: true }] }], templateElements: [{ type: i0.Input, args: [{ isSignal: true, alias: "templateElements", required: false }] }], mediaDataUrls: [{ type: i0.Input, args: [{ isSignal: true, alias: "mediaDataUrls", required: false }] }], presentationStartTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "presentationStartTime", required: false }] }], movePresentationSlide: [{ type: i0.Output, args: ["movePresentationSlide"] }], exit: [{ type: i0.Output, args: ["exit"] }] } });
76456
77933
 
76457
77934
  /**
@@ -80692,6 +82169,19 @@ class PresenterWindowService {
80692
82169
  isAudienceWindowOpen() {
80693
82170
  return this.audienceWindow !== null && !this.audienceWindow.closed;
80694
82171
  }
82172
+ /**
82173
+ * PowerPoint's "Swap Displays": trade screens with the audience window.
82174
+ * Counterpart of React's `usePresenterWindow().swapDisplays`. False means no
82175
+ * audience window, or no Window Management API to move windows with; that is
82176
+ * a capability report, not a failure, and nothing moves.
82177
+ */
82178
+ async swapDisplays() {
82179
+ const audience = this.audienceWindow;
82180
+ if (typeof window === 'undefined' || !audience || audience.closed) {
82181
+ return false;
82182
+ }
82183
+ return swapPresentationWindows(window, audience);
82184
+ }
80695
82185
  syncSlideToAudience(slideIndex) {
80696
82186
  this.updateSnapshot({ slideIndex });
80697
82187
  }
@@ -83276,7 +84766,11 @@ class PresentationToolbarComponent {
83276
84766
  ...(ngDevMode ? [{ debugName: "now" }] : /* istanbul ignore next */ []));
83277
84767
  counterLabel = computed(() => formatSlideCounter(this.currentSlideIndex(), this.totalSlides()), /* @ts-ignore */
83278
84768
  ...(ngDevMode ? [{ debugName: "counterLabel" }] : /* istanbul ignore next */ []));
83279
- elapsedLabel = computed(() => formatElapsed(this.now() - (this.presentationStartTime() ?? this.mountedAt)), /* @ts-ignore */
84769
+ // `elapsedSince` clamps a start time in the future to zero before formatting.
84770
+ // The shared `formatElapsed` deliberately does not clamp (a negative duration
84771
+ // is meaningful to some callers), so the guard belongs here, at the point the
84772
+ // duration is derived from the wall clock.
84773
+ elapsedLabel = computed(() => formatElapsed(elapsedSince(this.presentationStartTime() ?? this.mountedAt, this.now())), /* @ts-ignore */
83280
84774
  ...(ngDevMode ? [{ debugName: "elapsedLabel" }] : /* istanbul ignore next */ []));
83281
84775
  atFirstSlide = computed(() => isAtFirstSlide(this.currentSlideIndex()), /* @ts-ignore */
83282
84776
  ...(ngDevMode ? [{ debugName: "atFirstSlide" }] : /* istanbul ignore next */ []));
@@ -84202,7 +85696,7 @@ class PresentationOverlayComponent {
84202
85696
  this.closed.emit();
84203
85697
  }
84204
85698
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PresentationOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
84205
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: PresentationOverlayComponent, isStandalone: true, selector: "pptx-presentation-overlay", inputs: { slides: { classPropertyName: "slides", publicName: "slides", isSignal: true, isRequired: true, transformFunction: null }, canvasSize: { classPropertyName: "canvasSize", publicName: "canvasSize", isSignal: true, isRequired: true, transformFunction: null }, mediaDataUrls: { classPropertyName: "mediaDataUrls", publicName: "mediaDataUrls", isSignal: true, isRequired: false, transformFunction: null }, startIndex: { classPropertyName: "startIndex", publicName: "startIndex", isSignal: true, isRequired: false, transformFunction: null }, showWithAnimation: { classPropertyName: "showWithAnimation", publicName: "showWithAnimation", isSignal: true, isRequired: false, transformFunction: null }, useTimings: { classPropertyName: "useTimings", publicName: "useTimings", isSignal: true, isRequired: false, transformFunction: null }, subtitlesVisible: { classPropertyName: "subtitlesVisible", publicName: "subtitlesVisible", isSignal: true, isRequired: false, transformFunction: null }, sessionEnded: { classPropertyName: "sessionEnded", publicName: "sessionEnded", isSignal: true, isRequired: false, transformFunction: null }, endWithBlackSlide: { classPropertyName: "endWithBlackSlide", publicName: "endWithBlackSlide", isSignal: true, isRequired: false, transformFunction: null }, presenterMode: { classPropertyName: "presenterMode", publicName: "presenterMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { indexChange: "indexChange", closed: "closed", subtitlesChange: "subtitlesChange", presenterViewToggle: "presenterViewToggle", annotationsExit: "annotationsExit" }, host: { listeners: { "document:fullscreenchange": "onFullscreenChange()", "window:resize": "onWindowResize()", "document:keydown": "onKeyDown($event)" } }, providers: [AnimationPlaybackService, PresentationAnnotationsService, ZoomNavigationService], viewQueries: [{ propertyName: "stageRef", first: true, predicate: ["stage"], descendants: true, isSignal: true }, { propertyName: "rootRef", first: true, predicate: ["root"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #root class=\"pptx-ng-presentation-root\">\n\t<!--\n\t\tSlide counter, rendered first in DOM (before slide content) so a\n\t\tgeneric \"N / M\" text query resolves to it rather than to any slide-text\n\t\trun that happens to read like \"24 / 7\". Position is fixed, so DOM order\n\t\tdoes not affect its on-screen placement.\n\t-->\n\t<span class=\"pptx-ng-presentation-counter\" [ngStyle]=\"counterStyle\">\n\t\t{{ counterLabel() }}\n\t</span>\n\n\t<!-- Black \"End of slide show\" screen: the show has run past its last\n\t slide. It MUST be visible - while it is up the next input either\n\t goes nowhere (backward) or ends the show (forward), so a deck that\n\t kept painting the last slide looked stuck and swallowed advances. -->\n\t@if (endOfShow()) {\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"pptx-ng-presentation-end\"\n\t\t\tdata-pptx-end-of-show\n\t\t\t(click)=\"onEndScreenClick($event)\"\n\t\t>\n\t\t\t<span>{{ 'pptx.presentation.endOfSlideShow' | translate }}</span>\n\t\t</button>\n\t}\n\n\t<div\n\t\t#stage\n\t\tclass=\"pptx-ng-presentation-stage\"\n\t\t[ngStyle]=\"stageContainerStyle()\"\n\t\t(click)=\"onBodyClick($event)\"\n\t\t(mouseover)=\"onStageHover($event)\"\n\t\t(mouseout)=\"onStageHoverEnd($event)\"\n\t\t(contextmenu)=\"$event.preventDefault()\"\n\t>\n\t\t<pptx-slide-canvas\n\t\t\t[slide]=\"currentSlide()\"\n\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t[zoom]=\"zoom()\"\n\t\t\t[autoFit]=\"false\"\n\t\t\t[interactive]=\"false\"\n\t\t\t[presenting]=\"true\"\n\t\t/>\n\n\t\t@if (activeTransition(); as t) {\n\t\t\t<pptx-presentation-transition-overlay\n\t\t\t\t[outgoingSlide]=\"t.outgoing\"\n\t\t\t\t[incomingSlide]=\"currentSlide()\"\n\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t[transition]=\"t.transition\"\n\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t[zoom]=\"zoom()\"\n\t\t\t\t(complete)=\"activeTransition.set(null)\"\n\t\t\t/>\n\t\t}\n\n\t\t<!-- Ink annotation overlay (pen/highlighter/eraser/laser). Ctrl+M\n\t\t hides the markup without discarding the strokes. -->\n\t\t@if (inkMarkupVisible()) {\n\t\t\t<pptx-presentation-annotation-overlay [canvasSize]=\"canvasSize()\" [zoom]=\"zoom()\" />\n\t\t}\n\t</div>\n\t@if (presenterWindow.snapshot().blackout !== 'none') {\n\t\t<div class=\"presenter-blank\" [style.background]=\"presenterWindow.snapshot().blackout\"></div>\n\t}\n\t@if (presenterWindow.snapshot().pointer?.tool === 'laser') {\n\t\t<div\n\t\t\tclass=\"presenter-laser\"\n\t\t\t[style.left.%]=\"(presenterWindow.snapshot().pointer?.x ?? 0.5) * 100\"\n\t\t\t[style.top.%]=\"(presenterWindow.snapshot().pointer?.y ?? 0.5) * 100\"\n\t\t></div>\n\t}\n\t@if (presenterWindow.snapshot().subtitlesVisible && presenterWindow.snapshot().caption) {\n\t\t<div class=\"presenter-caption\">{{ presenterWindow.snapshot().caption }}</div>\n\t}\n\n\t<!-- Live-caption (subtitle) bar. -->\n\t<pptx-presentation-subtitle-bar [visible]=\"subtitlesVisible()\" />\n\n\t<!--\n\t\tThe show toolbar (bottom-centre, auto-hiding): navigation, counter,\n\t\telapsed time, annotation tools, presenter view and end-show, per\n\t\t`PRESENT_TOOLBAR_CONTROLS` in pptx-viewer-shared.\n\t-->\n\t<pptx-presentation-toolbar\n\t\t[currentSlideIndex]=\"currentIndex()\"\n\t\t[totalSlides]=\"slides().length\"\n\t\t[presentationStartTime]=\"showStartedAt\"\n\t\t[presenterMode]=\"presenterMode()\"\n\t\t(move)=\"navigator.navigate($event === 1 ? 'next' : 'prev')\"\n\t\t(presenterViewToggle)=\"presenterViewToggle.emit()\"\n\t\t(endPresentation)=\"onToolbarEnd()\"\n\t/>\n\n\t<!--\n\t\tTouch-only chrome (close button, edge arrows, counter pill). The\n\t\tcomponent stylesheet hides all three unless the device has a coarse\n\t\tpointer, matching React's `PresentationTouchControls`: on a desktop they\n\t\tduplicate the toolbar above and put a SECOND slide counter on screen.\n\t-->\n\t<!-- Always-visible close button (top-right, safe-area aware). -->\n\t<button\n\t\ttype=\"button\"\n\t\tclass=\"pptx-ng-presentation-close\"\n\t\t[ngStyle]=\"closeButtonStyle\"\n\t\t(click)=\"onChromeButton($event, 'close')\"\n\t\t(touchend)=\"onChromeButtonTouch($event, 'close')\"\n\t\t[attr.aria-label]=\"'pptx.presenter.endPresentation' | translate\"\n\t>\n\t\t<svg lucideX class=\"h-5 w-5\"></svg>\n\t</button>\n\n\t<!-- Edge navigation buttons (vertically centred, touch-friendly). -->\n\t<button\n\t\ttype=\"button\"\n\t\tclass=\"pptx-ng-presentation-nav pptx-ng-presentation-prev\"\n\t\t[ngStyle]=\"prevButtonStyle\"\n\t\t(click)=\"onChromeButton($event, 'prev')\"\n\t\t(touchend)=\"onChromeButtonTouch($event, 'prev')\"\n\t\t[attr.aria-label]=\"'pptx.presenter.previousSlide' | translate\"\n\t>\n\t\t<svg lucideChevronLeft class=\"h-6 w-6\"></svg>\n\t</button>\n\t<button\n\t\ttype=\"button\"\n\t\tclass=\"pptx-ng-presentation-nav pptx-ng-presentation-next\"\n\t\t[ngStyle]=\"nextButtonStyle\"\n\t\t(click)=\"onChromeButton($event, 'next')\"\n\t\t(touchend)=\"onChromeButtonTouch($event, 'next')\"\n\t\t[attr.aria-label]=\"'pptx.presenter.nextSlide' | translate\"\n\t>\n\t\t<svg lucideChevronRight class=\"h-6 w-6\"></svg>\n\t</button>\n</div>\n", styles: [":host{display:block;position:fixed;inset:0;z-index:10000;background:#000;cursor:pointer;-webkit-user-select:none;user-select:none}.pptx-ng-presentation-root{position:absolute;inset:0;touch-action:pan-y}.pptx-ng-presentation-close:hover,.pptx-ng-presentation-nav:hover{background:#000000bf}@media not all and (any-pointer:coarse){.pptx-ng-presentation-close,.pptx-ng-presentation-nav,.pptx-ng-presentation-counter{display:none!important}}.presenter-blank{position:absolute;inset:0;z-index:75}.pptx-ng-presentation-end{position:absolute;inset:0;z-index:90;display:flex;align-items:flex-start;border:0;padding:0;background:#000;text-align:left;cursor:default}.pptx-ng-presentation-end span{padding:.75rem 1rem;color:#ffffffb3;font-size:12px}.presenter-laser{position:absolute;z-index:76;width:20px;height:20px;transform:translate(-50%,-50%);border-radius:50%;background:#ef4444;box-shadow:0 0 20px 8px #ef444488;pointer-events:none}.presenter-caption{position:absolute;z-index:77;left:10%;right:10%;bottom:2rem;padding:.75rem 1.5rem;border-radius:.5rem;background:#000c;color:#fff;text-align:center;font-size:1.25rem;pointer-events:none}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: SlideCanvasComponent, selector: "pptx-slide-canvas", inputs: ["slide", "canvasSize", "mediaDataUrls", "zoom", "editable", "showGrid", "showRulers", "rulerUnit", "showGuides", "snapToGrid", "snapToShape", "guideCommand", "spellCheck", "snapToGuides", "autoFit", "transparentBackground", "interactive", "presenting", "selectedIds", "editingId", "editTemplateMode", "templateElements", "aiHighlights", "aiActive", "aiActiveSlideIndex", "aiChangeBatch", "aiPickMode", "drawTool", "drawColor", "drawWidth"], outputs: ["elementSelect", "backgroundClick", "transformStart", "transformUpdate", "contextMenu", "textEditStart", "textCommit", "textInput", "textCancel", "textFormat", "rotateUpdate", "marqueeSelect", "inkStrokeComplete", "eraserHit", "cellCommit", "tableChange"] }, { kind: "component", type: PresentationTransitionOverlayComponent, selector: "pptx-presentation-transition-overlay", inputs: ["outgoingSlide", "canvasSize", "transition", "templateElements", "mediaDataUrls", "durationMs", "zoom", "incomingSlide"], outputs: ["complete"] }, { kind: "component", type: PresentationAnnotationOverlayComponent, selector: "pptx-presentation-annotation-overlay", inputs: ["canvasSize", "zoom"] }, { kind: "component", type: PresentationSubtitleBarComponent, selector: "pptx-presentation-subtitle-bar", inputs: ["visible"] }, { kind: "component", type: PresentationToolbarComponent, selector: "pptx-presentation-toolbar", inputs: ["currentSlideIndex", "totalSlides", "presentationStartTime", "presenterMode"], outputs: ["move", "endPresentation", "presenterViewToggle"] }, { kind: "component", type: LucideX, selector: "svg[lucideX]" }, { kind: "component", type: LucideChevronLeft, selector: "svg[lucideChevronLeft]" }, { kind: "component", type: LucideChevronRight, selector: "svg[lucideChevronRight]" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
85699
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: PresentationOverlayComponent, isStandalone: true, selector: "pptx-presentation-overlay", inputs: { slides: { classPropertyName: "slides", publicName: "slides", isSignal: true, isRequired: true, transformFunction: null }, canvasSize: { classPropertyName: "canvasSize", publicName: "canvasSize", isSignal: true, isRequired: true, transformFunction: null }, mediaDataUrls: { classPropertyName: "mediaDataUrls", publicName: "mediaDataUrls", isSignal: true, isRequired: false, transformFunction: null }, startIndex: { classPropertyName: "startIndex", publicName: "startIndex", isSignal: true, isRequired: false, transformFunction: null }, showWithAnimation: { classPropertyName: "showWithAnimation", publicName: "showWithAnimation", isSignal: true, isRequired: false, transformFunction: null }, useTimings: { classPropertyName: "useTimings", publicName: "useTimings", isSignal: true, isRequired: false, transformFunction: null }, subtitlesVisible: { classPropertyName: "subtitlesVisible", publicName: "subtitlesVisible", isSignal: true, isRequired: false, transformFunction: null }, sessionEnded: { classPropertyName: "sessionEnded", publicName: "sessionEnded", isSignal: true, isRequired: false, transformFunction: null }, endWithBlackSlide: { classPropertyName: "endWithBlackSlide", publicName: "endWithBlackSlide", isSignal: true, isRequired: false, transformFunction: null }, presenterMode: { classPropertyName: "presenterMode", publicName: "presenterMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { indexChange: "indexChange", closed: "closed", subtitlesChange: "subtitlesChange", presenterViewToggle: "presenterViewToggle", annotationsExit: "annotationsExit" }, host: { listeners: { "document:fullscreenchange": "onFullscreenChange()", "window:resize": "onWindowResize()", "document:keydown": "onKeyDown($event)" } }, providers: [AnimationPlaybackService, PresentationAnnotationsService, ZoomNavigationService], viewQueries: [{ propertyName: "stageRef", first: true, predicate: ["stage"], descendants: true, isSignal: true }, { propertyName: "rootRef", first: true, predicate: ["root"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #root class=\"pptx-ng-presentation-root\">\n\t<!--\n\t\tSlide counter, rendered first in DOM (before slide content) so a\n\t\tgeneric \"N / M\" text query resolves to it rather than to any slide-text\n\t\trun that happens to read like \"24 / 7\". Position is fixed, so DOM order\n\t\tdoes not affect its on-screen placement.\n\t-->\n\t<span class=\"pptx-ng-presentation-counter\" [ngStyle]=\"counterStyle\">\n\t\t{{ counterLabel() }}\n\t</span>\n\n\t<!-- Black \"End of slide show\" screen: the show has run past its last\n\t slide. It MUST be visible - while it is up the next input either\n\t goes nowhere (backward) or ends the show (forward), so a deck that\n\t kept painting the last slide looked stuck and swallowed advances. -->\n\t@if (endOfShow()) {\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"pptx-ng-presentation-end\"\n\t\t\tdata-pptx-end-of-show\n\t\t\t(click)=\"onEndScreenClick($event)\"\n\t\t>\n\t\t\t<span>{{ 'pptx.presentation.endOfSlideShow' | translate }}</span>\n\t\t</button>\n\t}\n\n\t<div\n\t\t#stage\n\t\tclass=\"pptx-ng-presentation-stage\"\n\t\t[ngStyle]=\"stageContainerStyle()\"\n\t\t(click)=\"onBodyClick($event)\"\n\t\t(mouseover)=\"onStageHover($event)\"\n\t\t(mouseout)=\"onStageHoverEnd($event)\"\n\t\t(contextmenu)=\"$event.preventDefault()\"\n\t>\n\t\t<pptx-slide-canvas\n\t\t\t[slide]=\"currentSlide()\"\n\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t[zoom]=\"zoom()\"\n\t\t\t[autoFit]=\"false\"\n\t\t\t[interactive]=\"false\"\n\t\t\t[presenting]=\"true\"\n\t\t/>\n\n\t\t@if (activeTransition(); as t) {\n\t\t\t<pptx-presentation-transition-overlay\n\t\t\t\t[outgoingSlide]=\"t.outgoing\"\n\t\t\t\t[incomingSlide]=\"currentSlide()\"\n\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t[transition]=\"t.transition\"\n\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t[zoom]=\"zoom()\"\n\t\t\t\t(complete)=\"activeTransition.set(null)\"\n\t\t\t/>\n\t\t}\n\n\t\t<!-- Ink annotation overlay (pen/highlighter/eraser/laser). Ctrl+M\n\t\t hides the markup without discarding the strokes. -->\n\t\t@if (inkMarkupVisible()) {\n\t\t\t<pptx-presentation-annotation-overlay [canvasSize]=\"canvasSize()\" [zoom]=\"zoom()\" />\n\t\t}\n\t</div>\n\t@if (presenterWindow.snapshot().blackout !== 'none') {\n\t\t<div class=\"presenter-blank\" [style.background]=\"presenterWindow.snapshot().blackout\"></div>\n\t}\n\t@if (presenterWindow.snapshot().pointer?.tool === 'laser') {\n\t\t<div\n\t\t\tclass=\"presenter-laser\"\n\t\t\t[style.left.%]=\"(presenterWindow.snapshot().pointer?.x ?? 0.5) * 100\"\n\t\t\t[style.top.%]=\"(presenterWindow.snapshot().pointer?.y ?? 0.5) * 100\"\n\t\t></div>\n\t}\n\t@if (presenterWindow.snapshot().subtitlesVisible && presenterWindow.snapshot().caption) {\n\t\t<div class=\"presenter-caption\">{{ presenterWindow.snapshot().caption }}</div>\n\t}\n\n\t<!-- Live-caption (subtitle) bar. -->\n\t<pptx-presentation-subtitle-bar [visible]=\"subtitlesVisible()\" />\n\n\t<!--\n\t\tTouch-only chrome (close button, edge arrows, counter pill). The\n\t\tcomponent stylesheet hides all three unless the device has a coarse\n\t\tpointer, matching React's `PresentationTouchControls`: on a desktop they\n\t\tduplicate the toolbar above and put a SECOND slide counter on screen.\n\n\t\tRendered BEFORE the show toolbar, as React renders its touch controls,\n\t\tbecause both carry the same accessible names (\"Next Slide\", \"Previous\n\t\tSlide\", \"End Presentation\"). On a coarse pointer the toolbar's copies are\n\t\tstill in the DOM but inert (`pointer-events: none`), so whichever comes\n\t\tfirst is what a by-name lookup - a screen reader's, or a spec's `.first()`\n\t\t- resolves to. With the toolbar first, every such lookup landed on a\n\t\tbutton no user could press: it reported itself visible and enabled, and\n\t\tthe tap was swallowed by the overlay. Position is fixed on all of these,\n\t\tso moving them in the DOM does not move them on screen.\n\t-->\n\t<!-- Always-visible close button (top-right, safe-area aware). -->\n\t<button\n\t\ttype=\"button\"\n\t\tclass=\"pptx-ng-presentation-close\"\n\t\t[ngStyle]=\"closeButtonStyle\"\n\t\t(click)=\"onChromeButton($event, 'close')\"\n\t\t(touchend)=\"onChromeButtonTouch($event, 'close')\"\n\t\t[attr.aria-label]=\"'pptx.presenter.endPresentation' | translate\"\n\t>\n\t\t<svg lucideX class=\"h-5 w-5\"></svg>\n\t</button>\n\n\t<!-- Edge navigation buttons (vertically centred, touch-friendly). -->\n\t<button\n\t\ttype=\"button\"\n\t\tclass=\"pptx-ng-presentation-nav pptx-ng-presentation-prev\"\n\t\t[ngStyle]=\"prevButtonStyle\"\n\t\t(click)=\"onChromeButton($event, 'prev')\"\n\t\t(touchend)=\"onChromeButtonTouch($event, 'prev')\"\n\t\t[attr.aria-label]=\"'pptx.presenter.previousSlide' | translate\"\n\t>\n\t\t<svg lucideChevronLeft class=\"h-6 w-6\"></svg>\n\t</button>\n\t<button\n\t\ttype=\"button\"\n\t\tclass=\"pptx-ng-presentation-nav pptx-ng-presentation-next\"\n\t\t[ngStyle]=\"nextButtonStyle\"\n\t\t(click)=\"onChromeButton($event, 'next')\"\n\t\t(touchend)=\"onChromeButtonTouch($event, 'next')\"\n\t\t[attr.aria-label]=\"'pptx.presenter.nextSlide' | translate\"\n\t>\n\t\t<svg lucideChevronRight class=\"h-6 w-6\"></svg>\n\t</button>\n\n\t<!--\n\t\tThe show toolbar (bottom-centre, auto-hiding): navigation, counter,\n\t\telapsed time, annotation tools, presenter view and end-show, per\n\t\t`PRESENT_TOOLBAR_CONTROLS` in pptx-viewer-shared.\n\t-->\n\t<pptx-presentation-toolbar\n\t\t[currentSlideIndex]=\"currentIndex()\"\n\t\t[totalSlides]=\"slides().length\"\n\t\t[presentationStartTime]=\"showStartedAt\"\n\t\t[presenterMode]=\"presenterMode()\"\n\t\t(move)=\"navigator.navigate($event === 1 ? 'next' : 'prev')\"\n\t\t(presenterViewToggle)=\"presenterViewToggle.emit()\"\n\t\t(endPresentation)=\"onToolbarEnd()\"\n\t/>\n</div>\n", styles: [":host{display:block;position:fixed;inset:0;z-index:10000;background:#000;cursor:pointer;-webkit-user-select:none;user-select:none}.pptx-ng-presentation-root{position:absolute;inset:0;touch-action:pan-y}.pptx-ng-presentation-close:hover,.pptx-ng-presentation-nav:hover{background:#000000bf}@media not all and (any-pointer:coarse){.pptx-ng-presentation-close,.pptx-ng-presentation-nav,.pptx-ng-presentation-counter{display:none!important}}.presenter-blank{position:absolute;inset:0;z-index:75}.pptx-ng-presentation-end{position:absolute;inset:0;z-index:90;display:flex;align-items:flex-start;border:0;padding:0;background:#000;text-align:left;cursor:default}.pptx-ng-presentation-end span{padding:.75rem 1rem;color:#ffffffb3;font-size:12px}.presenter-laser{position:absolute;z-index:76;width:20px;height:20px;transform:translate(-50%,-50%);border-radius:50%;background:#ef4444;box-shadow:0 0 20px 8px #ef444488;pointer-events:none}.presenter-caption{position:absolute;z-index:77;left:10%;right:10%;bottom:2rem;padding:.75rem 1.5rem;border-radius:.5rem;background:#000c;color:#fff;text-align:center;font-size:1.25rem;pointer-events:none}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: SlideCanvasComponent, selector: "pptx-slide-canvas", inputs: ["slide", "canvasSize", "mediaDataUrls", "zoom", "editable", "showGrid", "showRulers", "rulerUnit", "showGuides", "snapToGrid", "snapToShape", "guideCommand", "spellCheck", "snapToGuides", "autoFit", "transparentBackground", "interactive", "presenting", "selectedIds", "editingId", "editTemplateMode", "templateElements", "aiHighlights", "aiActive", "aiActiveSlideIndex", "aiChangeBatch", "aiPickMode", "drawTool", "drawColor", "drawWidth"], outputs: ["elementSelect", "backgroundClick", "transformStart", "transformUpdate", "contextMenu", "textEditStart", "textCommit", "textInput", "textCancel", "textFormat", "rotateUpdate", "marqueeSelect", "inkStrokeComplete", "eraserHit", "cellCommit", "tableChange"] }, { kind: "component", type: PresentationTransitionOverlayComponent, selector: "pptx-presentation-transition-overlay", inputs: ["outgoingSlide", "canvasSize", "transition", "templateElements", "mediaDataUrls", "durationMs", "zoom", "incomingSlide"], outputs: ["complete"] }, { kind: "component", type: PresentationAnnotationOverlayComponent, selector: "pptx-presentation-annotation-overlay", inputs: ["canvasSize", "zoom"] }, { kind: "component", type: PresentationSubtitleBarComponent, selector: "pptx-presentation-subtitle-bar", inputs: ["visible"] }, { kind: "component", type: PresentationToolbarComponent, selector: "pptx-presentation-toolbar", inputs: ["currentSlideIndex", "totalSlides", "presentationStartTime", "presenterMode"], outputs: ["move", "endPresentation", "presenterViewToggle"] }, { kind: "component", type: LucideX, selector: "svg[lucideX]" }, { kind: "component", type: LucideChevronLeft, selector: "svg[lucideChevronLeft]" }, { kind: "component", type: LucideChevronRight, selector: "svg[lucideChevronRight]" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
84206
85700
  }
84207
85701
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PresentationOverlayComponent, decorators: [{
84208
85702
  type: Component,
@@ -84217,7 +85711,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
84217
85711
  LucideX,
84218
85712
  LucideChevronLeft,
84219
85713
  LucideChevronRight,
84220
- ], providers: [AnimationPlaybackService, PresentationAnnotationsService, ZoomNavigationService], template: "<div #root class=\"pptx-ng-presentation-root\">\n\t<!--\n\t\tSlide counter, rendered first in DOM (before slide content) so a\n\t\tgeneric \"N / M\" text query resolves to it rather than to any slide-text\n\t\trun that happens to read like \"24 / 7\". Position is fixed, so DOM order\n\t\tdoes not affect its on-screen placement.\n\t-->\n\t<span class=\"pptx-ng-presentation-counter\" [ngStyle]=\"counterStyle\">\n\t\t{{ counterLabel() }}\n\t</span>\n\n\t<!-- Black \"End of slide show\" screen: the show has run past its last\n\t slide. It MUST be visible - while it is up the next input either\n\t goes nowhere (backward) or ends the show (forward), so a deck that\n\t kept painting the last slide looked stuck and swallowed advances. -->\n\t@if (endOfShow()) {\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"pptx-ng-presentation-end\"\n\t\t\tdata-pptx-end-of-show\n\t\t\t(click)=\"onEndScreenClick($event)\"\n\t\t>\n\t\t\t<span>{{ 'pptx.presentation.endOfSlideShow' | translate }}</span>\n\t\t</button>\n\t}\n\n\t<div\n\t\t#stage\n\t\tclass=\"pptx-ng-presentation-stage\"\n\t\t[ngStyle]=\"stageContainerStyle()\"\n\t\t(click)=\"onBodyClick($event)\"\n\t\t(mouseover)=\"onStageHover($event)\"\n\t\t(mouseout)=\"onStageHoverEnd($event)\"\n\t\t(contextmenu)=\"$event.preventDefault()\"\n\t>\n\t\t<pptx-slide-canvas\n\t\t\t[slide]=\"currentSlide()\"\n\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t[zoom]=\"zoom()\"\n\t\t\t[autoFit]=\"false\"\n\t\t\t[interactive]=\"false\"\n\t\t\t[presenting]=\"true\"\n\t\t/>\n\n\t\t@if (activeTransition(); as t) {\n\t\t\t<pptx-presentation-transition-overlay\n\t\t\t\t[outgoingSlide]=\"t.outgoing\"\n\t\t\t\t[incomingSlide]=\"currentSlide()\"\n\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t[transition]=\"t.transition\"\n\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t[zoom]=\"zoom()\"\n\t\t\t\t(complete)=\"activeTransition.set(null)\"\n\t\t\t/>\n\t\t}\n\n\t\t<!-- Ink annotation overlay (pen/highlighter/eraser/laser). Ctrl+M\n\t\t hides the markup without discarding the strokes. -->\n\t\t@if (inkMarkupVisible()) {\n\t\t\t<pptx-presentation-annotation-overlay [canvasSize]=\"canvasSize()\" [zoom]=\"zoom()\" />\n\t\t}\n\t</div>\n\t@if (presenterWindow.snapshot().blackout !== 'none') {\n\t\t<div class=\"presenter-blank\" [style.background]=\"presenterWindow.snapshot().blackout\"></div>\n\t}\n\t@if (presenterWindow.snapshot().pointer?.tool === 'laser') {\n\t\t<div\n\t\t\tclass=\"presenter-laser\"\n\t\t\t[style.left.%]=\"(presenterWindow.snapshot().pointer?.x ?? 0.5) * 100\"\n\t\t\t[style.top.%]=\"(presenterWindow.snapshot().pointer?.y ?? 0.5) * 100\"\n\t\t></div>\n\t}\n\t@if (presenterWindow.snapshot().subtitlesVisible && presenterWindow.snapshot().caption) {\n\t\t<div class=\"presenter-caption\">{{ presenterWindow.snapshot().caption }}</div>\n\t}\n\n\t<!-- Live-caption (subtitle) bar. -->\n\t<pptx-presentation-subtitle-bar [visible]=\"subtitlesVisible()\" />\n\n\t<!--\n\t\tThe show toolbar (bottom-centre, auto-hiding): navigation, counter,\n\t\telapsed time, annotation tools, presenter view and end-show, per\n\t\t`PRESENT_TOOLBAR_CONTROLS` in pptx-viewer-shared.\n\t-->\n\t<pptx-presentation-toolbar\n\t\t[currentSlideIndex]=\"currentIndex()\"\n\t\t[totalSlides]=\"slides().length\"\n\t\t[presentationStartTime]=\"showStartedAt\"\n\t\t[presenterMode]=\"presenterMode()\"\n\t\t(move)=\"navigator.navigate($event === 1 ? 'next' : 'prev')\"\n\t\t(presenterViewToggle)=\"presenterViewToggle.emit()\"\n\t\t(endPresentation)=\"onToolbarEnd()\"\n\t/>\n\n\t<!--\n\t\tTouch-only chrome (close button, edge arrows, counter pill). The\n\t\tcomponent stylesheet hides all three unless the device has a coarse\n\t\tpointer, matching React's `PresentationTouchControls`: on a desktop they\n\t\tduplicate the toolbar above and put a SECOND slide counter on screen.\n\t-->\n\t<!-- Always-visible close button (top-right, safe-area aware). -->\n\t<button\n\t\ttype=\"button\"\n\t\tclass=\"pptx-ng-presentation-close\"\n\t\t[ngStyle]=\"closeButtonStyle\"\n\t\t(click)=\"onChromeButton($event, 'close')\"\n\t\t(touchend)=\"onChromeButtonTouch($event, 'close')\"\n\t\t[attr.aria-label]=\"'pptx.presenter.endPresentation' | translate\"\n\t>\n\t\t<svg lucideX class=\"h-5 w-5\"></svg>\n\t</button>\n\n\t<!-- Edge navigation buttons (vertically centred, touch-friendly). -->\n\t<button\n\t\ttype=\"button\"\n\t\tclass=\"pptx-ng-presentation-nav pptx-ng-presentation-prev\"\n\t\t[ngStyle]=\"prevButtonStyle\"\n\t\t(click)=\"onChromeButton($event, 'prev')\"\n\t\t(touchend)=\"onChromeButtonTouch($event, 'prev')\"\n\t\t[attr.aria-label]=\"'pptx.presenter.previousSlide' | translate\"\n\t>\n\t\t<svg lucideChevronLeft class=\"h-6 w-6\"></svg>\n\t</button>\n\t<button\n\t\ttype=\"button\"\n\t\tclass=\"pptx-ng-presentation-nav pptx-ng-presentation-next\"\n\t\t[ngStyle]=\"nextButtonStyle\"\n\t\t(click)=\"onChromeButton($event, 'next')\"\n\t\t(touchend)=\"onChromeButtonTouch($event, 'next')\"\n\t\t[attr.aria-label]=\"'pptx.presenter.nextSlide' | translate\"\n\t>\n\t\t<svg lucideChevronRight class=\"h-6 w-6\"></svg>\n\t</button>\n</div>\n", styles: [":host{display:block;position:fixed;inset:0;z-index:10000;background:#000;cursor:pointer;-webkit-user-select:none;user-select:none}.pptx-ng-presentation-root{position:absolute;inset:0;touch-action:pan-y}.pptx-ng-presentation-close:hover,.pptx-ng-presentation-nav:hover{background:#000000bf}@media not all and (any-pointer:coarse){.pptx-ng-presentation-close,.pptx-ng-presentation-nav,.pptx-ng-presentation-counter{display:none!important}}.presenter-blank{position:absolute;inset:0;z-index:75}.pptx-ng-presentation-end{position:absolute;inset:0;z-index:90;display:flex;align-items:flex-start;border:0;padding:0;background:#000;text-align:left;cursor:default}.pptx-ng-presentation-end span{padding:.75rem 1rem;color:#ffffffb3;font-size:12px}.presenter-laser{position:absolute;z-index:76;width:20px;height:20px;transform:translate(-50%,-50%);border-radius:50%;background:#ef4444;box-shadow:0 0 20px 8px #ef444488;pointer-events:none}.presenter-caption{position:absolute;z-index:77;left:10%;right:10%;bottom:2rem;padding:.75rem 1.5rem;border-radius:.5rem;background:#000c;color:#fff;text-align:center;font-size:1.25rem;pointer-events:none}\n"] }]
85714
+ ], providers: [AnimationPlaybackService, PresentationAnnotationsService, ZoomNavigationService], template: "<div #root class=\"pptx-ng-presentation-root\">\n\t<!--\n\t\tSlide counter, rendered first in DOM (before slide content) so a\n\t\tgeneric \"N / M\" text query resolves to it rather than to any slide-text\n\t\trun that happens to read like \"24 / 7\". Position is fixed, so DOM order\n\t\tdoes not affect its on-screen placement.\n\t-->\n\t<span class=\"pptx-ng-presentation-counter\" [ngStyle]=\"counterStyle\">\n\t\t{{ counterLabel() }}\n\t</span>\n\n\t<!-- Black \"End of slide show\" screen: the show has run past its last\n\t slide. It MUST be visible - while it is up the next input either\n\t goes nowhere (backward) or ends the show (forward), so a deck that\n\t kept painting the last slide looked stuck and swallowed advances. -->\n\t@if (endOfShow()) {\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"pptx-ng-presentation-end\"\n\t\t\tdata-pptx-end-of-show\n\t\t\t(click)=\"onEndScreenClick($event)\"\n\t\t>\n\t\t\t<span>{{ 'pptx.presentation.endOfSlideShow' | translate }}</span>\n\t\t</button>\n\t}\n\n\t<div\n\t\t#stage\n\t\tclass=\"pptx-ng-presentation-stage\"\n\t\t[ngStyle]=\"stageContainerStyle()\"\n\t\t(click)=\"onBodyClick($event)\"\n\t\t(mouseover)=\"onStageHover($event)\"\n\t\t(mouseout)=\"onStageHoverEnd($event)\"\n\t\t(contextmenu)=\"$event.preventDefault()\"\n\t>\n\t\t<pptx-slide-canvas\n\t\t\t[slide]=\"currentSlide()\"\n\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t[zoom]=\"zoom()\"\n\t\t\t[autoFit]=\"false\"\n\t\t\t[interactive]=\"false\"\n\t\t\t[presenting]=\"true\"\n\t\t/>\n\n\t\t@if (activeTransition(); as t) {\n\t\t\t<pptx-presentation-transition-overlay\n\t\t\t\t[outgoingSlide]=\"t.outgoing\"\n\t\t\t\t[incomingSlide]=\"currentSlide()\"\n\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t[transition]=\"t.transition\"\n\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t[zoom]=\"zoom()\"\n\t\t\t\t(complete)=\"activeTransition.set(null)\"\n\t\t\t/>\n\t\t}\n\n\t\t<!-- Ink annotation overlay (pen/highlighter/eraser/laser). Ctrl+M\n\t\t hides the markup without discarding the strokes. -->\n\t\t@if (inkMarkupVisible()) {\n\t\t\t<pptx-presentation-annotation-overlay [canvasSize]=\"canvasSize()\" [zoom]=\"zoom()\" />\n\t\t}\n\t</div>\n\t@if (presenterWindow.snapshot().blackout !== 'none') {\n\t\t<div class=\"presenter-blank\" [style.background]=\"presenterWindow.snapshot().blackout\"></div>\n\t}\n\t@if (presenterWindow.snapshot().pointer?.tool === 'laser') {\n\t\t<div\n\t\t\tclass=\"presenter-laser\"\n\t\t\t[style.left.%]=\"(presenterWindow.snapshot().pointer?.x ?? 0.5) * 100\"\n\t\t\t[style.top.%]=\"(presenterWindow.snapshot().pointer?.y ?? 0.5) * 100\"\n\t\t></div>\n\t}\n\t@if (presenterWindow.snapshot().subtitlesVisible && presenterWindow.snapshot().caption) {\n\t\t<div class=\"presenter-caption\">{{ presenterWindow.snapshot().caption }}</div>\n\t}\n\n\t<!-- Live-caption (subtitle) bar. -->\n\t<pptx-presentation-subtitle-bar [visible]=\"subtitlesVisible()\" />\n\n\t<!--\n\t\tTouch-only chrome (close button, edge arrows, counter pill). The\n\t\tcomponent stylesheet hides all three unless the device has a coarse\n\t\tpointer, matching React's `PresentationTouchControls`: on a desktop they\n\t\tduplicate the toolbar above and put a SECOND slide counter on screen.\n\n\t\tRendered BEFORE the show toolbar, as React renders its touch controls,\n\t\tbecause both carry the same accessible names (\"Next Slide\", \"Previous\n\t\tSlide\", \"End Presentation\"). On a coarse pointer the toolbar's copies are\n\t\tstill in the DOM but inert (`pointer-events: none`), so whichever comes\n\t\tfirst is what a by-name lookup - a screen reader's, or a spec's `.first()`\n\t\t- resolves to. With the toolbar first, every such lookup landed on a\n\t\tbutton no user could press: it reported itself visible and enabled, and\n\t\tthe tap was swallowed by the overlay. Position is fixed on all of these,\n\t\tso moving them in the DOM does not move them on screen.\n\t-->\n\t<!-- Always-visible close button (top-right, safe-area aware). -->\n\t<button\n\t\ttype=\"button\"\n\t\tclass=\"pptx-ng-presentation-close\"\n\t\t[ngStyle]=\"closeButtonStyle\"\n\t\t(click)=\"onChromeButton($event, 'close')\"\n\t\t(touchend)=\"onChromeButtonTouch($event, 'close')\"\n\t\t[attr.aria-label]=\"'pptx.presenter.endPresentation' | translate\"\n\t>\n\t\t<svg lucideX class=\"h-5 w-5\"></svg>\n\t</button>\n\n\t<!-- Edge navigation buttons (vertically centred, touch-friendly). -->\n\t<button\n\t\ttype=\"button\"\n\t\tclass=\"pptx-ng-presentation-nav pptx-ng-presentation-prev\"\n\t\t[ngStyle]=\"prevButtonStyle\"\n\t\t(click)=\"onChromeButton($event, 'prev')\"\n\t\t(touchend)=\"onChromeButtonTouch($event, 'prev')\"\n\t\t[attr.aria-label]=\"'pptx.presenter.previousSlide' | translate\"\n\t>\n\t\t<svg lucideChevronLeft class=\"h-6 w-6\"></svg>\n\t</button>\n\t<button\n\t\ttype=\"button\"\n\t\tclass=\"pptx-ng-presentation-nav pptx-ng-presentation-next\"\n\t\t[ngStyle]=\"nextButtonStyle\"\n\t\t(click)=\"onChromeButton($event, 'next')\"\n\t\t(touchend)=\"onChromeButtonTouch($event, 'next')\"\n\t\t[attr.aria-label]=\"'pptx.presenter.nextSlide' | translate\"\n\t>\n\t\t<svg lucideChevronRight class=\"h-6 w-6\"></svg>\n\t</button>\n\n\t<!--\n\t\tThe show toolbar (bottom-centre, auto-hiding): navigation, counter,\n\t\telapsed time, annotation tools, presenter view and end-show, per\n\t\t`PRESENT_TOOLBAR_CONTROLS` in pptx-viewer-shared.\n\t-->\n\t<pptx-presentation-toolbar\n\t\t[currentSlideIndex]=\"currentIndex()\"\n\t\t[totalSlides]=\"slides().length\"\n\t\t[presentationStartTime]=\"showStartedAt\"\n\t\t[presenterMode]=\"presenterMode()\"\n\t\t(move)=\"navigator.navigate($event === 1 ? 'next' : 'prev')\"\n\t\t(presenterViewToggle)=\"presenterViewToggle.emit()\"\n\t\t(endPresentation)=\"onToolbarEnd()\"\n\t/>\n</div>\n", styles: [":host{display:block;position:fixed;inset:0;z-index:10000;background:#000;cursor:pointer;-webkit-user-select:none;user-select:none}.pptx-ng-presentation-root{position:absolute;inset:0;touch-action:pan-y}.pptx-ng-presentation-close:hover,.pptx-ng-presentation-nav:hover{background:#000000bf}@media not all and (any-pointer:coarse){.pptx-ng-presentation-close,.pptx-ng-presentation-nav,.pptx-ng-presentation-counter{display:none!important}}.presenter-blank{position:absolute;inset:0;z-index:75}.pptx-ng-presentation-end{position:absolute;inset:0;z-index:90;display:flex;align-items:flex-start;border:0;padding:0;background:#000;text-align:left;cursor:default}.pptx-ng-presentation-end span{padding:.75rem 1rem;color:#ffffffb3;font-size:12px}.presenter-laser{position:absolute;z-index:76;width:20px;height:20px;transform:translate(-50%,-50%);border-radius:50%;background:#ef4444;box-shadow:0 0 20px 8px #ef444488;pointer-events:none}.presenter-caption{position:absolute;z-index:77;left:10%;right:10%;bottom:2rem;padding:.75rem 1.5rem;border-radius:.5rem;background:#000c;color:#fff;text-align:center;font-size:1.25rem;pointer-events:none}\n"] }]
84221
85715
  }], ctorParameters: () => [], propDecorators: { slides: [{ type: i0.Input, args: [{ isSignal: true, alias: "slides", required: true }] }], canvasSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "canvasSize", required: true }] }], mediaDataUrls: [{ type: i0.Input, args: [{ isSignal: true, alias: "mediaDataUrls", required: false }] }], startIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "startIndex", required: false }] }], showWithAnimation: [{ type: i0.Input, args: [{ isSignal: true, alias: "showWithAnimation", required: false }] }], useTimings: [{ type: i0.Input, args: [{ isSignal: true, alias: "useTimings", required: false }] }], subtitlesVisible: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitlesVisible", required: false }] }], sessionEnded: [{ type: i0.Input, args: [{ isSignal: true, alias: "sessionEnded", required: false }] }], endWithBlackSlide: [{ type: i0.Input, args: [{ isSignal: true, alias: "endWithBlackSlide", required: false }] }], presenterMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "presenterMode", required: false }] }], indexChange: [{ type: i0.Output, args: ["indexChange"] }], closed: [{ type: i0.Output, args: ["closed"] }], subtitlesChange: [{ type: i0.Output, args: ["subtitlesChange"] }], presenterViewToggle: [{ type: i0.Output, args: ["presenterViewToggle"] }], annotationsExit: [{ type: i0.Output, args: ["annotationsExit"] }], stageRef: [{ type: i0.ViewChild, args: ['stage', { isSignal: true }] }], rootRef: [{ type: i0.ViewChild, args: ['root', { isSignal: true }] }], onFullscreenChange: [{
84222
85716
  type: HostListener,
84223
85717
  args: ['document:fullscreenchange']
@@ -84229,6 +85723,440 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
84229
85723
  args: ['document:keydown', ['$event']]
84230
85724
  }] } });
84231
85725
 
85726
+ /**
85727
+ * presenter-console-helpers.ts
85728
+ *
85729
+ * Turns the shared presenter-console INVENTORY
85730
+ * (`PRESENTER_CONSOLE_CONTROLS`) into the per-slot view model the Angular strip
85731
+ * binds to.
85732
+ *
85733
+ * WHY this is a helper and not template logic: the strip used to be an inline
85734
+ * template of sixteen hand-written buttons with hard-coded English labels and no
85735
+ * `translate` pipe in sight, so the console was unreadable in every locale but
85736
+ * one and its black-screen switch announced itself to a screen reader as the
85737
+ * letter "B". Deriving the strip from the shared inventory fixes that at the
85738
+ * source, and keeping the derivation here (rather than in the component) keeps
85739
+ * the SFC-equivalent thin and lets the mapping be unit-tested without TestBed.
85740
+ *
85741
+ * Everything except the click handlers comes from `pptx-viewer-shared`: order,
85742
+ * ids, kinds, label keys, icon names and glyphs. Only "what does pressing this
85743
+ * do" stays in the component, because that is the genuinely per-binding half.
85744
+ */
85745
+ /**
85746
+ * The four strip slots that select an annotation tool, by control id.
85747
+ *
85748
+ * A lookup rather than four `case` arms because the ids and the tool names are
85749
+ * deliberately identical: if the shared inventory ever renames one, the missing
85750
+ * entry is a compile-visible gap instead of a silently inert button.
85751
+ */
85752
+ const POINTER_TOOLS = {
85753
+ laser: 'laser',
85754
+ pen: 'pen',
85755
+ highlighter: 'highlighter',
85756
+ eraser: 'eraser',
85757
+ };
85758
+ /**
85759
+ * Whether a slot reads as "on" right now.
85760
+ *
85761
+ * `timer-toggle` and `zoom-in` are buttons, not toggles: their activity only
85762
+ * picks the resume glyph / the emphasised zoom style, and
85763
+ * {@link PresenterConsoleSlot.pressed} keeps `aria-pressed` off them.
85764
+ */
85765
+ function presenterSlotActive(control, snapshot, audienceOpen) {
85766
+ const tool = POINTER_TOOLS[control.id];
85767
+ if (tool !== undefined) {
85768
+ return (snapshot.pointer?.tool ?? 'none') === tool;
85769
+ }
85770
+ switch (control.id) {
85771
+ case 'timer-toggle':
85772
+ return snapshot.paused;
85773
+ case 'zoom-in':
85774
+ return (snapshot.zoom?.scale ?? 1) > 1;
85775
+ case 'blackout-black':
85776
+ return snapshot.blackout === 'black';
85777
+ case 'blackout-white':
85778
+ return snapshot.blackout === 'white';
85779
+ case 'captions':
85780
+ return snapshot.subtitlesVisible === true;
85781
+ case 'audience':
85782
+ return audienceOpen;
85783
+ default:
85784
+ return false;
85785
+ }
85786
+ }
85787
+ /**
85788
+ * Whether a slot is unusable in the current session.
85789
+ *
85790
+ * Only Swap Displays, which needs a second window to swap with. Notably NOT
85791
+ * Next: see `presenterNextDisabled` in the shared module for why disabling it
85792
+ * strands the presenter on the final slide.
85793
+ */
85794
+ function presenterSlotDisabled(controlId, audienceOpen) {
85795
+ return controlId === 'swap-displays' && !audienceOpen;
85796
+ }
85797
+ /**
85798
+ * Resolve the whole strip, in the shared inventory's order.
85799
+ *
85800
+ * Dividers and spacers come through as slots too (with no label, icon or
85801
+ * handler) so the template renders one list instead of interleaving two.
85802
+ */
85803
+ function presenterConsoleSlots(snapshot, audienceOpen) {
85804
+ return PRESENTER_CONSOLE_CONTROLS.map((control) => {
85805
+ const active = presenterSlotActive(control, snapshot, audienceOpen);
85806
+ const isToggle = control.kind === 'toggle';
85807
+ return {
85808
+ control,
85809
+ id: control.id,
85810
+ active,
85811
+ disabled: presenterSlotDisabled(control.id, audienceOpen),
85812
+ labelKey: active && control.activeLabelKey ? control.activeLabelKey : control.labelKey,
85813
+ iconName: active && control.activeIcon ? control.activeIcon : control.icon,
85814
+ pressed: isToggle ? active : null,
85815
+ tool: POINTER_TOOLS[control.id],
85816
+ };
85817
+ });
85818
+ }
85819
+
85820
+ /**
85821
+ * presenter-console-icon.component.ts
85822
+ *
85823
+ * Renders one presenter-console glyph from the kebab-case icon NAME the shared
85824
+ * inventory publishes (`PresenterControl.icon` / `.activeIcon`).
85825
+ *
85826
+ * WHY a component and not an inline `@switch` in the strip: `@lucide/angular`
85827
+ * ships one standalone directive per icon (`svg[lucideZoomIn]`) and no
85828
+ * name-driven host, so the mapping is necessarily sixteen static template arms.
85829
+ * Keeping them here leaves `presenter-controls.component.ts` a thin strip and
85830
+ * keeps both files inside the repo's 300-line ceiling. React does the same
85831
+ * mapping with a `Record<string, IconType>`; this is its Angular half.
85832
+ *
85833
+ * An unknown name renders nothing rather than a fallback glyph: the button
85834
+ * still carries its translated `aria-label`, so a slot added to the shared
85835
+ * inventory before its icon is wired here degrades to an empty (but named and
85836
+ * usable) control instead of a wrong picture.
85837
+ */
85838
+ class PresenterConsoleIconComponent {
85839
+ /** kebab-case Lucide icon name, straight from the shared inventory. */
85840
+ name = input(undefined, /* @ts-ignore */
85841
+ ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
85842
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PresenterConsoleIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
85843
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: PresenterConsoleIconComponent, isStandalone: true, selector: "pptx-presenter-console-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "contents" }, ngImport: i0, template: `
85844
+ @switch (name()) {
85845
+ @case ('arrow-left-right') {
85846
+ <svg lucideArrowLeftRight class="h-4 w-4"></svg>
85847
+ }
85848
+ @case ('captions') {
85849
+ <svg lucideCaptions class="h-4 w-4"></svg>
85850
+ }
85851
+ @case ('circle-pause') {
85852
+ <svg lucideCirclePause class="h-4 w-4"></svg>
85853
+ }
85854
+ @case ('circle-play') {
85855
+ <svg lucideCirclePlay class="h-4 w-4"></svg>
85856
+ }
85857
+ @case ('eraser') {
85858
+ <svg lucideEraser class="h-4 w-4"></svg>
85859
+ }
85860
+ @case ('grid-2x2') {
85861
+ <svg lucideGrid2x2 class="h-4 w-4"></svg>
85862
+ }
85863
+ @case ('highlighter') {
85864
+ <svg lucideHighlighter class="h-4 w-4"></svg>
85865
+ }
85866
+ @case ('monitor') {
85867
+ <svg lucideMonitor class="h-4 w-4"></svg>
85868
+ }
85869
+ @case ('monitor-off') {
85870
+ <svg lucideMonitorOff class="h-4 w-4"></svg>
85871
+ }
85872
+ @case ('mouse-pointer-2') {
85873
+ <svg lucideMousePointer2 class="h-4 w-4"></svg>
85874
+ }
85875
+ @case ('pen-tool') {
85876
+ <svg lucidePenTool class="h-4 w-4"></svg>
85877
+ }
85878
+ @case ('rotate-ccw') {
85879
+ <svg lucideRotateCcw class="h-4 w-4"></svg>
85880
+ }
85881
+ @case ('scan') {
85882
+ <svg lucideScan class="h-4 w-4"></svg>
85883
+ }
85884
+ @case ('x') {
85885
+ <svg lucideX class="h-4 w-4"></svg>
85886
+ }
85887
+ @case ('zoom-in') {
85888
+ <svg lucideZoomIn class="h-4 w-4"></svg>
85889
+ }
85890
+ @case ('zoom-out') {
85891
+ <svg lucideZoomOut class="h-4 w-4"></svg>
85892
+ }
85893
+ }
85894
+ `, isInline: true, dependencies: [{ kind: "component", type: LucideArrowLeftRight, selector: "svg[lucideArrowLeftRight]" }, { kind: "component", type: LucideCaptions, selector: "svg[lucideCaptions], svg[lucideSubtitles]" }, { kind: "component", type: LucideCirclePause, selector: "svg[lucideCirclePause], svg[lucidePauseCircle]" }, { kind: "component", type: LucideCirclePlay, selector: "svg[lucideCirclePlay], svg[lucidePlayCircle]" }, { kind: "component", type: LucideEraser, selector: "svg[lucideEraser]" }, { kind: "component", type: LucideGrid2x2, selector: "svg[lucideGrid2x2], svg[lucideGrid2X2]" }, { kind: "component", type: LucideHighlighter, selector: "svg[lucideHighlighter]" }, { kind: "component", type: LucideMonitor, selector: "svg[lucideMonitor]" }, { kind: "component", type: LucideMonitorOff, selector: "svg[lucideMonitorOff]" }, { kind: "component", type: LucideMousePointer2, selector: "svg[lucideMousePointer2]" }, { kind: "component", type: LucidePenTool, selector: "svg[lucidePenTool]" }, { kind: "component", type: LucideRotateCcw, selector: "svg[lucideRotateCcw]" }, { kind: "component", type: LucideScan, selector: "svg[lucideScan]" }, { kind: "component", type: LucideX, selector: "svg[lucideX]" }, { kind: "component", type: LucideZoomIn, selector: "svg[lucideZoomIn]" }, { kind: "component", type: LucideZoomOut, selector: "svg[lucideZoomOut]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
85895
+ }
85896
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PresenterConsoleIconComponent, decorators: [{
85897
+ type: Component,
85898
+ args: [{
85899
+ selector: 'pptx-presenter-console-icon',
85900
+ standalone: true,
85901
+ changeDetection: ChangeDetectionStrategy.OnPush,
85902
+ host: { class: 'contents' },
85903
+ imports: [
85904
+ LucideArrowLeftRight,
85905
+ LucideCaptions,
85906
+ LucideCirclePause,
85907
+ LucideCirclePlay,
85908
+ LucideEraser,
85909
+ LucideGrid2x2,
85910
+ LucideHighlighter,
85911
+ LucideMonitor,
85912
+ LucideMonitorOff,
85913
+ LucideMousePointer2,
85914
+ LucidePenTool,
85915
+ LucideRotateCcw,
85916
+ LucideScan,
85917
+ LucideX,
85918
+ LucideZoomIn,
85919
+ LucideZoomOut,
85920
+ ],
85921
+ template: `
85922
+ @switch (name()) {
85923
+ @case ('arrow-left-right') {
85924
+ <svg lucideArrowLeftRight class="h-4 w-4"></svg>
85925
+ }
85926
+ @case ('captions') {
85927
+ <svg lucideCaptions class="h-4 w-4"></svg>
85928
+ }
85929
+ @case ('circle-pause') {
85930
+ <svg lucideCirclePause class="h-4 w-4"></svg>
85931
+ }
85932
+ @case ('circle-play') {
85933
+ <svg lucideCirclePlay class="h-4 w-4"></svg>
85934
+ }
85935
+ @case ('eraser') {
85936
+ <svg lucideEraser class="h-4 w-4"></svg>
85937
+ }
85938
+ @case ('grid-2x2') {
85939
+ <svg lucideGrid2x2 class="h-4 w-4"></svg>
85940
+ }
85941
+ @case ('highlighter') {
85942
+ <svg lucideHighlighter class="h-4 w-4"></svg>
85943
+ }
85944
+ @case ('monitor') {
85945
+ <svg lucideMonitor class="h-4 w-4"></svg>
85946
+ }
85947
+ @case ('monitor-off') {
85948
+ <svg lucideMonitorOff class="h-4 w-4"></svg>
85949
+ }
85950
+ @case ('mouse-pointer-2') {
85951
+ <svg lucideMousePointer2 class="h-4 w-4"></svg>
85952
+ }
85953
+ @case ('pen-tool') {
85954
+ <svg lucidePenTool class="h-4 w-4"></svg>
85955
+ }
85956
+ @case ('rotate-ccw') {
85957
+ <svg lucideRotateCcw class="h-4 w-4"></svg>
85958
+ }
85959
+ @case ('scan') {
85960
+ <svg lucideScan class="h-4 w-4"></svg>
85961
+ }
85962
+ @case ('x') {
85963
+ <svg lucideX class="h-4 w-4"></svg>
85964
+ }
85965
+ @case ('zoom-in') {
85966
+ <svg lucideZoomIn class="h-4 w-4"></svg>
85967
+ }
85968
+ @case ('zoom-out') {
85969
+ <svg lucideZoomOut class="h-4 w-4"></svg>
85970
+ }
85971
+ }
85972
+ `,
85973
+ }]
85974
+ }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }] } });
85975
+
85976
+ /**
85977
+ * presenter-slide-navigator.component.ts
85978
+ *
85979
+ * PowerPoint's "See All Slides" overlay, reachable from the presenter console's
85980
+ * grid button. Angular port of React's `PresenterSlideNavigator`.
85981
+ *
85982
+ * Split out of {@link PresenterControlsComponent} so the strip stays a strip:
85983
+ * the overlay is a full-viewport surface with its own heading, grid and close
85984
+ * affordance, and folding it back in pushed that file past the repo's 300-line
85985
+ * ceiling.
85986
+ *
85987
+ * Its three strings used to be hard-coded English here, which made the only way
85988
+ * OUT of the overlay ("Close") unreadable in every other locale. They now
85989
+ * resolve through the shared `PRESENTER_NAVIGATOR_LABEL_KEYS`, as in the other
85990
+ * four bindings, and the grid geometry comes from the shared metrics rather
85991
+ * than a magic 220px repeated per binding.
85992
+ */
85993
+ class PresenterSlideNavigatorComponent {
85994
+ slides = input.required(/* @ts-ignore */
85995
+ ...(ngDevMode ? [{ debugName: "slides" }] : /* istanbul ignore next */ []));
85996
+ current = input.required(/* @ts-ignore */
85997
+ ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
85998
+ canvasSize = input.required(/* @ts-ignore */
85999
+ ...(ngDevMode ? [{ debugName: "canvasSize" }] : /* istanbul ignore next */ []));
86000
+ mediaDataUrls = input.required(/* @ts-ignore */
86001
+ ...(ngDevMode ? [{ debugName: "mediaDataUrls" }] : /* istanbul ignore next */ []));
86002
+ /** Master/layout elements drawn behind every tile, as on the live stage. */
86003
+ templateElements = input([], /* @ts-ignore */
86004
+ ...(ngDevMode ? [{ debugName: "templateElements" }] : /* istanbul ignore next */ []));
86005
+ select = output();
86006
+ close = output();
86007
+ classes = PRESENTER_CONSOLE_CLASSES;
86008
+ tileWidth = PRESENTER_LAYOUT_METRICS.navigatorTileWidth;
86009
+ /**
86010
+ * Tile scale. `autoFit` is off (as on every other thumbnail in this package)
86011
+ * so this zoom is the ONLY scale; with auto-fit left on, the two compound and
86012
+ * the tiles shrink to a fraction of the 200px the grid reserves for them.
86013
+ */
86014
+ tileZoom = computed(() => {
86015
+ const width = this.canvasSize().width;
86016
+ return width > 0 ? this.tileWidth / width : 1;
86017
+ }, /* @ts-ignore */
86018
+ ...(ngDevMode ? [{ debugName: "tileZoom" }] : /* istanbul ignore next */ []));
86019
+ tiles = computed(() => {
86020
+ const template = this.templateElements();
86021
+ return this.slides().map((slide, index) => ({
86022
+ slide: template.length > 0 ? { ...slide, elements: [...template, ...slide.elements] } : slide,
86023
+ index,
86024
+ key: slide.id || `slide-${String(index)}`,
86025
+ }));
86026
+ }, /* @ts-ignore */
86027
+ ...(ngDevMode ? [{ debugName: "tiles" }] : /* istanbul ignore next */ []));
86028
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PresenterSlideNavigatorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
86029
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: PresenterSlideNavigatorComponent, isStandalone: true, selector: "pptx-presenter-slide-navigator", inputs: { slides: { classPropertyName: "slides", publicName: "slides", isSignal: true, isRequired: true, transformFunction: null }, current: { classPropertyName: "current", publicName: "current", isSignal: true, isRequired: true, transformFunction: null }, canvasSize: { classPropertyName: "canvasSize", publicName: "canvasSize", isSignal: true, isRequired: true, transformFunction: null }, mediaDataUrls: { classPropertyName: "mediaDataUrls", publicName: "mediaDataUrls", isSignal: true, isRequired: true, transformFunction: null }, templateElements: { classPropertyName: "templateElements", publicName: "templateElements", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { select: "select", close: "close" }, ngImport: i0, template: `
86030
+ <div [class]="classes.navigator + ' text-foreground'" data-pptx-presenter-navigator>
86031
+ <header class="mb-4 flex items-center justify-between border-b border-border pb-4">
86032
+ <div>
86033
+ <p class="text-xs uppercase tracking-[0.22em] text-sky-300">
86034
+ {{ 'pptx.presenter.slideNavigator' | translate }}
86035
+ </p>
86036
+ <h2 class="text-xl font-semibold">
86037
+ {{ 'pptx.presenter.seeAllSlides' | translate }}
86038
+ </h2>
86039
+ </div>
86040
+ <button
86041
+ type="button"
86042
+ class="rounded-md bg-muted px-4 py-2 hover:bg-accent"
86043
+ data-pptx-presenter-control="navigator-close"
86044
+ [attr.aria-label]="'pptx.presenter.closeNavigator' | translate"
86045
+ [title]="'pptx.presenter.closeNavigator' | translate"
86046
+ (click)="close.emit()"
86047
+ >
86048
+ {{ 'pptx.presenter.closeNavigator' | translate }}
86049
+ </button>
86050
+ </header>
86051
+ <div [class]="classes.navigatorGrid">
86052
+ @for (tile of tiles(); track tile.key) {
86053
+ <button
86054
+ type="button"
86055
+ class="group text-left"
86056
+ [class.ring-2]="tile.index === current()"
86057
+ [class.ring-sky-400]="tile.index === current()"
86058
+ [class.opacity-45]="tile.slide.hidden"
86059
+ (click)="select.emit(tile.index)"
86060
+ >
86061
+ <div class="overflow-hidden" [style.width.px]="tileWidth">
86062
+ <pptx-slide-canvas
86063
+ [slide]="tile.slide"
86064
+ [canvasSize]="canvasSize()"
86065
+ [mediaDataUrls]="mediaDataUrls()"
86066
+ [zoom]="tileZoom()"
86067
+ [autoFit]="false"
86068
+ [interactive]="false"
86069
+ />
86070
+ </div>
86071
+ <span class="mt-2 block text-xs tabular-nums text-muted-foreground">
86072
+ {{ tile.index + 1 }}{{ tile.slide.hidden ? ' - hidden' : '' }}
86073
+ </span>
86074
+ </button>
86075
+ }
86076
+ </div>
86077
+ </div>
86078
+ `, isInline: true, dependencies: [{ kind: "component", type: SlideCanvasComponent, selector: "pptx-slide-canvas", inputs: ["slide", "canvasSize", "mediaDataUrls", "zoom", "editable", "showGrid", "showRulers", "rulerUnit", "showGuides", "snapToGrid", "snapToShape", "guideCommand", "spellCheck", "snapToGuides", "autoFit", "transparentBackground", "interactive", "presenting", "selectedIds", "editingId", "editTemplateMode", "templateElements", "aiHighlights", "aiActive", "aiActiveSlideIndex", "aiChangeBatch", "aiPickMode", "drawTool", "drawColor", "drawWidth"], outputs: ["elementSelect", "backgroundClick", "transformStart", "transformUpdate", "contextMenu", "textEditStart", "textCommit", "textInput", "textCancel", "textFormat", "rotateUpdate", "marqueeSelect", "inkStrokeComplete", "eraserHit", "cellCommit", "tableChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
86079
+ }
86080
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PresenterSlideNavigatorComponent, decorators: [{
86081
+ type: Component,
86082
+ args: [{
86083
+ selector: 'pptx-presenter-slide-navigator',
86084
+ standalone: true,
86085
+ changeDetection: ChangeDetectionStrategy.OnPush,
86086
+ imports: [SlideCanvasComponent, TranslatePipe],
86087
+ template: `
86088
+ <div [class]="classes.navigator + ' text-foreground'" data-pptx-presenter-navigator>
86089
+ <header class="mb-4 flex items-center justify-between border-b border-border pb-4">
86090
+ <div>
86091
+ <p class="text-xs uppercase tracking-[0.22em] text-sky-300">
86092
+ {{ 'pptx.presenter.slideNavigator' | translate }}
86093
+ </p>
86094
+ <h2 class="text-xl font-semibold">
86095
+ {{ 'pptx.presenter.seeAllSlides' | translate }}
86096
+ </h2>
86097
+ </div>
86098
+ <button
86099
+ type="button"
86100
+ class="rounded-md bg-muted px-4 py-2 hover:bg-accent"
86101
+ data-pptx-presenter-control="navigator-close"
86102
+ [attr.aria-label]="'pptx.presenter.closeNavigator' | translate"
86103
+ [title]="'pptx.presenter.closeNavigator' | translate"
86104
+ (click)="close.emit()"
86105
+ >
86106
+ {{ 'pptx.presenter.closeNavigator' | translate }}
86107
+ </button>
86108
+ </header>
86109
+ <div [class]="classes.navigatorGrid">
86110
+ @for (tile of tiles(); track tile.key) {
86111
+ <button
86112
+ type="button"
86113
+ class="group text-left"
86114
+ [class.ring-2]="tile.index === current()"
86115
+ [class.ring-sky-400]="tile.index === current()"
86116
+ [class.opacity-45]="tile.slide.hidden"
86117
+ (click)="select.emit(tile.index)"
86118
+ >
86119
+ <div class="overflow-hidden" [style.width.px]="tileWidth">
86120
+ <pptx-slide-canvas
86121
+ [slide]="tile.slide"
86122
+ [canvasSize]="canvasSize()"
86123
+ [mediaDataUrls]="mediaDataUrls()"
86124
+ [zoom]="tileZoom()"
86125
+ [autoFit]="false"
86126
+ [interactive]="false"
86127
+ />
86128
+ </div>
86129
+ <span class="mt-2 block text-xs tabular-nums text-muted-foreground">
86130
+ {{ tile.index + 1 }}{{ tile.slide.hidden ? ' - hidden' : '' }}
86131
+ </span>
86132
+ </button>
86133
+ }
86134
+ </div>
86135
+ </div>
86136
+ `,
86137
+ }]
86138
+ }], propDecorators: { slides: [{ type: i0.Input, args: [{ isSignal: true, alias: "slides", required: true }] }], current: [{ type: i0.Input, args: [{ isSignal: true, alias: "current", required: true }] }], canvasSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "canvasSize", required: true }] }], mediaDataUrls: [{ type: i0.Input, args: [{ isSignal: true, alias: "mediaDataUrls", required: true }] }], templateElements: [{ type: i0.Input, args: [{ isSignal: true, alias: "templateElements", required: false }] }], select: [{ type: i0.Output, args: ["select"] }], close: [{ type: i0.Output, args: ["close"] }] } });
86139
+
86140
+ /**
86141
+ * presenter-controls.component.ts
86142
+ *
86143
+ * PowerPoint's presenter-console strip, rendered from the SHARED inventory
86144
+ * (`PRESENTER_CONSOLE_CONTROLS`) rather than hand-written.
86145
+ *
86146
+ * The hand-written version is what this replaces, and it had drifted in three
86147
+ * separate ways at once: it hard-coded English in an inline template with no
86148
+ * `translate` pipe anywhere (so the console was unreadable in de/es/fr), it
86149
+ * ordered zoom `-` before `+` where every other binding puts `+` first, and it
86150
+ * called reset-zoom "Fit". Worse, the blackout switches were labelled only by
86151
+ * their `B` / `W` glyph, so a screen reader announced the deck's black-screen
86152
+ * control as the letter "B".
86153
+ *
86154
+ * Now the order, ids, kinds, label keys, icon names, glyphs and Tailwind class
86155
+ * tokens all come from `pptx-viewer-shared`; the only thing left here is what a
86156
+ * press DOES, which is the genuinely per-binding half. Every control carries
86157
+ * `data-pptx-presenter-control` so the cross-binding e2e specs can address the
86158
+ * strip by id instead of by (now translated) name.
86159
+ */
84232
86160
  class PresenterControlsComponent {
84233
86161
  snapshot = input.required(/* @ts-ignore */
84234
86162
  ...(ngDevMode ? [{ debugName: "snapshot" }] : /* istanbul ignore next */ []));
@@ -84242,23 +86170,85 @@ class PresenterControlsComponent {
84242
86170
  ...(ngDevMode ? [{ debugName: "canvasSize" }] : /* istanbul ignore next */ []));
84243
86171
  mediaDataUrls = input.required(/* @ts-ignore */
84244
86172
  ...(ngDevMode ? [{ debugName: "mediaDataUrls" }] : /* istanbul ignore next */ []));
86173
+ /** Master/layout elements drawn behind every navigator tile. */
86174
+ templateElements = input([], /* @ts-ignore */
86175
+ ...(ngDevMode ? [{ debugName: "templateElements" }] : /* istanbul ignore next */ []));
84245
86176
  patch = output();
84246
86177
  navigate = output();
84247
86178
  audience = output();
86179
+ swapDisplays = output();
84248
86180
  end = output();
86181
+ classes = PRESENTER_CONSOLE_CLASSES;
84249
86182
  showSlides = signal(false, /* @ts-ignore */
84250
86183
  ...(ngDevMode ? [{ debugName: "showSlides" }] : /* istanbul ignore next */ []));
84251
- tools = ['laser', 'pen', 'highlighter', 'eraser'];
86184
+ slots = computed(() => presenterConsoleSlots(this.snapshot(), this.audienceOpen()), /* @ts-ignore */
86185
+ ...(ngDevMode ? [{ debugName: "slots" }] : /* istanbul ignore next */ []));
86186
+ /**
86187
+ * Run a slot's action.
86188
+ *
86189
+ * A slot the shared inventory grows before this switch learns it renders
86190
+ * inert rather than firing a neighbour's handler: a missing behaviour shows
86191
+ * up in the parity specs, a wrong one silently ends the show.
86192
+ */
86193
+ activate(slot) {
86194
+ if (slot.tool !== undefined) {
86195
+ this.setTool(slot.tool, slot.active);
86196
+ return;
86197
+ }
86198
+ switch (slot.id) {
86199
+ case 'timer-toggle':
86200
+ this.patch.emit({ paused: !this.snapshot().paused });
86201
+ break;
86202
+ case 'timer-reset':
86203
+ this.patch.emit({ paused: false, elapsedMs: 0 });
86204
+ break;
86205
+ case 'all-slides':
86206
+ this.showSlides.set(true);
86207
+ break;
86208
+ case 'zoom-in':
86209
+ this.zoom(1);
86210
+ break;
86211
+ case 'zoom-out':
86212
+ this.zoom(-1);
86213
+ break;
86214
+ case 'zoom-reset':
86215
+ this.patch.emit({ zoom: { scale: 1, originX: 0.5, originY: 0.5 } });
86216
+ break;
86217
+ case 'blackout-black':
86218
+ this.toggleBlank('black');
86219
+ break;
86220
+ case 'blackout-white':
86221
+ this.toggleBlank('white');
86222
+ break;
86223
+ case 'captions':
86224
+ this.patch.emit({ subtitlesVisible: !this.snapshot().subtitlesVisible });
86225
+ break;
86226
+ case 'audience':
86227
+ this.audience.emit();
86228
+ break;
86229
+ case 'swap-displays':
86230
+ this.swapDisplays.emit();
86231
+ break;
86232
+ case 'end':
86233
+ this.end.emit();
86234
+ break;
86235
+ default:
86236
+ break;
86237
+ }
86238
+ }
84252
86239
  zoom(direction) {
84253
86240
  this.patch.emit({
84254
86241
  zoom: stepPresenterZoom(this.snapshot().zoom ?? { scale: 1, originX: 0.5, originY: 0.5 }, direction),
84255
86242
  });
84256
86243
  }
84257
- setTool(tool) {
86244
+ setTool(tool, active) {
86245
+ if (tool === undefined) {
86246
+ return;
86247
+ }
84258
86248
  this.patch.emit({
84259
86249
  pointer: {
84260
86250
  ...(this.snapshot().pointer ?? { x: 0.5, y: 0.5, color: '#ef4444' }),
84261
- tool: this.snapshot().pointer?.tool === tool ? 'none' : tool,
86251
+ tool: active ? 'none' : tool,
84262
86252
  },
84263
86253
  });
84264
86254
  }
@@ -84270,38 +86260,88 @@ class PresenterControlsComponent {
84270
86260
  this.showSlides.set(false);
84271
86261
  }
84272
86262
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PresenterControlsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
84273
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: PresenterControlsComponent, isStandalone: true, selector: "pptx-presenter-controls", inputs: { snapshot: { classPropertyName: "snapshot", publicName: "snapshot", isSignal: true, isRequired: true, transformFunction: null }, audienceOpen: { classPropertyName: "audienceOpen", publicName: "audienceOpen", isSignal: true, isRequired: false, transformFunction: null }, slides: { classPropertyName: "slides", publicName: "slides", isSignal: true, isRequired: true, transformFunction: null }, current: { classPropertyName: "current", publicName: "current", isSignal: true, isRequired: true, transformFunction: null }, canvasSize: { classPropertyName: "canvasSize", publicName: "canvasSize", isSignal: true, isRequired: true, transformFunction: null }, mediaDataUrls: { classPropertyName: "mediaDataUrls", publicName: "mediaDataUrls", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { patch: "patch", navigate: "navigate", audience: "audience", end: "end" }, ngImport: i0, template: `
84274
- <div class="strip">
84275
- <button (click)="patch.emit({paused:!snapshot().paused})">{{snapshot().paused?'Resume':'Pause'}}</button><button (click)="patch.emit({paused:false,elapsedMs:0})">Reset</button>
84276
- <button (click)="showSlides.set(true)">All slides</button><button (click)="zoom(-1)">Zoom -</button><button (click)="zoom(1)">Zoom +</button><button (click)="patch.emit({zoom:{scale:1,originX:.5,originY:.5}})">Fit</button>
84277
- @for(tool of tools;track tool){<button [class.active]="snapshot().pointer?.tool===tool" (click)="setTool(tool)">{{tool}}</button>}
84278
- <button [class.active]="snapshot().blackout==='black'" (click)="toggleBlank('black')">B</button><button [class.active]="snapshot().blackout==='white'" (click)="toggleBlank('white')">W</button>
84279
- <button [class.active]="snapshot().subtitlesVisible" (click)="patch.emit({subtitlesVisible:!snapshot().subtitlesVisible})">Captions</button><span></span><button (click)="audience.emit()">{{audienceOpen()?'Disconnect':'Audience'}}</button><button (click)="end.emit()">End</button>
86263
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: PresenterControlsComponent, isStandalone: true, selector: "pptx-presenter-controls", inputs: { snapshot: { classPropertyName: "snapshot", publicName: "snapshot", isSignal: true, isRequired: true, transformFunction: null }, audienceOpen: { classPropertyName: "audienceOpen", publicName: "audienceOpen", isSignal: true, isRequired: false, transformFunction: null }, slides: { classPropertyName: "slides", publicName: "slides", isSignal: true, isRequired: true, transformFunction: null }, current: { classPropertyName: "current", publicName: "current", isSignal: true, isRequired: true, transformFunction: null }, canvasSize: { classPropertyName: "canvasSize", publicName: "canvasSize", isSignal: true, isRequired: true, transformFunction: null }, mediaDataUrls: { classPropertyName: "mediaDataUrls", publicName: "mediaDataUrls", isSignal: true, isRequired: true, transformFunction: null }, templateElements: { classPropertyName: "templateElements", publicName: "templateElements", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { patch: "patch", navigate: "navigate", audience: "audience", swapDisplays: "swapDisplays", end: "end" }, ngImport: i0, template: `
86264
+ <div [class]="classes.strip" data-pptx-presenter-toolbar>
86265
+ @for (slot of slots(); track slot.id) {
86266
+ @if (slot.control.kind === 'divider' || slot.control.kind === 'spacer') {
86267
+ <span
86268
+ [attr.data-pptx-presenter-control]="slot.id"
86269
+ [class]="slot.control.kind === 'divider' ? classes.divider : classes.spacer"
86270
+ ></span>
86271
+ } @else {
86272
+ <button
86273
+ type="button"
86274
+ [attr.data-pptx-presenter-control]="slot.id"
86275
+ [class]="slot.active ? classes.controlActive : classes.control"
86276
+ [disabled]="slot.disabled"
86277
+ [attr.aria-pressed]="slot.pressed"
86278
+ [attr.aria-label]="slot.labelKey ? (slot.labelKey | translate) : null"
86279
+ [title]="slot.labelKey ? (slot.labelKey | translate) : ''"
86280
+ (click)="activate(slot)"
86281
+ >
86282
+ <pptx-presenter-console-icon [name]="slot.iconName" />
86283
+ @if (slot.control.glyph) {
86284
+ <span aria-hidden="true">{{ slot.control.glyph }}</span>
86285
+ }
86286
+ </button>
86287
+ }
86288
+ }
84280
86289
  </div>
84281
- @if(showSlides()){
84282
- <div class="grid"><header><div><small>Slide navigator</small><h2>See all slides</h2></div><button (click)="showSlides.set(false)">Close</button></header><main>
84283
- @for(slide of slides();track slide.id;let index=$index){<button class="tile" [class.current]="index===current()" [class.hidden]="slide.hidden" (click)="select(index)"><div class="preview" [style.height.px]="canvasSize().height*(200/canvasSize().width)"><pptx-slide-canvas [slide]="slide" [canvasSize]="canvasSize()" [mediaDataUrls]="mediaDataUrls()" [zoom]="200/canvasSize().width" [interactive]="false" /></div><span>{{index+1}}{{slide.hidden?' - hidden':''}}</span></button>}
84284
- </main></div>
86290
+ @if (showSlides()) {
86291
+ <pptx-presenter-slide-navigator
86292
+ [slides]="slides()"
86293
+ [current]="current()"
86294
+ [canvasSize]="canvasSize()"
86295
+ [mediaDataUrls]="mediaDataUrls()"
86296
+ [templateElements]="templateElements()"
86297
+ (select)="select($event)"
86298
+ (close)="showSlides.set(false)"
86299
+ />
84285
86300
  }
84286
- `, isInline: true, styles: [":host{display:block}.strip{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:8px 12px;background:#020617;border-bottom:1px solid #ffffff1a}.strip button,.grid button{border:0;border-radius:5px;padding:7px 10px;background:#ffffff12;color:#e2e8f0;cursor:pointer}.strip button:hover,.strip .active{background:#38bdf8;color:#082f49}.strip span{flex:1}.grid{position:fixed;inset:0;z-index:120;display:flex;flex-direction:column;background:#020617fa;color:#f8fafc}.grid header{display:flex;align-items:center;justify-content:space-between;padding:18px 24px;border-bottom:1px solid #ffffff1a}.grid main{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:20px;padding:24px;overflow:auto}.tile{text-align:left}.tile.current{outline:2px solid #38bdf8}.tile.hidden{opacity:.45}.preview{width:200px;overflow:hidden}.tile span{display:block;margin-top:8px;color:#94a3b8}\n"], dependencies: [{ kind: "component", type: SlideCanvasComponent, selector: "pptx-slide-canvas", inputs: ["slide", "canvasSize", "mediaDataUrls", "zoom", "editable", "showGrid", "showRulers", "rulerUnit", "showGuides", "snapToGrid", "snapToShape", "guideCommand", "spellCheck", "snapToGuides", "autoFit", "transparentBackground", "interactive", "presenting", "selectedIds", "editingId", "editTemplateMode", "templateElements", "aiHighlights", "aiActive", "aiActiveSlideIndex", "aiChangeBatch", "aiPickMode", "drawTool", "drawColor", "drawWidth"], outputs: ["elementSelect", "backgroundClick", "transformStart", "transformUpdate", "contextMenu", "textEditStart", "textCommit", "textInput", "textCancel", "textFormat", "rotateUpdate", "marqueeSelect", "inkStrokeComplete", "eraserHit", "cellCommit", "tableChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
86301
+ `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type: PresenterConsoleIconComponent, selector: "pptx-presenter-console-icon", inputs: ["name"] }, { kind: "component", type: PresenterSlideNavigatorComponent, selector: "pptx-presenter-slide-navigator", inputs: ["slides", "current", "canvasSize", "mediaDataUrls", "templateElements"], outputs: ["select", "close"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
84287
86302
  }
84288
86303
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PresenterControlsComponent, decorators: [{
84289
86304
  type: Component,
84290
- args: [{ selector: 'pptx-presenter-controls', standalone: true, imports: [SlideCanvasComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
84291
- <div class="strip">
84292
- <button (click)="patch.emit({paused:!snapshot().paused})">{{snapshot().paused?'Resume':'Pause'}}</button><button (click)="patch.emit({paused:false,elapsedMs:0})">Reset</button>
84293
- <button (click)="showSlides.set(true)">All slides</button><button (click)="zoom(-1)">Zoom -</button><button (click)="zoom(1)">Zoom +</button><button (click)="patch.emit({zoom:{scale:1,originX:.5,originY:.5}})">Fit</button>
84294
- @for(tool of tools;track tool){<button [class.active]="snapshot().pointer?.tool===tool" (click)="setTool(tool)">{{tool}}</button>}
84295
- <button [class.active]="snapshot().blackout==='black'" (click)="toggleBlank('black')">B</button><button [class.active]="snapshot().blackout==='white'" (click)="toggleBlank('white')">W</button>
84296
- <button [class.active]="snapshot().subtitlesVisible" (click)="patch.emit({subtitlesVisible:!snapshot().subtitlesVisible})">Captions</button><span></span><button (click)="audience.emit()">{{audienceOpen()?'Disconnect':'Audience'}}</button><button (click)="end.emit()">End</button>
86305
+ args: [{ selector: 'pptx-presenter-controls', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [TranslatePipe, PresenterConsoleIconComponent, PresenterSlideNavigatorComponent], template: `
86306
+ <div [class]="classes.strip" data-pptx-presenter-toolbar>
86307
+ @for (slot of slots(); track slot.id) {
86308
+ @if (slot.control.kind === 'divider' || slot.control.kind === 'spacer') {
86309
+ <span
86310
+ [attr.data-pptx-presenter-control]="slot.id"
86311
+ [class]="slot.control.kind === 'divider' ? classes.divider : classes.spacer"
86312
+ ></span>
86313
+ } @else {
86314
+ <button
86315
+ type="button"
86316
+ [attr.data-pptx-presenter-control]="slot.id"
86317
+ [class]="slot.active ? classes.controlActive : classes.control"
86318
+ [disabled]="slot.disabled"
86319
+ [attr.aria-pressed]="slot.pressed"
86320
+ [attr.aria-label]="slot.labelKey ? (slot.labelKey | translate) : null"
86321
+ [title]="slot.labelKey ? (slot.labelKey | translate) : ''"
86322
+ (click)="activate(slot)"
86323
+ >
86324
+ <pptx-presenter-console-icon [name]="slot.iconName" />
86325
+ @if (slot.control.glyph) {
86326
+ <span aria-hidden="true">{{ slot.control.glyph }}</span>
86327
+ }
86328
+ </button>
86329
+ }
86330
+ }
84297
86331
  </div>
84298
- @if(showSlides()){
84299
- <div class="grid"><header><div><small>Slide navigator</small><h2>See all slides</h2></div><button (click)="showSlides.set(false)">Close</button></header><main>
84300
- @for(slide of slides();track slide.id;let index=$index){<button class="tile" [class.current]="index===current()" [class.hidden]="slide.hidden" (click)="select(index)"><div class="preview" [style.height.px]="canvasSize().height*(200/canvasSize().width)"><pptx-slide-canvas [slide]="slide" [canvasSize]="canvasSize()" [mediaDataUrls]="mediaDataUrls()" [zoom]="200/canvasSize().width" [interactive]="false" /></div><span>{{index+1}}{{slide.hidden?' - hidden':''}}</span></button>}
84301
- </main></div>
86332
+ @if (showSlides()) {
86333
+ <pptx-presenter-slide-navigator
86334
+ [slides]="slides()"
86335
+ [current]="current()"
86336
+ [canvasSize]="canvasSize()"
86337
+ [mediaDataUrls]="mediaDataUrls()"
86338
+ [templateElements]="templateElements()"
86339
+ (select)="select($event)"
86340
+ (close)="showSlides.set(false)"
86341
+ />
84302
86342
  }
84303
- `, styles: [":host{display:block}.strip{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:8px 12px;background:#020617;border-bottom:1px solid #ffffff1a}.strip button,.grid button{border:0;border-radius:5px;padding:7px 10px;background:#ffffff12;color:#e2e8f0;cursor:pointer}.strip button:hover,.strip .active{background:#38bdf8;color:#082f49}.strip span{flex:1}.grid{position:fixed;inset:0;z-index:120;display:flex;flex-direction:column;background:#020617fa;color:#f8fafc}.grid header{display:flex;align-items:center;justify-content:space-between;padding:18px 24px;border-bottom:1px solid #ffffff1a}.grid main{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:20px;padding:24px;overflow:auto}.tile{text-align:left}.tile.current{outline:2px solid #38bdf8}.tile.hidden{opacity:.45}.preview{width:200px;overflow:hidden}.tile span{display:block;margin-top:8px;color:#94a3b8}\n"] }]
84304
- }], propDecorators: { snapshot: [{ type: i0.Input, args: [{ isSignal: true, alias: "snapshot", required: true }] }], audienceOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "audienceOpen", required: false }] }], slides: [{ type: i0.Input, args: [{ isSignal: true, alias: "slides", required: true }] }], current: [{ type: i0.Input, args: [{ isSignal: true, alias: "current", required: true }] }], canvasSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "canvasSize", required: true }] }], mediaDataUrls: [{ type: i0.Input, args: [{ isSignal: true, alias: "mediaDataUrls", required: true }] }], patch: [{ type: i0.Output, args: ["patch"] }], navigate: [{ type: i0.Output, args: ["navigate"] }], audience: [{ type: i0.Output, args: ["audience"] }], end: [{ type: i0.Output, args: ["end"] }] } });
86343
+ `, styles: [":host{display:block}\n"] }]
86344
+ }], propDecorators: { snapshot: [{ type: i0.Input, args: [{ isSignal: true, alias: "snapshot", required: true }] }], audienceOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "audienceOpen", required: false }] }], slides: [{ type: i0.Input, args: [{ isSignal: true, alias: "slides", required: true }] }], current: [{ type: i0.Input, args: [{ isSignal: true, alias: "current", required: true }] }], canvasSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "canvasSize", required: true }] }], mediaDataUrls: [{ type: i0.Input, args: [{ isSignal: true, alias: "mediaDataUrls", required: true }] }], templateElements: [{ type: i0.Input, args: [{ isSignal: true, alias: "templateElements", required: false }] }], patch: [{ type: i0.Output, args: ["patch"] }], navigate: [{ type: i0.Output, args: ["navigate"] }], audience: [{ type: i0.Output, args: ["audience"] }], swapDisplays: [{ type: i0.Output, args: ["swapDisplays"] }], end: [{ type: i0.Output, args: ["end"] }] } });
84305
86345
 
84306
86346
  /** Clock tick interval (ms) for the current-time / elapsed display. */
84307
86347
  const CLOCK_TICK_MS = 1000;
@@ -84400,8 +86440,41 @@ class PresenterViewComponent {
84400
86440
  ...(ngDevMode ? [{ debugName: "elapsedMs" }] : /* istanbul ignore next */ []));
84401
86441
  elapsedLabel = computed(() => formatElapsed(this.elapsedMs()), /* @ts-ignore */
84402
86442
  ...(ngDevMode ? [{ debugName: "elapsedLabel" }] : /* istanbul ignore next */ []));
84403
- timerProgress = computed(() => computeTimerProgress(this.elapsedMs()), /* @ts-ignore */
86443
+ timerProgress = computed(() => presenterTimerProgress(this.elapsedMs()), /* @ts-ignore */
84404
86444
  ...(ngDevMode ? [{ debugName: "timerProgress" }] : /* istanbul ignore next */ []));
86445
+ /**
86446
+ * Whether Previous / Next are unusable, straight from the shared rule.
86447
+ *
86448
+ * Next is NEVER disabled: PowerPoint's console advances from the last slide
86449
+ * to the end-of-show screen and then out of the show, so gating it on
86450
+ * `index >= slides.length - 1` (as this component used to) strands the
86451
+ * presenter on the final slide with no way to finish, and the audience
86452
+ * display never closes either.
86453
+ */
86454
+ prevDisabled = computed(() => presenterPrevDisabled(this.currentSlideIndex()), /* @ts-ignore */
86455
+ ...(ngDevMode ? [{ debugName: "prevDisabled" }] : /* istanbul ignore next */ []));
86456
+ nextDisabled = computed(() => presenterNextDisabled(), /* @ts-ignore */
86457
+ ...(ngDevMode ? [{ debugName: "nextDisabled" }] : /* istanbul ignore next */ []));
86458
+ /**
86459
+ * The console zoom, applied to the current-slide pane.
86460
+ *
86461
+ * The pane used to hard-code `[zoom]="1"`, so the strip's zoom buttons
86462
+ * mutated the snapshot (and the audience display honoured it) while the
86463
+ * presenter's own pane never moved a pixel. Scaling the STAGE wrapper rather
86464
+ * than the canvas mirrors React's `PresenterSlideFrame`: the canvas keeps
86465
+ * auto-fitting its (layout-measured, transform-immune) viewport, and the
86466
+ * zoom rides on top of that fit about the snapshot's focal point.
86467
+ */
86468
+ previewStageStyle = computed(() => {
86469
+ const zoom = this.presenterWindow.snapshot().zoom;
86470
+ const originX = (zoom?.originX ?? 0.5) * 100;
86471
+ const originY = (zoom?.originY ?? 0.5) * 100;
86472
+ return {
86473
+ transform: `scale(${zoom?.scale ?? 1})`,
86474
+ 'transform-origin': `${originX}% ${originY}%`,
86475
+ };
86476
+ }, /* @ts-ignore */
86477
+ ...(ngDevMode ? [{ debugName: "previewStageStyle" }] : /* istanbul ignore next */ []));
84405
86478
  timerPercent = computed(() => this.timerProgress().percent, /* @ts-ignore */
84406
86479
  ...(ngDevMode ? [{ debugName: "timerPercent" }] : /* istanbul ignore next */ []));
84407
86480
  progressValue = computed(() => Math.round(this.timerProgress().percent), /* @ts-ignore */
@@ -84445,6 +86518,15 @@ class PresenterViewComponent {
84445
86518
  this.openAudienceWindow.emit();
84446
86519
  }
84447
86520
  }
86521
+ /**
86522
+ * Move the console onto the audience's screen and vice versa (PowerPoint's
86523
+ * "Swap Displays"). Best-effort: the underlying Window Management API is not
86524
+ * universally available, so a `false` result is not an error, it is a browser
86525
+ * that will not move windows for us.
86526
+ */
86527
+ onSwapDisplays() {
86528
+ void this.presenterWindow.swapDisplays();
86529
+ }
84448
86530
  // ------------------------------------------------------------------
84449
86531
  // Helpers
84450
86532
  // ------------------------------------------------------------------
@@ -84459,11 +86541,11 @@ class PresenterViewComponent {
84459
86541
  return { ...slide, elements: [...template, ...slide.elements] };
84460
86542
  }
84461
86543
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PresenterViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
84462
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: PresenterViewComponent, isStandalone: true, selector: "pptx-presenter-view", inputs: { slides: { classPropertyName: "slides", publicName: "slides", isSignal: true, isRequired: true, transformFunction: null }, currentSlideIndex: { classPropertyName: "currentSlideIndex", publicName: "currentSlideIndex", isSignal: true, isRequired: true, transformFunction: null }, canvasSize: { classPropertyName: "canvasSize", publicName: "canvasSize", isSignal: true, isRequired: true, transformFunction: null }, templateElements: { classPropertyName: "templateElements", publicName: "templateElements", isSignal: true, isRequired: false, transformFunction: null }, mediaDataUrls: { classPropertyName: "mediaDataUrls", publicName: "mediaDataUrls", isSignal: true, isRequired: false, transformFunction: null }, presentationStartTime: { classPropertyName: "presentationStartTime", publicName: "presentationStartTime", isSignal: true, isRequired: false, transformFunction: null }, isAudienceWindowOpen: { classPropertyName: "isAudienceWindowOpen", publicName: "isAudienceWindowOpen", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { movePresentationSlide: "movePresentationSlide", exit: "exit", openAudienceWindow: "openAudienceWindow", closeAudienceWindow: "closeAudienceWindow", navigateToSlide: "navigateToSlide" }, ngImport: i0, template: "@if (currentSlide(); as current) {\n\t<pptx-presenter-controls\n\t\t[snapshot]=\"presenterWindow.snapshot()\"\n\t\t[audienceOpen]=\"isAudienceWindowOpen()\"\n\t\t[slides]=\"slides()\"\n\t\t[current]=\"currentSlideIndex()\"\n\t\t[canvasSize]=\"canvasSize()\"\n\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t(patch)=\"presenterWindow.updateSnapshot($event)\"\n\t\t(navigate)=\"navigateToSlide.emit($event)\"\n\t\t(audience)=\"onToggleAudienceWindow()\"\n\t\t(end)=\"exit.emit()\"\n\t/>\n\t<div class=\"pptx-ng-presenter-body\">\n\t\t<!-- Current slide (\u224870%) -->\n\t\t<div\n\t\t\tclass=\"pptx-ng-presenter-current\"\n\t\t\t[class.pptx-ng-presenter-current--advances]=\"paneAdvancesOnClick()\"\n\t\t\trole=\"presentation\"\n\t\t\tdata-pptx-presenter-slide\n\t\t\t(click)=\"onSlidePaneClick()\"\n\t\t>\n\t\t\t<div class=\"pptx-ng-presenter-preview-stage\">\n\t\t\t\t<pptx-slide-canvas\n\t\t\t\t\t[slide]=\"currentPreviewSlide()\"\n\t\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zoom]=\"1\"\n\t\t\t\t\t[interactive]=\"false\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<div class=\"pptx-ng-presenter-slide-badge\">{{ slideBadge() }}</div>\n\t\t</div>\n\n\t\t<!-- Controls (\u224830%) -->\n\t\t<div class=\"pptx-ng-presenter-side\">\n\t\t\t<!-- Header: clock + elapsed + window/exit -->\n\t\t\t<div class=\"pptx-ng-presenter-header\">\n\t\t\t\t<div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-label\">\n\t\t\t\t\t\t{{ 'pptx.presenter.currentTime' | translate }}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-clock\">{{ clockLabel() }}</div>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-label\">{{ 'pptx.presenter.elapsed' | translate }}</div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-clock pptx-ng-presenter-elapsed\">\n\t\t\t\t\t\t{{ elapsedLabel() }}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div style=\"display: flex; align-items: center; gap: 0.25rem\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"pptx-ng-presenter-iconbtn\"\n\t\t\t\t\t\t(click)=\"onToggleAudienceWindow()\"\n\t\t\t\t\t\t[attr.aria-label]=\"\n\t\t\t\t\t\t\t(isAudienceWindowOpen()\n\t\t\t\t\t\t\t\t? 'pptx.presenter.closeAudienceWindow'\n\t\t\t\t\t\t\t\t: 'pptx.presenter.openAudienceWindow'\n\t\t\t\t\t\t\t) | translate\n\t\t\t\t\t\t\"\n\t\t\t\t\t\t[title]=\"\n\t\t\t\t\t\t\t(isAudienceWindowOpen()\n\t\t\t\t\t\t\t\t? 'pptx.presenter.closeAudienceWindow'\n\t\t\t\t\t\t\t\t: 'pptx.presenter.openAudienceWindow'\n\t\t\t\t\t\t\t) | translate\n\t\t\t\t\t\t\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{{ isAudienceWindowOpen() ? '\u25A3' : '\u25A1' }}\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"pptx-ng-presenter-iconbtn\"\n\t\t\t\t\t\t(click)=\"exit.emit()\"\n\t\t\t\t\t\t[attr.aria-label]=\"'pptx.presenter.endPresentation' | translate\"\n\t\t\t\t\t\t[title]=\"'pptx.presenter.endPresentation' | translate\"\n\t\t\t\t\t>\n\t\t\t\t\t\t&#x2715;\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Navigation -->\n\t\t\t<div class=\"pptx-ng-presenter-nav\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-presenter-navbtn\"\n\t\t\t\t\t(click)=\"movePresentationSlide.emit(-1)\"\n\t\t\t\t\t[disabled]=\"currentSlideIndex() === 0\"\n\t\t\t\t\t[title]=\"'pptx.presenter.previousSlide' | translate\"\n\t\t\t\t>\n\t\t\t\t\t&#x2039; {{ 'pptx.presenter.prev' | translate }}\n\t\t\t\t</button>\n\t\t\t\t<span class=\"pptx-ng-presenter-counter\">{{ counterLabel() }}</span>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-presenter-navbtn\"\n\t\t\t\t\t(click)=\"movePresentationSlide.emit(1)\"\n\t\t\t\t\t[disabled]=\"currentSlideIndex() >= slides().length - 1\"\n\t\t\t\t\t[title]=\"'pptx.presenter.nextSlide' | translate\"\n\t\t\t\t>\n\t\t\t\t\t{{ 'pptx.presenter.next' | translate }} &#x203A;\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t<!-- Next slide preview -->\n\t\t\t<div class=\"pptx-ng-presenter-next\">\n\t\t\t\t<div class=\"pptx-ng-presenter-label\" style=\"margin-bottom: 0.5rem\">\n\t\t\t\t\t{{ 'pptx.presenter.nextSlidePreview' | translate }}\n\t\t\t\t</div>\n\t\t\t\t@if (nextPreviewSlide(); as next) {\n\t\t\t\t\t<pptx-slide-canvas\n\t\t\t\t\t\t[slide]=\"next\"\n\t\t\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t\t[zoom]=\"1\"\n\t\t\t\t\t\t[interactive]=\"false\"\n\t\t\t\t\t/>\n\t\t\t\t} @else {\n\t\t\t\t\t<div class=\"pptx-ng-presenter-next-empty\">\n\t\t\t\t\t\t{{ 'pptx.presenter.endOfPresentation' | translate }}\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\n\t\t\t<!-- Speaker notes -->\n\t\t\t<div class=\"pptx-ng-presenter-notes\">\n\t\t\t\t<div class=\"pptx-ng-presenter-notes-head\">\n\t\t\t\t\t<div class=\"pptx-ng-presenter-label\">\n\t\t\t\t\t\t{{ 'pptx.presenter.speakerNotes' | translate }}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-notes-size\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclass=\"pptx-ng-presenter-iconbtn\"\n\t\t\t\t\t\t\t(click)=\"decreaseNotesFontSize()\"\n\t\t\t\t\t\t\t[disabled]=\"notesFontSize() <= NOTES_FONT_SIZE_MIN\"\n\t\t\t\t\t\t\t[attr.aria-label]=\"'pptx.presenter.decreaseFontSize' | translate\"\n\t\t\t\t\t\t\t[title]=\"'pptx.presenter.decreaseFontSize' | translate\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t&#x2212;\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<span class=\"pptx-ng-presenter-notes-size-value\">{{ notesFontSize() }}px</span>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclass=\"pptx-ng-presenter-iconbtn\"\n\t\t\t\t\t\t\t(click)=\"increaseNotesFontSize()\"\n\t\t\t\t\t\t\t[disabled]=\"notesFontSize() >= NOTES_FONT_SIZE_MAX\"\n\t\t\t\t\t\t\t[attr.aria-label]=\"'pptx.presenter.increaseFontSize' | translate\"\n\t\t\t\t\t\t\t[title]=\"'pptx.presenter.increaseFontSize' | translate\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t&#x2b;\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"pptx-ng-presenter-notes-body\" [ngStyle]=\"notesBodyStyle()\">\n\t\t\t\t\t@if (notes().hasRichNotes) {\n\t\t\t\t\t\t@for (seg of notes().segments; track seg.key) {\n\t\t\t\t\t\t\t@if (seg.isBreak) {\n\t\t\t\t\t\t\t\t<br />\n\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t<span [ngStyle]=\"seg.style\">{{ seg.text }}</span>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} @else if (notes().hasAnyNotes) {\n\t\t\t\t\t\t{{ notes().plainText }}\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<span class=\"pptx-ng-presenter-notes-empty\">\n\t\t\t\t\t\t\t{{ 'pptx.presenter.noNotes' | translate }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n\n\t<!-- Timer progress bar -->\n\t<div\n\t\tclass=\"pptx-ng-presenter-progress\"\n\t\trole=\"progressbar\"\n\t\t[attr.aria-valuenow]=\"progressValue()\"\n\t\taria-valuemin=\"0\"\n\t\taria-valuemax=\"100\"\n\t\t[attr.aria-label]=\"'pptx.presenter.timerProgress' | translate\"\n\t>\n\t\t<div class=\"pptx-ng-presenter-progress-fill\" [style.width.%]=\"timerPercent()\"></div>\n\t</div>\n} @else {\n\t<div class=\"pptx-ng-presenter-empty\">{{ 'pptx.presenter.noSlides' | translate }}</div>\n}\n", styles: [":host{position:absolute;inset:0;z-index:50;display:flex;flex-direction:column;background:var(--pptx-card, #0b0b0c);color:var(--pptx-foreground, #f5f5f5);font-family:system-ui,sans-serif}.pptx-ng-presenter-body{display:flex;flex:1 1 auto;min-height:0}.pptx-ng-presenter-current--advances{cursor:pointer}.pptx-ng-presenter-current{flex:7 1 0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#000;padding:1rem;min-width:0}.pptx-ng-presenter-preview-stage{width:100%;max-width:100%;min-height:0}.pptx-ng-presenter-slide-badge{margin-top:.5rem;font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:.75rem;color:#ffffff80;-webkit-user-select:none;user-select:none}.pptx-ng-presenter-side{flex:3 1 0;display:flex;flex-direction:column;background:var(--pptx-background, #18181b);border-left:1px solid var(--pptx-border, rgba(255, 255, 255, .12));min-width:260px;max-width:440px}.pptx-ng-presenter-header,.pptx-ng-presenter-nav,.pptx-ng-presenter-next{padding:.5rem 1rem;border-bottom:1px solid var(--pptx-border, rgba(255, 255, 255, .12))}.pptx-ng-presenter-header{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.pptx-ng-presenter-label{font-size:.625rem;text-transform:uppercase;letter-spacing:.06em;color:var(--pptx-muted-foreground, rgba(255, 255, 255, .55))}.pptx-ng-presenter-clock{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.125rem}.pptx-ng-presenter-elapsed{color:var(--pptx-primary, #6ea8fe)}.pptx-ng-presenter-iconbtn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;border-radius:6px;background:transparent;color:var(--pptx-foreground, rgba(255, 255, 255, .7));cursor:pointer;font-size:1rem;line-height:1}.pptx-ng-presenter-iconbtn:hover{background:var(--pptx-secondary, rgba(255, 255, 255, .1));color:#fff}.pptx-ng-presenter-iconbtn:disabled{opacity:.3;cursor:not-allowed}.pptx-ng-presenter-nav{display:flex;align-items:center;justify-content:space-between}.pptx-ng-presenter-navbtn{display:inline-flex;align-items:center;gap:.375rem;padding:.375rem .75rem;border:none;border-radius:6px;background:var(--pptx-secondary, rgba(255, 255, 255, .08));color:var(--pptx-foreground, #f5f5f5);cursor:pointer;font-size:.75rem}.pptx-ng-presenter-navbtn:hover:not(:disabled){background:#ffffff29}.pptx-ng-presenter-navbtn:disabled{opacity:.4;cursor:not-allowed}.pptx-ng-presenter-counter{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:.875rem}.pptx-ng-presenter-next-empty{display:flex;align-items:center;justify-content:center;height:4rem;border:1px solid var(--pptx-border, rgba(255, 255, 255, .12));border-radius:6px;background:var(--pptx-muted, rgba(255, 255, 255, .04));font-size:.75rem;font-style:italic;color:#ffffff80}.pptx-ng-presenter-notes{flex:1 1 auto;display:flex;flex-direction:column;min-height:0;padding:.75rem 1rem}.pptx-ng-presenter-notes-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:.5rem}.pptx-ng-presenter-notes-size{display:flex;align-items:center;gap:.25rem}.pptx-ng-presenter-notes-size-value{min-width:28px;text-align:center;font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:.625rem;color:var(--pptx-muted-foreground, rgba(255, 255, 255, .55));-webkit-user-select:none;user-select:none}.pptx-ng-presenter-notes-body{flex:1 1 auto;overflow-y:auto;border:1px solid var(--pptx-border, rgba(255, 255, 255, .12));border-radius:6px;background:var(--pptx-muted, rgba(255, 255, 255, .04));padding:.5rem .75rem;white-space:pre-wrap;line-height:1.5}.pptx-ng-presenter-notes-empty{font-style:italic;color:#ffffff80}.pptx-ng-presenter-progress{height:6px;width:100%;background:#ffffff1f;flex:0 0 auto}.pptx-ng-presenter-progress-fill{height:100%;background:#6ea8fe;transition:width 1s linear}.pptx-ng-presenter-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#fff9}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: SlideCanvasComponent, selector: "pptx-slide-canvas", inputs: ["slide", "canvasSize", "mediaDataUrls", "zoom", "editable", "showGrid", "showRulers", "rulerUnit", "showGuides", "snapToGrid", "snapToShape", "guideCommand", "spellCheck", "snapToGuides", "autoFit", "transparentBackground", "interactive", "presenting", "selectedIds", "editingId", "editTemplateMode", "templateElements", "aiHighlights", "aiActive", "aiActiveSlideIndex", "aiChangeBatch", "aiPickMode", "drawTool", "drawColor", "drawWidth"], outputs: ["elementSelect", "backgroundClick", "transformStart", "transformUpdate", "contextMenu", "textEditStart", "textCommit", "textInput", "textCancel", "textFormat", "rotateUpdate", "marqueeSelect", "inkStrokeComplete", "eraserHit", "cellCommit", "tableChange"] }, { kind: "component", type: PresenterControlsComponent, selector: "pptx-presenter-controls", inputs: ["snapshot", "audienceOpen", "slides", "current", "canvasSize", "mediaDataUrls"], outputs: ["patch", "navigate", "audience", "end"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
86544
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: PresenterViewComponent, isStandalone: true, selector: "pptx-presenter-view", inputs: { slides: { classPropertyName: "slides", publicName: "slides", isSignal: true, isRequired: true, transformFunction: null }, currentSlideIndex: { classPropertyName: "currentSlideIndex", publicName: "currentSlideIndex", isSignal: true, isRequired: true, transformFunction: null }, canvasSize: { classPropertyName: "canvasSize", publicName: "canvasSize", isSignal: true, isRequired: true, transformFunction: null }, templateElements: { classPropertyName: "templateElements", publicName: "templateElements", isSignal: true, isRequired: false, transformFunction: null }, mediaDataUrls: { classPropertyName: "mediaDataUrls", publicName: "mediaDataUrls", isSignal: true, isRequired: false, transformFunction: null }, presentationStartTime: { classPropertyName: "presentationStartTime", publicName: "presentationStartTime", isSignal: true, isRequired: false, transformFunction: null }, isAudienceWindowOpen: { classPropertyName: "isAudienceWindowOpen", publicName: "isAudienceWindowOpen", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { movePresentationSlide: "movePresentationSlide", exit: "exit", openAudienceWindow: "openAudienceWindow", closeAudienceWindow: "closeAudienceWindow", navigateToSlide: "navigateToSlide" }, ngImport: i0, template: "@if (currentSlide(); as current) {\n\t<pptx-presenter-controls\n\t\t[snapshot]=\"presenterWindow.snapshot()\"\n\t\t[audienceOpen]=\"isAudienceWindowOpen()\"\n\t\t[slides]=\"slides()\"\n\t\t[current]=\"currentSlideIndex()\"\n\t\t[canvasSize]=\"canvasSize()\"\n\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t[templateElements]=\"templateElements()\"\n\t\t(patch)=\"presenterWindow.updateSnapshot($event)\"\n\t\t(navigate)=\"navigateToSlide.emit($event)\"\n\t\t(audience)=\"onToggleAudienceWindow()\"\n\t\t(swapDisplays)=\"onSwapDisplays()\"\n\t\t(end)=\"exit.emit()\"\n\t/>\n\t<div class=\"pptx-ng-presenter-body\">\n\t\t<!-- Current slide (\u224870%) -->\n\t\t<div\n\t\t\tclass=\"pptx-ng-presenter-current\"\n\t\t\t[class.pptx-ng-presenter-current--advances]=\"paneAdvancesOnClick()\"\n\t\t\trole=\"presentation\"\n\t\t\tdata-pptx-presenter-slide\n\t\t\t(click)=\"onSlidePaneClick()\"\n\t\t>\n\t\t\t<div class=\"pptx-ng-presenter-preview-stage\" [ngStyle]=\"previewStageStyle()\">\n\t\t\t\t<pptx-slide-canvas\n\t\t\t\t\t[slide]=\"currentPreviewSlide()\"\n\t\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zoom]=\"1\"\n\t\t\t\t\t[interactive]=\"false\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<div class=\"pptx-ng-presenter-slide-badge\">{{ slideBadge() }}</div>\n\t\t</div>\n\n\t\t<!-- Controls (\u224830%) -->\n\t\t<div class=\"pptx-ng-presenter-side\">\n\t\t\t<!-- Header: clock + elapsed + window/exit -->\n\t\t\t<div class=\"pptx-ng-presenter-header\">\n\t\t\t\t<div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-label\">\n\t\t\t\t\t\t{{ 'pptx.presenter.currentTime' | translate }}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-clock\">{{ clockLabel() }}</div>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-label\">{{ 'pptx.presenter.elapsed' | translate }}</div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-clock pptx-ng-presenter-elapsed\">\n\t\t\t\t\t\t{{ elapsedLabel() }}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div style=\"display: flex; align-items: center; gap: 0.25rem\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"pptx-ng-presenter-iconbtn\"\n\t\t\t\t\t\t(click)=\"onToggleAudienceWindow()\"\n\t\t\t\t\t\t[attr.aria-label]=\"\n\t\t\t\t\t\t\t(isAudienceWindowOpen()\n\t\t\t\t\t\t\t\t? 'pptx.presenter.closeAudienceWindow'\n\t\t\t\t\t\t\t\t: 'pptx.presenter.openAudienceWindow'\n\t\t\t\t\t\t\t) | translate\n\t\t\t\t\t\t\"\n\t\t\t\t\t\t[title]=\"\n\t\t\t\t\t\t\t(isAudienceWindowOpen()\n\t\t\t\t\t\t\t\t? 'pptx.presenter.closeAudienceWindow'\n\t\t\t\t\t\t\t\t: 'pptx.presenter.openAudienceWindow'\n\t\t\t\t\t\t\t) | translate\n\t\t\t\t\t\t\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{{ isAudienceWindowOpen() ? '\u25A3' : '\u25A1' }}\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"pptx-ng-presenter-iconbtn\"\n\t\t\t\t\t\t(click)=\"exit.emit()\"\n\t\t\t\t\t\t[attr.aria-label]=\"'pptx.presenter.endPresentation' | translate\"\n\t\t\t\t\t\t[title]=\"'pptx.presenter.endPresentation' | translate\"\n\t\t\t\t\t>\n\t\t\t\t\t\t&#x2715;\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Navigation -->\n\t\t\t<div class=\"pptx-ng-presenter-nav\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-presenter-navbtn\"\n\t\t\t\t\t(click)=\"movePresentationSlide.emit(-1)\"\n\t\t\t\t\t[disabled]=\"prevDisabled()\"\n\t\t\t\t\tdata-pptx-presenter-control=\"prev\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.presenter.prev' | translate\"\n\t\t\t\t\t[title]=\"'pptx.presenter.prev' | translate\"\n\t\t\t\t>\n\t\t\t\t\t&#x2039; {{ 'pptx.presenter.prev' | translate }}\n\t\t\t\t</button>\n\t\t\t\t<span class=\"pptx-ng-presenter-counter\">{{ counterLabel() }}</span>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-presenter-navbtn\"\n\t\t\t\t\t(click)=\"movePresentationSlide.emit(1)\"\n\t\t\t\t\t[disabled]=\"nextDisabled()\"\n\t\t\t\t\tdata-pptx-presenter-control=\"next\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.presenter.next' | translate\"\n\t\t\t\t\t[title]=\"'pptx.presenter.next' | translate\"\n\t\t\t\t>\n\t\t\t\t\t{{ 'pptx.presenter.next' | translate }} &#x203A;\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t<!-- Next slide preview -->\n\t\t\t<div class=\"pptx-ng-presenter-next\" data-pptx-presenter-next-preview>\n\t\t\t\t<div class=\"pptx-ng-presenter-label\" style=\"margin-bottom: 0.5rem\">\n\t\t\t\t\t{{ 'pptx.presenter.nextSlidePreview' | translate }}\n\t\t\t\t</div>\n\t\t\t\t@if (nextPreviewSlide(); as next) {\n\t\t\t\t\t<pptx-slide-canvas\n\t\t\t\t\t\t[slide]=\"next\"\n\t\t\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t\t[zoom]=\"1\"\n\t\t\t\t\t\t[interactive]=\"false\"\n\t\t\t\t\t/>\n\t\t\t\t} @else {\n\t\t\t\t\t<div class=\"pptx-ng-presenter-next-empty\">\n\t\t\t\t\t\t{{ 'pptx.presenter.endOfPresentation' | translate }}\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\n\t\t\t<!-- Speaker notes -->\n\t\t\t<div class=\"pptx-ng-presenter-notes\" data-pptx-presenter-notes>\n\t\t\t\t<div class=\"pptx-ng-presenter-notes-head\">\n\t\t\t\t\t<div class=\"pptx-ng-presenter-label\">\n\t\t\t\t\t\t{{ 'pptx.presenter.speakerNotes' | translate }}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-notes-size\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclass=\"pptx-ng-presenter-iconbtn\"\n\t\t\t\t\t\t\t(click)=\"decreaseNotesFontSize()\"\n\t\t\t\t\t\t\t[disabled]=\"notesFontSize() <= NOTES_FONT_SIZE_MIN\"\n\t\t\t\t\t\t\tdata-pptx-presenter-control=\"notes-font-decrease\"\n\t\t\t\t\t\t\t[attr.aria-label]=\"'pptx.presenter.decreaseFontSize' | translate\"\n\t\t\t\t\t\t\t[title]=\"'pptx.presenter.decreaseFontSize' | translate\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t&#x2212;\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<span class=\"pptx-ng-presenter-notes-size-value\">{{ notesFontSize() }}px</span>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclass=\"pptx-ng-presenter-iconbtn\"\n\t\t\t\t\t\t\t(click)=\"increaseNotesFontSize()\"\n\t\t\t\t\t\t\t[disabled]=\"notesFontSize() >= NOTES_FONT_SIZE_MAX\"\n\t\t\t\t\t\t\tdata-pptx-presenter-control=\"notes-font-increase\"\n\t\t\t\t\t\t\t[attr.aria-label]=\"'pptx.presenter.increaseFontSize' | translate\"\n\t\t\t\t\t\t\t[title]=\"'pptx.presenter.increaseFontSize' | translate\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t&#x2b;\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"pptx-ng-presenter-notes-body\" [ngStyle]=\"notesBodyStyle()\">\n\t\t\t\t\t@if (notes().hasRichNotes) {\n\t\t\t\t\t\t@for (seg of notes().segments; track seg.key) {\n\t\t\t\t\t\t\t@if (seg.isBreak) {\n\t\t\t\t\t\t\t\t<br />\n\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t<span [ngStyle]=\"seg.style\">{{ seg.text }}</span>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} @else if (notes().hasAnyNotes) {\n\t\t\t\t\t\t{{ notes().plainText }}\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<span class=\"pptx-ng-presenter-notes-empty\">\n\t\t\t\t\t\t\t{{ 'pptx.presenter.noNotes' | translate }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n\n\t<!-- Timer progress bar -->\n\t<div\n\t\tclass=\"pptx-ng-presenter-progress\"\n\t\trole=\"progressbar\"\n\t\t[attr.aria-valuenow]=\"progressValue()\"\n\t\taria-valuemin=\"0\"\n\t\taria-valuemax=\"100\"\n\t\t[attr.aria-label]=\"'pptx.presenter.timerProgress' | translate\"\n\t>\n\t\t<div class=\"pptx-ng-presenter-progress-fill\" [style.width.%]=\"timerPercent()\"></div>\n\t</div>\n} @else {\n\t<div class=\"pptx-ng-presenter-empty\">{{ 'pptx.presenter.noSlides' | translate }}</div>\n}\n", styles: [":host{position:absolute;inset:0;z-index:50;display:flex;flex-direction:column;background:var(--pptx-card, #0b0b0c);color:var(--pptx-foreground, #f5f5f5);font-family:system-ui,sans-serif}.pptx-ng-presenter-body{display:flex;flex:1 1 auto;min-height:0}.pptx-ng-presenter-current--advances{cursor:pointer}.pptx-ng-presenter-current{flex:7 1 0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#000;padding:1rem;min-width:0;overflow:hidden}.pptx-ng-presenter-preview-stage{width:100%;max-width:100%;min-height:0;transition:transform .2s ease}.pptx-ng-presenter-slide-badge{margin-top:.5rem;font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:.75rem;color:#ffffff80;-webkit-user-select:none;user-select:none}.pptx-ng-presenter-side{flex:3 1 0;display:flex;flex-direction:column;background:var(--pptx-background, #18181b);border-left:1px solid var(--pptx-border, rgba(255, 255, 255, .12));min-width:260px;max-width:440px}.pptx-ng-presenter-header,.pptx-ng-presenter-nav,.pptx-ng-presenter-next{padding:.5rem 1rem;border-bottom:1px solid var(--pptx-border, rgba(255, 255, 255, .12))}.pptx-ng-presenter-header{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.pptx-ng-presenter-label{font-size:.625rem;text-transform:uppercase;letter-spacing:.06em;color:var(--pptx-muted-foreground, rgba(255, 255, 255, .55))}.pptx-ng-presenter-clock{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.125rem}.pptx-ng-presenter-elapsed{color:var(--pptx-primary, #6ea8fe)}.pptx-ng-presenter-iconbtn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;border-radius:6px;background:transparent;color:var(--pptx-foreground, rgba(255, 255, 255, .7));cursor:pointer;font-size:1rem;line-height:1}.pptx-ng-presenter-iconbtn:hover{background:var(--pptx-secondary, rgba(255, 255, 255, .1));color:#fff}.pptx-ng-presenter-iconbtn:disabled{opacity:.3;cursor:not-allowed}.pptx-ng-presenter-nav{display:flex;align-items:center;justify-content:space-between}.pptx-ng-presenter-navbtn{display:inline-flex;align-items:center;gap:.375rem;padding:.375rem .75rem;border:none;border-radius:6px;background:var(--pptx-secondary, rgba(255, 255, 255, .08));color:var(--pptx-foreground, #f5f5f5);cursor:pointer;font-size:.75rem}.pptx-ng-presenter-navbtn:hover:not(:disabled){background:#ffffff29}.pptx-ng-presenter-navbtn:disabled{opacity:.4;cursor:not-allowed}.pptx-ng-presenter-counter{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:.875rem}.pptx-ng-presenter-next-empty{display:flex;align-items:center;justify-content:center;height:4rem;border:1px solid var(--pptx-border, rgba(255, 255, 255, .12));border-radius:6px;background:var(--pptx-muted, rgba(255, 255, 255, .04));font-size:.75rem;font-style:italic;color:#ffffff80}.pptx-ng-presenter-notes{flex:1 1 auto;display:flex;flex-direction:column;min-height:0;padding:.75rem 1rem}.pptx-ng-presenter-notes-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:.5rem}.pptx-ng-presenter-notes-size{display:flex;align-items:center;gap:.25rem}.pptx-ng-presenter-notes-size-value{min-width:28px;text-align:center;font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:.625rem;color:var(--pptx-muted-foreground, rgba(255, 255, 255, .55));-webkit-user-select:none;user-select:none}.pptx-ng-presenter-notes-body{flex:1 1 auto;overflow-y:auto;border:1px solid var(--pptx-border, rgba(255, 255, 255, .12));border-radius:6px;background:var(--pptx-muted, rgba(255, 255, 255, .04));padding:.5rem .75rem;white-space:pre-wrap;line-height:1.5}.pptx-ng-presenter-notes-empty{font-style:italic;color:#ffffff80}.pptx-ng-presenter-progress{height:6px;width:100%;background:#ffffff1f;flex:0 0 auto}.pptx-ng-presenter-progress-fill{height:100%;background:#6ea8fe;transition:width 1s linear}.pptx-ng-presenter-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#fff9}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: SlideCanvasComponent, selector: "pptx-slide-canvas", inputs: ["slide", "canvasSize", "mediaDataUrls", "zoom", "editable", "showGrid", "showRulers", "rulerUnit", "showGuides", "snapToGrid", "snapToShape", "guideCommand", "spellCheck", "snapToGuides", "autoFit", "transparentBackground", "interactive", "presenting", "selectedIds", "editingId", "editTemplateMode", "templateElements", "aiHighlights", "aiActive", "aiActiveSlideIndex", "aiChangeBatch", "aiPickMode", "drawTool", "drawColor", "drawWidth"], outputs: ["elementSelect", "backgroundClick", "transformStart", "transformUpdate", "contextMenu", "textEditStart", "textCommit", "textInput", "textCancel", "textFormat", "rotateUpdate", "marqueeSelect", "inkStrokeComplete", "eraserHit", "cellCommit", "tableChange"] }, { kind: "component", type: PresenterControlsComponent, selector: "pptx-presenter-controls", inputs: ["snapshot", "audienceOpen", "slides", "current", "canvasSize", "mediaDataUrls", "templateElements"], outputs: ["patch", "navigate", "audience", "swapDisplays", "end"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
84463
86545
  }
84464
86546
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PresenterViewComponent, decorators: [{
84465
86547
  type: Component,
84466
- args: [{ selector: 'pptx-presenter-view', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgStyle, SlideCanvasComponent, PresenterControlsComponent, TranslatePipe], template: "@if (currentSlide(); as current) {\n\t<pptx-presenter-controls\n\t\t[snapshot]=\"presenterWindow.snapshot()\"\n\t\t[audienceOpen]=\"isAudienceWindowOpen()\"\n\t\t[slides]=\"slides()\"\n\t\t[current]=\"currentSlideIndex()\"\n\t\t[canvasSize]=\"canvasSize()\"\n\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t(patch)=\"presenterWindow.updateSnapshot($event)\"\n\t\t(navigate)=\"navigateToSlide.emit($event)\"\n\t\t(audience)=\"onToggleAudienceWindow()\"\n\t\t(end)=\"exit.emit()\"\n\t/>\n\t<div class=\"pptx-ng-presenter-body\">\n\t\t<!-- Current slide (\u224870%) -->\n\t\t<div\n\t\t\tclass=\"pptx-ng-presenter-current\"\n\t\t\t[class.pptx-ng-presenter-current--advances]=\"paneAdvancesOnClick()\"\n\t\t\trole=\"presentation\"\n\t\t\tdata-pptx-presenter-slide\n\t\t\t(click)=\"onSlidePaneClick()\"\n\t\t>\n\t\t\t<div class=\"pptx-ng-presenter-preview-stage\">\n\t\t\t\t<pptx-slide-canvas\n\t\t\t\t\t[slide]=\"currentPreviewSlide()\"\n\t\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zoom]=\"1\"\n\t\t\t\t\t[interactive]=\"false\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<div class=\"pptx-ng-presenter-slide-badge\">{{ slideBadge() }}</div>\n\t\t</div>\n\n\t\t<!-- Controls (\u224830%) -->\n\t\t<div class=\"pptx-ng-presenter-side\">\n\t\t\t<!-- Header: clock + elapsed + window/exit -->\n\t\t\t<div class=\"pptx-ng-presenter-header\">\n\t\t\t\t<div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-label\">\n\t\t\t\t\t\t{{ 'pptx.presenter.currentTime' | translate }}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-clock\">{{ clockLabel() }}</div>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-label\">{{ 'pptx.presenter.elapsed' | translate }}</div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-clock pptx-ng-presenter-elapsed\">\n\t\t\t\t\t\t{{ elapsedLabel() }}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div style=\"display: flex; align-items: center; gap: 0.25rem\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"pptx-ng-presenter-iconbtn\"\n\t\t\t\t\t\t(click)=\"onToggleAudienceWindow()\"\n\t\t\t\t\t\t[attr.aria-label]=\"\n\t\t\t\t\t\t\t(isAudienceWindowOpen()\n\t\t\t\t\t\t\t\t? 'pptx.presenter.closeAudienceWindow'\n\t\t\t\t\t\t\t\t: 'pptx.presenter.openAudienceWindow'\n\t\t\t\t\t\t\t) | translate\n\t\t\t\t\t\t\"\n\t\t\t\t\t\t[title]=\"\n\t\t\t\t\t\t\t(isAudienceWindowOpen()\n\t\t\t\t\t\t\t\t? 'pptx.presenter.closeAudienceWindow'\n\t\t\t\t\t\t\t\t: 'pptx.presenter.openAudienceWindow'\n\t\t\t\t\t\t\t) | translate\n\t\t\t\t\t\t\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{{ isAudienceWindowOpen() ? '\u25A3' : '\u25A1' }}\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"pptx-ng-presenter-iconbtn\"\n\t\t\t\t\t\t(click)=\"exit.emit()\"\n\t\t\t\t\t\t[attr.aria-label]=\"'pptx.presenter.endPresentation' | translate\"\n\t\t\t\t\t\t[title]=\"'pptx.presenter.endPresentation' | translate\"\n\t\t\t\t\t>\n\t\t\t\t\t\t&#x2715;\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Navigation -->\n\t\t\t<div class=\"pptx-ng-presenter-nav\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-presenter-navbtn\"\n\t\t\t\t\t(click)=\"movePresentationSlide.emit(-1)\"\n\t\t\t\t\t[disabled]=\"currentSlideIndex() === 0\"\n\t\t\t\t\t[title]=\"'pptx.presenter.previousSlide' | translate\"\n\t\t\t\t>\n\t\t\t\t\t&#x2039; {{ 'pptx.presenter.prev' | translate }}\n\t\t\t\t</button>\n\t\t\t\t<span class=\"pptx-ng-presenter-counter\">{{ counterLabel() }}</span>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-presenter-navbtn\"\n\t\t\t\t\t(click)=\"movePresentationSlide.emit(1)\"\n\t\t\t\t\t[disabled]=\"currentSlideIndex() >= slides().length - 1\"\n\t\t\t\t\t[title]=\"'pptx.presenter.nextSlide' | translate\"\n\t\t\t\t>\n\t\t\t\t\t{{ 'pptx.presenter.next' | translate }} &#x203A;\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t<!-- Next slide preview -->\n\t\t\t<div class=\"pptx-ng-presenter-next\">\n\t\t\t\t<div class=\"pptx-ng-presenter-label\" style=\"margin-bottom: 0.5rem\">\n\t\t\t\t\t{{ 'pptx.presenter.nextSlidePreview' | translate }}\n\t\t\t\t</div>\n\t\t\t\t@if (nextPreviewSlide(); as next) {\n\t\t\t\t\t<pptx-slide-canvas\n\t\t\t\t\t\t[slide]=\"next\"\n\t\t\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t\t[zoom]=\"1\"\n\t\t\t\t\t\t[interactive]=\"false\"\n\t\t\t\t\t/>\n\t\t\t\t} @else {\n\t\t\t\t\t<div class=\"pptx-ng-presenter-next-empty\">\n\t\t\t\t\t\t{{ 'pptx.presenter.endOfPresentation' | translate }}\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\n\t\t\t<!-- Speaker notes -->\n\t\t\t<div class=\"pptx-ng-presenter-notes\">\n\t\t\t\t<div class=\"pptx-ng-presenter-notes-head\">\n\t\t\t\t\t<div class=\"pptx-ng-presenter-label\">\n\t\t\t\t\t\t{{ 'pptx.presenter.speakerNotes' | translate }}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-notes-size\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclass=\"pptx-ng-presenter-iconbtn\"\n\t\t\t\t\t\t\t(click)=\"decreaseNotesFontSize()\"\n\t\t\t\t\t\t\t[disabled]=\"notesFontSize() <= NOTES_FONT_SIZE_MIN\"\n\t\t\t\t\t\t\t[attr.aria-label]=\"'pptx.presenter.decreaseFontSize' | translate\"\n\t\t\t\t\t\t\t[title]=\"'pptx.presenter.decreaseFontSize' | translate\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t&#x2212;\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<span class=\"pptx-ng-presenter-notes-size-value\">{{ notesFontSize() }}px</span>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclass=\"pptx-ng-presenter-iconbtn\"\n\t\t\t\t\t\t\t(click)=\"increaseNotesFontSize()\"\n\t\t\t\t\t\t\t[disabled]=\"notesFontSize() >= NOTES_FONT_SIZE_MAX\"\n\t\t\t\t\t\t\t[attr.aria-label]=\"'pptx.presenter.increaseFontSize' | translate\"\n\t\t\t\t\t\t\t[title]=\"'pptx.presenter.increaseFontSize' | translate\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t&#x2b;\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"pptx-ng-presenter-notes-body\" [ngStyle]=\"notesBodyStyle()\">\n\t\t\t\t\t@if (notes().hasRichNotes) {\n\t\t\t\t\t\t@for (seg of notes().segments; track seg.key) {\n\t\t\t\t\t\t\t@if (seg.isBreak) {\n\t\t\t\t\t\t\t\t<br />\n\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t<span [ngStyle]=\"seg.style\">{{ seg.text }}</span>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} @else if (notes().hasAnyNotes) {\n\t\t\t\t\t\t{{ notes().plainText }}\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<span class=\"pptx-ng-presenter-notes-empty\">\n\t\t\t\t\t\t\t{{ 'pptx.presenter.noNotes' | translate }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n\n\t<!-- Timer progress bar -->\n\t<div\n\t\tclass=\"pptx-ng-presenter-progress\"\n\t\trole=\"progressbar\"\n\t\t[attr.aria-valuenow]=\"progressValue()\"\n\t\taria-valuemin=\"0\"\n\t\taria-valuemax=\"100\"\n\t\t[attr.aria-label]=\"'pptx.presenter.timerProgress' | translate\"\n\t>\n\t\t<div class=\"pptx-ng-presenter-progress-fill\" [style.width.%]=\"timerPercent()\"></div>\n\t</div>\n} @else {\n\t<div class=\"pptx-ng-presenter-empty\">{{ 'pptx.presenter.noSlides' | translate }}</div>\n}\n", styles: [":host{position:absolute;inset:0;z-index:50;display:flex;flex-direction:column;background:var(--pptx-card, #0b0b0c);color:var(--pptx-foreground, #f5f5f5);font-family:system-ui,sans-serif}.pptx-ng-presenter-body{display:flex;flex:1 1 auto;min-height:0}.pptx-ng-presenter-current--advances{cursor:pointer}.pptx-ng-presenter-current{flex:7 1 0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#000;padding:1rem;min-width:0}.pptx-ng-presenter-preview-stage{width:100%;max-width:100%;min-height:0}.pptx-ng-presenter-slide-badge{margin-top:.5rem;font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:.75rem;color:#ffffff80;-webkit-user-select:none;user-select:none}.pptx-ng-presenter-side{flex:3 1 0;display:flex;flex-direction:column;background:var(--pptx-background, #18181b);border-left:1px solid var(--pptx-border, rgba(255, 255, 255, .12));min-width:260px;max-width:440px}.pptx-ng-presenter-header,.pptx-ng-presenter-nav,.pptx-ng-presenter-next{padding:.5rem 1rem;border-bottom:1px solid var(--pptx-border, rgba(255, 255, 255, .12))}.pptx-ng-presenter-header{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.pptx-ng-presenter-label{font-size:.625rem;text-transform:uppercase;letter-spacing:.06em;color:var(--pptx-muted-foreground, rgba(255, 255, 255, .55))}.pptx-ng-presenter-clock{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.125rem}.pptx-ng-presenter-elapsed{color:var(--pptx-primary, #6ea8fe)}.pptx-ng-presenter-iconbtn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;border-radius:6px;background:transparent;color:var(--pptx-foreground, rgba(255, 255, 255, .7));cursor:pointer;font-size:1rem;line-height:1}.pptx-ng-presenter-iconbtn:hover{background:var(--pptx-secondary, rgba(255, 255, 255, .1));color:#fff}.pptx-ng-presenter-iconbtn:disabled{opacity:.3;cursor:not-allowed}.pptx-ng-presenter-nav{display:flex;align-items:center;justify-content:space-between}.pptx-ng-presenter-navbtn{display:inline-flex;align-items:center;gap:.375rem;padding:.375rem .75rem;border:none;border-radius:6px;background:var(--pptx-secondary, rgba(255, 255, 255, .08));color:var(--pptx-foreground, #f5f5f5);cursor:pointer;font-size:.75rem}.pptx-ng-presenter-navbtn:hover:not(:disabled){background:#ffffff29}.pptx-ng-presenter-navbtn:disabled{opacity:.4;cursor:not-allowed}.pptx-ng-presenter-counter{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:.875rem}.pptx-ng-presenter-next-empty{display:flex;align-items:center;justify-content:center;height:4rem;border:1px solid var(--pptx-border, rgba(255, 255, 255, .12));border-radius:6px;background:var(--pptx-muted, rgba(255, 255, 255, .04));font-size:.75rem;font-style:italic;color:#ffffff80}.pptx-ng-presenter-notes{flex:1 1 auto;display:flex;flex-direction:column;min-height:0;padding:.75rem 1rem}.pptx-ng-presenter-notes-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:.5rem}.pptx-ng-presenter-notes-size{display:flex;align-items:center;gap:.25rem}.pptx-ng-presenter-notes-size-value{min-width:28px;text-align:center;font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:.625rem;color:var(--pptx-muted-foreground, rgba(255, 255, 255, .55));-webkit-user-select:none;user-select:none}.pptx-ng-presenter-notes-body{flex:1 1 auto;overflow-y:auto;border:1px solid var(--pptx-border, rgba(255, 255, 255, .12));border-radius:6px;background:var(--pptx-muted, rgba(255, 255, 255, .04));padding:.5rem .75rem;white-space:pre-wrap;line-height:1.5}.pptx-ng-presenter-notes-empty{font-style:italic;color:#ffffff80}.pptx-ng-presenter-progress{height:6px;width:100%;background:#ffffff1f;flex:0 0 auto}.pptx-ng-presenter-progress-fill{height:100%;background:#6ea8fe;transition:width 1s linear}.pptx-ng-presenter-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#fff9}\n"] }]
86548
+ args: [{ selector: 'pptx-presenter-view', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgStyle, SlideCanvasComponent, PresenterControlsComponent, TranslatePipe], template: "@if (currentSlide(); as current) {\n\t<pptx-presenter-controls\n\t\t[snapshot]=\"presenterWindow.snapshot()\"\n\t\t[audienceOpen]=\"isAudienceWindowOpen()\"\n\t\t[slides]=\"slides()\"\n\t\t[current]=\"currentSlideIndex()\"\n\t\t[canvasSize]=\"canvasSize()\"\n\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t[templateElements]=\"templateElements()\"\n\t\t(patch)=\"presenterWindow.updateSnapshot($event)\"\n\t\t(navigate)=\"navigateToSlide.emit($event)\"\n\t\t(audience)=\"onToggleAudienceWindow()\"\n\t\t(swapDisplays)=\"onSwapDisplays()\"\n\t\t(end)=\"exit.emit()\"\n\t/>\n\t<div class=\"pptx-ng-presenter-body\">\n\t\t<!-- Current slide (\u224870%) -->\n\t\t<div\n\t\t\tclass=\"pptx-ng-presenter-current\"\n\t\t\t[class.pptx-ng-presenter-current--advances]=\"paneAdvancesOnClick()\"\n\t\t\trole=\"presentation\"\n\t\t\tdata-pptx-presenter-slide\n\t\t\t(click)=\"onSlidePaneClick()\"\n\t\t>\n\t\t\t<div class=\"pptx-ng-presenter-preview-stage\" [ngStyle]=\"previewStageStyle()\">\n\t\t\t\t<pptx-slide-canvas\n\t\t\t\t\t[slide]=\"currentPreviewSlide()\"\n\t\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zoom]=\"1\"\n\t\t\t\t\t[interactive]=\"false\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<div class=\"pptx-ng-presenter-slide-badge\">{{ slideBadge() }}</div>\n\t\t</div>\n\n\t\t<!-- Controls (\u224830%) -->\n\t\t<div class=\"pptx-ng-presenter-side\">\n\t\t\t<!-- Header: clock + elapsed + window/exit -->\n\t\t\t<div class=\"pptx-ng-presenter-header\">\n\t\t\t\t<div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-label\">\n\t\t\t\t\t\t{{ 'pptx.presenter.currentTime' | translate }}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-clock\">{{ clockLabel() }}</div>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-label\">{{ 'pptx.presenter.elapsed' | translate }}</div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-clock pptx-ng-presenter-elapsed\">\n\t\t\t\t\t\t{{ elapsedLabel() }}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div style=\"display: flex; align-items: center; gap: 0.25rem\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"pptx-ng-presenter-iconbtn\"\n\t\t\t\t\t\t(click)=\"onToggleAudienceWindow()\"\n\t\t\t\t\t\t[attr.aria-label]=\"\n\t\t\t\t\t\t\t(isAudienceWindowOpen()\n\t\t\t\t\t\t\t\t? 'pptx.presenter.closeAudienceWindow'\n\t\t\t\t\t\t\t\t: 'pptx.presenter.openAudienceWindow'\n\t\t\t\t\t\t\t) | translate\n\t\t\t\t\t\t\"\n\t\t\t\t\t\t[title]=\"\n\t\t\t\t\t\t\t(isAudienceWindowOpen()\n\t\t\t\t\t\t\t\t? 'pptx.presenter.closeAudienceWindow'\n\t\t\t\t\t\t\t\t: 'pptx.presenter.openAudienceWindow'\n\t\t\t\t\t\t\t) | translate\n\t\t\t\t\t\t\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{{ isAudienceWindowOpen() ? '\u25A3' : '\u25A1' }}\n\t\t\t\t\t</button>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"pptx-ng-presenter-iconbtn\"\n\t\t\t\t\t\t(click)=\"exit.emit()\"\n\t\t\t\t\t\t[attr.aria-label]=\"'pptx.presenter.endPresentation' | translate\"\n\t\t\t\t\t\t[title]=\"'pptx.presenter.endPresentation' | translate\"\n\t\t\t\t\t>\n\t\t\t\t\t\t&#x2715;\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Navigation -->\n\t\t\t<div class=\"pptx-ng-presenter-nav\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-presenter-navbtn\"\n\t\t\t\t\t(click)=\"movePresentationSlide.emit(-1)\"\n\t\t\t\t\t[disabled]=\"prevDisabled()\"\n\t\t\t\t\tdata-pptx-presenter-control=\"prev\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.presenter.prev' | translate\"\n\t\t\t\t\t[title]=\"'pptx.presenter.prev' | translate\"\n\t\t\t\t>\n\t\t\t\t\t&#x2039; {{ 'pptx.presenter.prev' | translate }}\n\t\t\t\t</button>\n\t\t\t\t<span class=\"pptx-ng-presenter-counter\">{{ counterLabel() }}</span>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-presenter-navbtn\"\n\t\t\t\t\t(click)=\"movePresentationSlide.emit(1)\"\n\t\t\t\t\t[disabled]=\"nextDisabled()\"\n\t\t\t\t\tdata-pptx-presenter-control=\"next\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.presenter.next' | translate\"\n\t\t\t\t\t[title]=\"'pptx.presenter.next' | translate\"\n\t\t\t\t>\n\t\t\t\t\t{{ 'pptx.presenter.next' | translate }} &#x203A;\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t<!-- Next slide preview -->\n\t\t\t<div class=\"pptx-ng-presenter-next\" data-pptx-presenter-next-preview>\n\t\t\t\t<div class=\"pptx-ng-presenter-label\" style=\"margin-bottom: 0.5rem\">\n\t\t\t\t\t{{ 'pptx.presenter.nextSlidePreview' | translate }}\n\t\t\t\t</div>\n\t\t\t\t@if (nextPreviewSlide(); as next) {\n\t\t\t\t\t<pptx-slide-canvas\n\t\t\t\t\t\t[slide]=\"next\"\n\t\t\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t\t[zoom]=\"1\"\n\t\t\t\t\t\t[interactive]=\"false\"\n\t\t\t\t\t/>\n\t\t\t\t} @else {\n\t\t\t\t\t<div class=\"pptx-ng-presenter-next-empty\">\n\t\t\t\t\t\t{{ 'pptx.presenter.endOfPresentation' | translate }}\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\n\t\t\t<!-- Speaker notes -->\n\t\t\t<div class=\"pptx-ng-presenter-notes\" data-pptx-presenter-notes>\n\t\t\t\t<div class=\"pptx-ng-presenter-notes-head\">\n\t\t\t\t\t<div class=\"pptx-ng-presenter-label\">\n\t\t\t\t\t\t{{ 'pptx.presenter.speakerNotes' | translate }}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"pptx-ng-presenter-notes-size\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclass=\"pptx-ng-presenter-iconbtn\"\n\t\t\t\t\t\t\t(click)=\"decreaseNotesFontSize()\"\n\t\t\t\t\t\t\t[disabled]=\"notesFontSize() <= NOTES_FONT_SIZE_MIN\"\n\t\t\t\t\t\t\tdata-pptx-presenter-control=\"notes-font-decrease\"\n\t\t\t\t\t\t\t[attr.aria-label]=\"'pptx.presenter.decreaseFontSize' | translate\"\n\t\t\t\t\t\t\t[title]=\"'pptx.presenter.decreaseFontSize' | translate\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t&#x2212;\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<span class=\"pptx-ng-presenter-notes-size-value\">{{ notesFontSize() }}px</span>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclass=\"pptx-ng-presenter-iconbtn\"\n\t\t\t\t\t\t\t(click)=\"increaseNotesFontSize()\"\n\t\t\t\t\t\t\t[disabled]=\"notesFontSize() >= NOTES_FONT_SIZE_MAX\"\n\t\t\t\t\t\t\tdata-pptx-presenter-control=\"notes-font-increase\"\n\t\t\t\t\t\t\t[attr.aria-label]=\"'pptx.presenter.increaseFontSize' | translate\"\n\t\t\t\t\t\t\t[title]=\"'pptx.presenter.increaseFontSize' | translate\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t&#x2b;\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"pptx-ng-presenter-notes-body\" [ngStyle]=\"notesBodyStyle()\">\n\t\t\t\t\t@if (notes().hasRichNotes) {\n\t\t\t\t\t\t@for (seg of notes().segments; track seg.key) {\n\t\t\t\t\t\t\t@if (seg.isBreak) {\n\t\t\t\t\t\t\t\t<br />\n\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t<span [ngStyle]=\"seg.style\">{{ seg.text }}</span>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} @else if (notes().hasAnyNotes) {\n\t\t\t\t\t\t{{ notes().plainText }}\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<span class=\"pptx-ng-presenter-notes-empty\">\n\t\t\t\t\t\t\t{{ 'pptx.presenter.noNotes' | translate }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n\n\t<!-- Timer progress bar -->\n\t<div\n\t\tclass=\"pptx-ng-presenter-progress\"\n\t\trole=\"progressbar\"\n\t\t[attr.aria-valuenow]=\"progressValue()\"\n\t\taria-valuemin=\"0\"\n\t\taria-valuemax=\"100\"\n\t\t[attr.aria-label]=\"'pptx.presenter.timerProgress' | translate\"\n\t>\n\t\t<div class=\"pptx-ng-presenter-progress-fill\" [style.width.%]=\"timerPercent()\"></div>\n\t</div>\n} @else {\n\t<div class=\"pptx-ng-presenter-empty\">{{ 'pptx.presenter.noSlides' | translate }}</div>\n}\n", styles: [":host{position:absolute;inset:0;z-index:50;display:flex;flex-direction:column;background:var(--pptx-card, #0b0b0c);color:var(--pptx-foreground, #f5f5f5);font-family:system-ui,sans-serif}.pptx-ng-presenter-body{display:flex;flex:1 1 auto;min-height:0}.pptx-ng-presenter-current--advances{cursor:pointer}.pptx-ng-presenter-current{flex:7 1 0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#000;padding:1rem;min-width:0;overflow:hidden}.pptx-ng-presenter-preview-stage{width:100%;max-width:100%;min-height:0;transition:transform .2s ease}.pptx-ng-presenter-slide-badge{margin-top:.5rem;font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:.75rem;color:#ffffff80;-webkit-user-select:none;user-select:none}.pptx-ng-presenter-side{flex:3 1 0;display:flex;flex-direction:column;background:var(--pptx-background, #18181b);border-left:1px solid var(--pptx-border, rgba(255, 255, 255, .12));min-width:260px;max-width:440px}.pptx-ng-presenter-header,.pptx-ng-presenter-nav,.pptx-ng-presenter-next{padding:.5rem 1rem;border-bottom:1px solid var(--pptx-border, rgba(255, 255, 255, .12))}.pptx-ng-presenter-header{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.pptx-ng-presenter-label{font-size:.625rem;text-transform:uppercase;letter-spacing:.06em;color:var(--pptx-muted-foreground, rgba(255, 255, 255, .55))}.pptx-ng-presenter-clock{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.125rem}.pptx-ng-presenter-elapsed{color:var(--pptx-primary, #6ea8fe)}.pptx-ng-presenter-iconbtn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;border-radius:6px;background:transparent;color:var(--pptx-foreground, rgba(255, 255, 255, .7));cursor:pointer;font-size:1rem;line-height:1}.pptx-ng-presenter-iconbtn:hover{background:var(--pptx-secondary, rgba(255, 255, 255, .1));color:#fff}.pptx-ng-presenter-iconbtn:disabled{opacity:.3;cursor:not-allowed}.pptx-ng-presenter-nav{display:flex;align-items:center;justify-content:space-between}.pptx-ng-presenter-navbtn{display:inline-flex;align-items:center;gap:.375rem;padding:.375rem .75rem;border:none;border-radius:6px;background:var(--pptx-secondary, rgba(255, 255, 255, .08));color:var(--pptx-foreground, #f5f5f5);cursor:pointer;font-size:.75rem}.pptx-ng-presenter-navbtn:hover:not(:disabled){background:#ffffff29}.pptx-ng-presenter-navbtn:disabled{opacity:.4;cursor:not-allowed}.pptx-ng-presenter-counter{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:.875rem}.pptx-ng-presenter-next-empty{display:flex;align-items:center;justify-content:center;height:4rem;border:1px solid var(--pptx-border, rgba(255, 255, 255, .12));border-radius:6px;background:var(--pptx-muted, rgba(255, 255, 255, .04));font-size:.75rem;font-style:italic;color:#ffffff80}.pptx-ng-presenter-notes{flex:1 1 auto;display:flex;flex-direction:column;min-height:0;padding:.75rem 1rem}.pptx-ng-presenter-notes-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:.5rem}.pptx-ng-presenter-notes-size{display:flex;align-items:center;gap:.25rem}.pptx-ng-presenter-notes-size-value{min-width:28px;text-align:center;font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:.625rem;color:var(--pptx-muted-foreground, rgba(255, 255, 255, .55));-webkit-user-select:none;user-select:none}.pptx-ng-presenter-notes-body{flex:1 1 auto;overflow-y:auto;border:1px solid var(--pptx-border, rgba(255, 255, 255, .12));border-radius:6px;background:var(--pptx-muted, rgba(255, 255, 255, .04));padding:.5rem .75rem;white-space:pre-wrap;line-height:1.5}.pptx-ng-presenter-notes-empty{font-style:italic;color:#ffffff80}.pptx-ng-presenter-progress{height:6px;width:100%;background:#ffffff1f;flex:0 0 auto}.pptx-ng-presenter-progress-fill{height:100%;background:#6ea8fe;transition:width 1s linear}.pptx-ng-presenter-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#fff9}\n"] }]
84467
86549
  }], ctorParameters: () => [], propDecorators: { slides: [{ type: i0.Input, args: [{ isSignal: true, alias: "slides", required: true }] }], currentSlideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentSlideIndex", required: true }] }], canvasSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "canvasSize", required: true }] }], templateElements: [{ type: i0.Input, args: [{ isSignal: true, alias: "templateElements", required: false }] }], mediaDataUrls: [{ type: i0.Input, args: [{ isSignal: true, alias: "mediaDataUrls", required: false }] }], presentationStartTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "presentationStartTime", required: false }] }], isAudienceWindowOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isAudienceWindowOpen", required: false }] }], movePresentationSlide: [{ type: i0.Output, args: ["movePresentationSlide"] }], exit: [{ type: i0.Output, args: ["exit"] }], openAudienceWindow: [{ type: i0.Output, args: ["openAudienceWindow"] }], closeAudienceWindow: [{ type: i0.Output, args: ["closeAudienceWindow"] }], navigateToSlide: [{ type: i0.Output, args: ["navigateToSlide"] }] } });
84468
86550
 
84469
86551
  /**
@@ -87537,13 +89619,19 @@ class RibbonSlideshowSectionComponent {
87537
89619
  {{ 'pptx.slideShow.fromCurrent' | translate }}
87538
89620
  </button>
87539
89621
  <span class="pptx-rb-sep"></span>
89622
+ <!-- Presenter View. Deliberately NOT gated on slideCount(): no other
89623
+ binding disables it, and e2e/ribbon-control-inventory.spec.ts diffs
89624
+ exactly which controls each binding leaves usable. The label key is
89625
+ pptx.slideShow.* for the same reason: the old pptx.ribbon.* key
89626
+ happens to carry the same English, so the inventory's accessible-name
89627
+ diff passed by luck and would have broken on any locale edit. -->
87540
89628
  <button
87541
89629
  type="button"
87542
89630
  class="pptx-rb-pill"
87543
- [disabled]="slideCount() === 0"
89631
+ [title]="'pptx.slideShow.presenterViewTooltip' | translate"
87544
89632
  (click)="presenter.emit()"
87545
89633
  >
87546
- {{ 'pptx.ribbon.presenterView' | translate }}
89634
+ {{ 'pptx.slideShow.presenterView' | translate }}
87547
89635
  </button>
87548
89636
  <button
87549
89637
  type="button"
@@ -87668,13 +89756,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
87668
89756
  {{ 'pptx.slideShow.fromCurrent' | translate }}
87669
89757
  </button>
87670
89758
  <span class="pptx-rb-sep"></span>
89759
+ <!-- Presenter View. Deliberately NOT gated on slideCount(): no other
89760
+ binding disables it, and e2e/ribbon-control-inventory.spec.ts diffs
89761
+ exactly which controls each binding leaves usable. The label key is
89762
+ pptx.slideShow.* for the same reason: the old pptx.ribbon.* key
89763
+ happens to carry the same English, so the inventory's accessible-name
89764
+ diff passed by luck and would have broken on any locale edit. -->
87671
89765
  <button
87672
89766
  type="button"
87673
89767
  class="pptx-rb-pill"
87674
- [disabled]="slideCount() === 0"
89768
+ [title]="'pptx.slideShow.presenterViewTooltip' | translate"
87675
89769
  (click)="presenter.emit()"
87676
89770
  >
87677
- {{ 'pptx.ribbon.presenterView' | translate }}
89771
+ {{ 'pptx.slideShow.presenterView' | translate }}
87678
89772
  </button>
87679
89773
  <button
87680
89774
  type="button"
@@ -89740,7 +91834,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
89740
91834
  }], propDecorators: { canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], slideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideIndex", required: false }] }] } });
89741
91835
 
89742
91836
  // Generated by scripts/inline-shared.mjs from package.json. Do not edit.
89743
- const PPTX_ANGULAR_VIEWER_VERSION = "2.11.1";
91837
+ const PPTX_ANGULAR_VIEWER_VERSION = "2.12.1";
89744
91838
 
89745
91839
  /**
89746
91840
  * account-page.component.ts: File > Account content.
@@ -93136,7 +95230,8 @@ class RibbonPrimaryRowComponent {
93136
95230
  [disabled]="slideCount() === 0"
93137
95231
  (click)="presenter.emit(); presentMenuOpen.set(false)"
93138
95232
  >
93139
- {{ 'pptx.ribbon.presenterView' | translate }}
95233
+ <!-- Same key as the Slide Show tab and the other four bindings. -->
95234
+ {{ 'pptx.slideShow.presenterView' | translate }}
93140
95235
  </button>
93141
95236
  @if (!toolbar.isHidden('broadcast')) {
93142
95237
  <button
@@ -93328,7 +95423,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
93328
95423
  [disabled]="slideCount() === 0"
93329
95424
  (click)="presenter.emit(); presentMenuOpen.set(false)"
93330
95425
  >
93331
- {{ 'pptx.ribbon.presenterView' | translate }}
95426
+ <!-- Same key as the Slide Show tab and the other four bindings. -->
95427
+ {{ 'pptx.slideShow.presenterView' | translate }}
93332
95428
  </button>
93333
95429
  @if (!toolbar.isHidden('broadcast')) {
93334
95430
  <button
@@ -100025,21 +102121,14 @@ function fillPatternLabelKey(preset) {
100025
102121
  return schemaLabelKey(FILL_PATTERN_LABEL_KEYS, preset);
100026
102122
  }
100027
102123
 
100028
- const ARROWS = [
100029
- 'none',
100030
- 'triangle',
100031
- 'arrow',
100032
- 'stealth',
100033
- 'diamond',
100034
- 'oval',
100035
- ];
100036
102124
  /**
100037
102125
  * Arrowhead width / length steps, i.e. the `a:headEnd/@w` and `@len` values.
100038
102126
  *
100039
- * Exported so a unit test can pin the offered set while the labels change (the
100040
- * package's suite is TestBed-free, so the template is out of reach).
102127
+ * Re-exported from the shared descriptor table so a unit test can pin the
102128
+ * offered set while the labels change (the package's suite is TestBed-free, so
102129
+ * the template is out of reach).
100041
102130
  */
100042
- const ARROW_SIZE_VALUES = ['sm', 'med', 'lg'];
102131
+ const ARROW_SIZE_VALUES = CONNECTOR_ARROW_SIZE_VALUES;
100043
102132
  const GEOMETRIES = [
100044
102133
  ['straightConnector1', 'Straight'],
100045
102134
  ['bentConnector2', 'Bent'],
@@ -100059,10 +102148,13 @@ class ElementMiscPropertiesComponent {
100059
102148
  element = input.required(/* @ts-ignore */
100060
102149
  ...(ngDevMode ? [{ debugName: "element" }] : /* istanbul ignore next */ []));
100061
102150
  patch = output();
100062
- arrows = ARROWS;
100063
- sizes = ARROW_SIZE_VALUES;
102151
+ /**
102152
+ * The six arrowhead dropdowns, described once in shared. Angular used to
102153
+ * declare its own value order and interpolate sentence-case captions
102154
+ * ("Start arrow"), which read differently from the other four bindings.
102155
+ */
102156
+ arrowControls = CONNECTOR_ARROW_CONTROLS;
100064
102157
  geometries = GEOMETRIES;
100065
- ends = ['Start', 'End'];
100066
102158
  connector = computed(() => this.element().type === 'connector', /* @ts-ignore */
100067
102159
  ...(ngDevMode ? [{ debugName: "connector" }] : /* istanbul ignore next */ []));
100068
102160
  connectorType = computed(() => this.element().shapeType ?? 'straightConnector1', /* @ts-ignore */
@@ -100073,36 +102165,25 @@ class ElementMiscPropertiesComponent {
100073
102165
  ...(ngDevMode ? [{ debugName: "ole" }] : /* istanbul ignore next */ []));
100074
102166
  oleType = computed(() => getOleObjectTypeLabel(this.ole()?.oleObjectType), /* @ts-ignore */
100075
102167
  ...(ngDevMode ? [{ debugName: "oleType" }] : /* istanbul ignore next */ []));
100076
- arrowLabel(value) {
100077
- return arrowheadLabelKey(value);
100078
- }
100079
102168
  /**
100080
- * Spell an arrowhead width/length step. `sm` / `med` / `lg` are the literal
100081
- * `a:headEnd/@w` and `@len` attribute values, so the picker was offering
100082
- * three abbreviations from the schema rather than words.
102169
+ * Spell one option. Resolving a KEY (not finished text) keeps the wording
102170
+ * live under `OnPush`, since `TranslatePipe` marks the view for check when
102171
+ * the language changes. See `schema-token-labels`.
100083
102172
  */
100084
- sizeLabel(size) {
100085
- return arrowSizeLabelKey(size);
102173
+ optionLabelKey(control, value) {
102174
+ return schemaLabelKey(control.optionLabelKeys, value);
100086
102175
  }
100087
- arrowValue(end) {
100088
- return (this.element().shapeStyle?.[`connector${end}Arrow`] ?? 'none');
102176
+ arrowValue(control) {
102177
+ return connectorArrowValue(control, this.element().shapeStyle);
100089
102178
  }
100090
- sizeValue(end, dimension) {
100091
- return String(this.element().shapeStyle?.[`connector${end}Arrow${dimension}`] ?? 'med');
100092
- }
100093
- onArrow(end, event) {
100094
- this.updateStyle({ [`connector${end}Arrow`]: event.target.value });
102179
+ onArrow(control, event) {
102180
+ this.updateStyle(connectorArrowPatch(control, event.target.value));
100095
102181
  }
100096
102182
  onConnectorType(event) {
100097
102183
  this.patch.emit({
100098
102184
  shapeType: event.target.value,
100099
102185
  });
100100
102186
  }
100101
- onSize(end, dimension, event) {
100102
- this.updateStyle({
100103
- [`connector${end}Arrow${dimension}`]: event.target.value,
100104
- });
100105
- }
100106
102187
  updateStyle(update) {
100107
102188
  this.patch.emit(connectorStylePatch(this.element(), update));
100108
102189
  }
@@ -100111,37 +102192,34 @@ class ElementMiscPropertiesComponent {
100111
102192
  @if (connector()) {
100112
102193
  <section class="card" aria-label="Connector">
100113
102194
  <h3>Connector</h3>
102195
+ <!--
102196
+ Selection is expressed with [selected] on each option rather than
102197
+ [value] on the select: Angular applies an element's own property
102198
+ bindings before the @for below it has produced any options, so a
102199
+ [value] naming a token was assigned to an EMPTY select and dropped
102200
+ back to the first entry. Every one of these dropdowns therefore read
102201
+ "none" / "Small" no matter what the deck authored, and the card
102202
+ silently misreported the connector it was editing.
102203
+ -->
100114
102204
  <label class="geometry">
100115
102205
  <span>Geometry</span>
100116
- <select [value]="connectorType()" (change)="onConnectorType($event)">
102206
+ <select (change)="onConnectorType($event)">
100117
102207
  @for (geometry of geometries; track geometry[0]) {
100118
- <option [value]="geometry[0]">{{ geometry[1] }}</option>
102208
+ <option [value]="geometry[0]" [selected]="geometry[0] === connectorType()">
102209
+ {{ geometry[1] }}
102210
+ </option>
100119
102211
  }
100120
102212
  </select>
100121
102213
  </label>
100122
102214
  <div class="grid">
100123
- @for (end of ends; track end) {
100124
- <label>
100125
- <span>{{ end }} arrow</span>
100126
- <select [value]="arrowValue(end)" (change)="onArrow(end, $event)">
100127
- @for (arrow of arrows; track arrow) {
100128
- <option [value]="arrow">{{ arrowLabel(arrow) | translate }}</option>
100129
- }
100130
- </select>
100131
- </label>
102215
+ @for (control of arrowControls; track control.styleKey) {
100132
102216
  <label>
100133
- <span>{{ end }} width</span>
100134
- <select [value]="sizeValue(end, 'Width')" (change)="onSize(end, 'Width', $event)">
100135
- @for (size of sizes; track size) {
100136
- <option [value]="size">{{ sizeLabel(size) | translate }}</option>
100137
- }
100138
- </select>
100139
- </label>
100140
- <label>
100141
- <span>{{ end }} length</span>
100142
- <select [value]="sizeValue(end, 'Length')" (change)="onSize(end, 'Length', $event)">
100143
- @for (size of sizes; track size) {
100144
- <option [value]="size">{{ sizeLabel(size) | translate }}</option>
102217
+ <span>{{ control.labelKey | translate }}</span>
102218
+ <select (change)="onArrow(control, $event)">
102219
+ @for (value of control.values; track value) {
102220
+ <option [value]="value" [selected]="value === arrowValue(control)">
102221
+ {{ optionLabelKey(control, value) | translate }}
102222
+ </option>
100145
102223
  }
100146
102224
  </select>
100147
102225
  </label>
@@ -100184,37 +102262,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
100184
102262
  @if (connector()) {
100185
102263
  <section class="card" aria-label="Connector">
100186
102264
  <h3>Connector</h3>
102265
+ <!--
102266
+ Selection is expressed with [selected] on each option rather than
102267
+ [value] on the select: Angular applies an element's own property
102268
+ bindings before the @for below it has produced any options, so a
102269
+ [value] naming a token was assigned to an EMPTY select and dropped
102270
+ back to the first entry. Every one of these dropdowns therefore read
102271
+ "none" / "Small" no matter what the deck authored, and the card
102272
+ silently misreported the connector it was editing.
102273
+ -->
100187
102274
  <label class="geometry">
100188
102275
  <span>Geometry</span>
100189
- <select [value]="connectorType()" (change)="onConnectorType($event)">
102276
+ <select (change)="onConnectorType($event)">
100190
102277
  @for (geometry of geometries; track geometry[0]) {
100191
- <option [value]="geometry[0]">{{ geometry[1] }}</option>
102278
+ <option [value]="geometry[0]" [selected]="geometry[0] === connectorType()">
102279
+ {{ geometry[1] }}
102280
+ </option>
100192
102281
  }
100193
102282
  </select>
100194
102283
  </label>
100195
102284
  <div class="grid">
100196
- @for (end of ends; track end) {
100197
- <label>
100198
- <span>{{ end }} arrow</span>
100199
- <select [value]="arrowValue(end)" (change)="onArrow(end, $event)">
100200
- @for (arrow of arrows; track arrow) {
100201
- <option [value]="arrow">{{ arrowLabel(arrow) | translate }}</option>
100202
- }
100203
- </select>
100204
- </label>
100205
- <label>
100206
- <span>{{ end }} width</span>
100207
- <select [value]="sizeValue(end, 'Width')" (change)="onSize(end, 'Width', $event)">
100208
- @for (size of sizes; track size) {
100209
- <option [value]="size">{{ sizeLabel(size) | translate }}</option>
100210
- }
100211
- </select>
100212
- </label>
102285
+ @for (control of arrowControls; track control.styleKey) {
100213
102286
  <label>
100214
- <span>{{ end }} length</span>
100215
- <select [value]="sizeValue(end, 'Length')" (change)="onSize(end, 'Length', $event)">
100216
- @for (size of sizes; track size) {
100217
- <option [value]="size">{{ sizeLabel(size) | translate }}</option>
102287
+ <span>{{ control.labelKey | translate }}</span>
102288
+ <select (change)="onArrow(control, $event)">
102289
+ @for (value of control.values; track value) {
102290
+ <option [value]="value" [selected]="value === arrowValue(control)">
102291
+ {{ optionLabelKey(control, value) | translate }}
102292
+ </option>
100218
102293
  }
100219
102294
  </select>
100220
102295
  </label>
@@ -120842,5 +122917,5 @@ function cn(...values) {
120842
122917
  * Generated bundle index. Do not edit.
120843
122918
  */
120844
122919
 
120845
- export { CustomShowsComponent as $, ALIGN_OPTIONS as A, BroadcastDialogComponent as B, CHART_EDITOR_STYLES as C, CanvasFitService as D, ChartAxisOptionsComponent as E, ChartAxisStyleOptionsComponent as F, ChartComboTypeOptionsComponent as G, ChartDataEditorComponent as H, ChartDataLabelOptionsComponent as I, ChartDatapointMarkerOptionsComponent as J, ChartDatapointOptionsComponent as K, ChartDisplayOptionsComponent as L, ChartElementViewComponent as M, ChartErrorBarOptionsComponent as N, ChartMarkerOptionsComponent as O, ChartPartSelectionService as P, ChartPrimitivesComponent as Q, ChartRendererComponent as R, ChartTrendlineOptionsComponent as S, CollaborationCursorsComponent as T, CollaborationService as U, ColorChangedImageComponent as V, CommentsPanelComponent as W, CommentsService as X, ComparePanelComponent as Y, ConnectorRendererComponent as Z, ConnectorTextOverlayComponent as _, ANIMATION_PRESET_CATEGORIES as a, LoadContentService as a$, DATA_TABLE_HEADER_H as a0, DATA_TABLE_KEY_W as a1, DATA_TABLE_PADDING as a2, DATA_TABLE_ROW_H as a3, DEFAULT_BOUNDS as a4, DEFAULT_BROADCAST_SERVER_URL as a5, DEFAULT_CANVAS_HEIGHT as a6, DEFAULT_CANVAS_WIDTH as a7, DEFAULT_COLOR_SCHEME as a8, DEFAULT_FILL_COLOR as a9, EquationEditorDialogComponent as aA, EquationRendererComponent as aB, EquationTemplateGalleryComponent as aC, ExportProgressModalComponent as aD, ExportService as aE, FieldContextService as aF, FindBarComponent as aG, FindReplaceBarComponent as aH, FollowModeBarComponent as aI, FontEmbeddingListComponent as aJ, FontEmbeddingPanelComponent as aK, GALLERY_THEME_PRESETS as aL, GradientPickerComponent as aM, HANDOUT_OPTIONS as aN, HeaderFooterDialogComponent as aO, HyperlinkDialogComponent as aP, ImagePropertiesPanelComponent as aQ, InkDrawingService as aR, InkRendererComponent as aS, InsertSmartArtDialogComponent as aT, InspectorPaneHeaderComponent as aU, InspectorPanelComponent as aV, IsMobileService as aW, KeepAnnotationsDialogComponent as aX, LOCALE_CATALOG as aY, LONG_PRESS_DURATION_MS as aZ, LONG_PRESS_MOVE_TOLERANCE_PX as a_, DEFAULT_LAYOUT as aa, DEFAULT_PALETTE$1 as ab, DEFAULT_PATTERN_FILL_PRESET as ac, DEFAULT_PRINT_SETTINGS as ad, DEFAULT_SLIDE_BACKGROUND as ae, DEFAULT_STROKE_COLOR as af, DEFAULT_STYLE as ag, DEFAULT_TABLE_ROW_HEIGHT as ah, DEFAULT_TEXT_COLOR$1 as ai, DEFAULT_VIEWER_PROFILE as aj, DIRECTIONAL_PRESETS as ak, DIRECTION_OPTIONS as al, DocumentPropertiesCardComponent as am, EMBEDDED_FONTS_STYLE_ID as an, EMPHASIS_PRESETS as ao, ENTRANCE_PRESETS as ap, TEMPLATES as aq, EXIT_PRESETS as ar, EditorContextMenuComponent as as, EditorHistory as at, EditorStateService as au, EditorToolbarComponent as av, EffectsPanelComponent as aw, ElementRendererComponent as ax, EmbeddedFontsService as ay, EncryptedFileDialogComponent as az, AUDIENCE_HASH as b, RibbonInsertFieldsComponent as b$, LocalPresencePublisher as b0, MAX_ZOOM_SCALE as b1, MIN_ZOOM_SCALE as b2, MOTION_PATH_COLUMNS as b3, MediaPreviewComponent as b4, MediaPropertiesPanelComponent as b5, MediaRendererComponent as b6, MediaTrimTimelineComponent as b7, MobileBottomBarComponent as b8, MobileMenuSheetComponent as b9, PresentationToolbarComponent as bA, PresentationTransitionOverlayComponent as bB, PresenterViewComponent as bC, PresenterWindowService as bD, PrintDialogComponent as bE, PrintService as bF, PrintSettingsPanelComponent as bG, PropertiesDialogComponent as bH, REPEAT_MODE_OPTIONS as bI, RESIZE_HANDLES as bJ, RULER_FONT_SIZE as bK, RULER_THICKNESS as bL, ReadingViewOverlayComponent as bM, RemoteSelectionOverlayComponent as bN, RibbonAnimationGalleryComponent as bO, RibbonAnimationsSectionComponent as bP, RibbonArrangeSectionComponent as bQ, RibbonColorPopoverComponent as bR, RibbonComponent as bS, RibbonDesignSectionComponent as bT, RibbonDrawSectionComponent as bU, RibbonDrawingGroupComponent as bV, RibbonEditingSectionComponent as bW, RibbonFileSectionComponent as bX, RibbonFontControlsComponent as bY, RibbonHomeSectionComponent as bZ, RibbonHyperlinkButtonComponent as b_, MobilePresenterViewComponent as ba, MobileSheetComponent as bb, MobileSlidesSheetComponent as bc, MobileToolbarComponent as bd, ModalDialogComponent as be, Model3DRendererComponent as bf, NotesHandoutCardComponent as bg, NotesPanelComponent as bh, NotesToolbarComponent as bi, OleRendererComponent as bj, OutlineViewOverlayComponent as bk, POWER_POINT_VIEWER_PROVIDERS as bl, PRESENTER_CHANNEL_NAME as bm, PRESENTER_MSG_ORIGIN as bn, PX_PER_CM as bo, PX_PER_INCH as bp, PasswordProtectionDialogComponent as bq, PasswordStrengthMeterComponent as br, PowerPointViewerComponent as bs, PresentToolbarAutoHide as bt, PresentationAnnotationOverlayComponent as bu, PresentationAnnotationsService as bv, PresentationOverlayComponent as bw, PresentationPropertiesPanelComponent as bx, PresentationSettingsCardComponent as by, PresentationSubtitleBarComponent as bz, AUDIENCE_NONCE_KEY as c, TablePropertiesComponent as c$, RibbonInsertSectionComponent as c0, RibbonMotionPathGalleryComponent as c1, RibbonParagraphControlsComponent as c2, RibbonPrimaryRowComponent as c3, RibbonReviewSectionComponent as c4, RibbonShapeExtrasComponent as c5, RibbonSlideshowSectionComponent as c6, RibbonTransitionsSectionComponent as c7, RibbonViewSectionComponent as c8, RulerGuidesService as c9, SlideCanvasComponent as cA, SlideDefaultInspectorComponent as cB, SlideDiffChangesComponent as cC, SlideDiffRowComponent as cD, SlideDiffThumbnailsComponent as cE, SlideSizeCardComponent as cF, SlideSorterOverlayComponent as cG, SlideThemeOverridePanelComponent as cH, SlideTransitionCardComponent as cI, SlidesPanelComponent as cJ, SmartArt3DRendererComponent as cK, SmartArt3DService as cL, SmartArtPreviewComponent as cM, SmartArtPropertiesComponent as cN, SmartArtRendererComponent as cO, StatusBarComponent as cP, TABLE_STRUCTURE_TOGGLES as cQ, TEXT_3D_BOTTOM_BEVEL_KEYS as cR, TEXT_3D_TOP_BEVEL_KEYS as cS, TEXT_DIRECTION_OPTIONS$1 as cT, THEME_CATALOG as cU, TIMING_CURVE_OPTIONS as cV, TRIGGER_OPTIONS as cW, TYPE_LABELS as cX, TableCellAdvancedFillComponent as cY, TableCellFormattingComponent as cZ, TableDataEditorComponent as c_, SEQUENCE_OPTIONS as ca, SEVERITY_GROUPS as cb, SEVERITY_LABELS as cc, SHORTCUT_REFERENCE_ITEMS as cd, SLIDE_TRANSITION_KEYFRAMES as ce, DEFAULT_PALETTE as cf, PALETTES$1 as cg, SMART_ART_COLOR_SCHEMES as ch, SMART_ART_STYLE_OPTIONS as ci, SUB_ITEM_LABEL as cj, SVG_WARP_PRESETS as ck, SWIPE_MAX_VERTICAL_PX as cl, SWIPE_THRESHOLD_PX as cm, SelectionPaneComponent as cn, SetUpSlideShowDialogComponent as co, SettingsAppearanceTabComponent as cp, SettingsDialogComponent as cq, SettingsLanguageTabComponent as cr, ShareDialogComponent as cs, ShortcutPanelComponent as ct, ShowOptionsFieldsetComponent as cu, ShowSlidesFieldsetComponent as cv, SignatureStrippedDialogComponent as cw, SignaturesPanelComponent as cx, SignaturesService as cy, SlideBackgroundCardComponent as cz, AVATAR_COLOR_SWATCHES as d, attachTouchGestures as d$, TableRendererComponent as d0, TableResizeOverlayComponent as d1, TableSelectionService as d2, TagsCardComponent as d3, Text3DBevelSectionComponent as d4, Text3DPanelComponent as d5, TextAdvancedPanelComponent as d6, ThemeEditorFieldsComponent as d7, ThemeGalleryComponent as d8, ThemeSelectorCardComponent as d9, ViewerZoomService as dA, WEBM_MIME_CANDIDATES as dB, WriteBackScheduler as dC, ZoomNavigationService as dD, ZoomRendererComponent as dE, ZoomTargetService as dF, addCategory as dG, addCommentToList as dH, addGradientStopPatch as dI, addItem as dJ, addSeries as dK, addSubItem as dL, advanceStep as dM, aiToggleVisible as dN, alignPatch as dO, animationFor as dP, animationPresetLabelKey as dQ, annotationMapToInkInserts as dR, applyAcceptedDiff as dS, applyAnimationPreset as dT, applyFindReplacements as dU, applyFormatToElement as dV, applyMove as dW, applyResize as dX, applyTableStylePreset as dY, asMediaElement as dZ, assignUserColor as d_, TitleBarComponent as da, TitleBarSearchComponent as db, TransitionDirectionPickerComponent as dc, TransitionPreviewComponent as dd, VALIGN_OPTIONS as de, VIEWER_THEME as df, VersionHistoryPanelComponent as dg, ViewerCanvasEditingService as dh, ViewerCollabCursorService as di, ViewerCollaborationSessionService as dj, ViewerCompareService as dk, ViewerCustomShowsService as dl, ViewerDialogsService as dm, ViewerDocumentPropertiesService as dn, ViewerExportService as dp, ViewerExtraDialogsComponent as dq, ViewerFileIOService as dr, ViewerFindReplaceService as ds, ViewerFormatPainterService as dt, ViewerInspectorPanelService as du, ViewerKeyboardService as dv, ViewerMobileSheetService as dw, ViewerPresentationModeService as dx, ViewerThemeGalleryService as dy, ViewerTouchGesturesService as dz, AccessibilityPanelComponent as e, captionDisplayText as e$, beginNodeEdit as e0, bevelSizePatch as e1, boolFromEvent as e2, bringForward as e3, bringToFront as e4, buildBarActions as e5, buildBroadcastConfig as e6, buildBroadcastViewerUrl as e7, buildCategoryLabels as e8, buildCellParagraphs as e9, buildOleInfoRows as eA, buildPatternFillCss as eB, buildPrintHtmlDocument as eC, buildPropertiesPatch as eD, buildRegionMapViewModel as eE, buildSaveSlides as eF, buildShareUrl as eG, buildSmartArtInsertElement as eH, buildSmartArtNodes as eI, buildStockViewModel as eJ, buildSurfaceViewModel as eK, buildTableViewModel as eL, buildTreemapViewModel as eM, buildTrimFragment as eN, buildWaterfallViewModel as eO, buildZeroLine as eP, buildZoomContainerStyle as eQ, buildZoomViewModel as eR, bulletIndentPx as eS, canAddTopLevelNode as eT, canGroupSelection as eU, canRemoveTopLevelNode as eV, canSetStrokeWidth as eW, canStartBroadcast as eX, canStartShare as eY, canUngroupSelection as eZ, canUseClipboard as e_, buildChartViewModel as ea, buildChatLogExport as eb, buildChatLogMarkdown as ec, buildChromeStyle as ed, buildClearHyperlinkPatch as ee, buildClickGroups as ef, buildColStyles as eg, buildCollaborationConfig as eh, buildComboViewModel as ei, buildCssGradientFromShapeStyle as ej, buildDuotoneFilter as ek, buildDuotoneFilterId as el, buildEmbeddedFontStyles as em, buildEquationElement as en, buildEquationSegment as eo, buildFallbackViewModel as ep, buildFontFaceRule as eq, buildGradientFillCss as er, buildGridlinesAndLabels as es, buildHyperlinkPatch as et, buildInkContainerStyle as eu, buildInkStrokes as ev, buildLegend as ew, buildModel3DContainerStyle as ex, buildModel3DViewModel as ey, buildOleActionModel as ez, AccessibilityService as f, createSwipeDismissDrag as f$, cellRunStyle as f0, cellStyleToStyleMap as f1, cellTdStyle as f2, changeCountLabel as f3, changeIcon as f4, characterSpacingPatch as f5, checkFontAvailable as f6, clampCursorPosition as f7, clampGifDimensions as f8, clampIndex as f9, computeIsTablet as fA, computeLinePoints as fB, computeLinearRegression as fC, computePageCount as fD, computePieLayout as fE, computePieSlicePath as fF, computePieSlices as fG, computePlotLayout as fH, computeRSquared as fI, computeRadarPoints as fJ, computeScatterDots as fK, computeSelectionBoxes as fL, computeSingleSelected as fM, computeSlideIndices as fN, computeSnap as fO, computeStackedBarRects as fP, computeStackedValueRange as fQ, computeTextLines as fR, computeTimerProgress as fS, computeTrendlinePrimitives as fT, computeValueRange as fU, convertOmmlToMathMl as fV, copyFormatFromElement as fW, countAccessibilityIssues as fX, countAnnotationStrokes as fY, createAngularAiBridge as fZ, createCustomShow as f_, clampNotesFontSize as fa, clampScale as fb, clampStep as fc, clearAllLocalViewerData as fd, clearAudienceContent as fe, cn as ff, collectAccessibilityIssues as fg, collectElementText as fh, collectSlideText as fi, collectStoredChats as fj, collectUsedFontFamilies as fk, columnWidthStyle as fl, commitNodeText as fm, computeAlign as fn, computeAxisTitlePrimitives as fo, computeBarRects as fp, computeBubbleRadius as fq, computeCornerHandle as fr, computeDataTablePrimitives as fs, computeDistribute as ft, computeDrawingViewBox as fu, computeErrorBarPrimitives as fv, computeFocusTargets as fw, computeHandleBoxes as fx, computeHandoutLayout as fy, computeIsMobile as fz, AccountPageComponent as g, getImageSrc as g$, createWebrtcBundle as g0, createWebsocketBundle as g1, cssObjectToStyleMap as g2, currentColorScheme as g3, currentLayout as g4, currentStyle as g5, defaultCssVars as g6, defaultRadius as g7, defaultThemeColors as g8, deleteElementsByIds as g9, fillColorOf as gA, findInSlides as gB, findOwningSlideIndex as gC, findSlideIndexByElementId as gD, firstVisibleIndex as gE, fitPolynomial as gF, fitZoom as gG, focusTargetChips as gH, fontMimeForFormat as gI, fontSizeOf as gJ, formatAutoNumber as gK, formatAxisValue as gL, formatBytes as gM, formatCursorLabel as gN, formatElapsed as gO, formatFileSize as gP, formatPropertyDate as gQ, formatTime as gR, fpsToFrameIntervalMs as gS, generateBroadcastRoomId as gT, generateCommentId as gU, generateCustomShowId as gV, generatePressureCircles as gW, generateTicks as gX, getClrChangeParams as gY, getContainerStyle as gZ, getDuotoneFilterDef as g_, deleteVersion as ga, demoteNode as gb, deriveModel3DBlobUrl as gc, derivePresenceList as gd, describeSmartArtBounds as ge, disableGlowPatch as gf, disableInnerShadowPatch as gg, disableOuterShadowPatch as gh, disableReflectionPatch as gi, disableSoftEdgePatch as gj, duplicateElementById as gk, durationOf as gl, effectsStateOf as gm, enableGlowPatch as gn, enableInnerShadowPatch as go, enableOuterShadowPatch as gp, enableReflectionPatch as gq, enableSoftEdgePatch as gr, encodeGif as gs, estimatePageCount as gt, evenColumnWidths as gu, evenRowHeights as gv, exitPresentationFullscreen as gw, exportAiChatLogs as gx, extractPathPoints as gy, eyedropperAvailable as gz, ActionSettingsPanelComponent as h, latexToMathml as h$, getLocalStorageUsageSummary as h0, getOleAriaLabel as h1, getOleBadgeLabel as h2, getOleDisplayName as h3, getOleDownloadFileName as h4, getOleTypeColor as h5, getOleTypeLabel as h6, getPasswordStrength as h7, getPatternSvg as h8, getPlaceholderStyle as h9, headerLabel as hA, imageDimensions as hB, inkViewBox as hC, insertTableElementColumn as hD, insertTableElementRow as hE, interpolateWidth as hF, isAudienceTab as hG, isBold as hH, isBrowserOpenableMime as hI, isChildNode as hJ, isElementInteractive as hK, isInjectableUrl as hL, isItalic as hM, isPpactionUrl as hN, isPresenterMessage as hO, isSigned as hP, isTextElement as hQ, isTwoTableFocus as hR, isUnderline as hS, isUrlSafe as hT, isValidRoomId as hU, isViewportBackgroundPressTarget as hV, isZoomActivationKey as hW, issueTrackKey as hX, issueTypeLabel as hY, keyToLabel as hZ, lastVisibleIndex as h_, getVersions as ha, getResolvedShapeClipPath as hb, getResolvedShapeClipPathFor as hc, getShapeFillStrokeStyle as hd, getSlideBackgroundStyle as he, getSlideTransitionAnimations as hf, getSmartArtNodeBounds as hg, getSpeechRecognitionCtor as hh, getTextBlockStyle as hi, getTextWarp as hj, getTouchDistance as hk, getWarpCategory as hl, getWarpPath as hm, gradientStateFromStyle as hn, gradientStateOf as ho, gradientStatePatch as hp, gridColumns as hq, groupElements as hr, groupIssuesBySeverity as hs, hasAnimation as ht, hasCopyableFormat as hu, hasExistingLink as hv, hasExitedFullscreen as hw, hasGradientFill as hx, hasPressureVariation as hy, hasVisibleSlideAfter as hz, AdvancedChartEditorComponent as i, radarAngle as i$, linePointsToSvgString as i0, lineSpacingPatch as i1, loadAudienceContent as i2, mergeCaptionResults as i3, mergeDown as i4, mergeRight as i5, mergeSelection as i6, moveElementBy as i7, moveNodeDown as i8, moveNodeUp as i9, ooxmlDashToCssBorderStyle as iA, openNativeEyeDropper as iB, overallStatus as iC, paletteColor as iD, parseAudienceNonce as iE, parseNodeTextarea as iF, partitionSlides as iG, patchChartData as iH, patchChartStyle as iI, patchTableData as iJ, patchTextStyle as iK, patternPresetOptions as iL, pendingElementStyles as iM, pickColorByClickFallback as iN, pickFile as iO, pickSupportedMimeType as iP, planGifFrames as iQ, planVideoSegments as iR, pointsToSvgPathD as iS, presenceToCursors as iT, presetByLayout as iU, presetsForCategory as iV, pressuresToWidths as iW, prevVisibleIndex as iX, projectDrawingShapes as iY, promoteNode as iZ, provideViewerTheme as i_, msToFrameDelayCs as ia, narrowToCircle as ib, narrowToPolygon as ic, narrowToRect as id, newChartElement as ie, newEquationElement as ig, newPresetShapeElement as ih, newShapeElement as ii, newSmartArtElement as ij, newTableElement as ik, newTextElement as il, nextVisibleIndex as im, nodeBold as io, nodeEditBox as ip, nodeFillColor as iq, nodeFontColor as ir, nodeIdFromKey as is, nodeItalic as it, nodeStyle as iu, normalizeFontFormat as iv, normalizeSlidesPerPage as iw, normalizeValue as ix, numFromEvent as iy, ommlToMathml as iz, AiChangeOverlayComponent as j, setAxisLogScale as j$, radarRingPoints as j0, readAsDataUrl as j1, recordWebm as j2, redistributeColumnWidth as j3, removeAnimation as j4, removeCategory as j5, removeTableElementColumn as j6, removeCommentFromList as j7, removeElementAnimation as j8, removeGradientStopPatch as j9, routeOrthogonalConnector as jA, rowStyle as jB, rulerDragToGuidePosition as jC, rulerHighlight as jD, rulerStripTicks as jE, sampleColorFromSlide as jF, sanitizeColor as jG, sanitizeSlideIndex as jH, sanitizeUserName as jI, saveViewerProfile as jJ, scanAvailableFonts as jK, searchSlides as jL, seedBroadcastFields as jM, seedHyperlinkDraft as jN, seedPropertiesDraft as jO, seedShareFields as jP, segmentFrameCount as jQ, selectValue$2 as jR, sendBackward as jS, sendToBack as jT, sequentialColorScale as jU, serializeWriteBack as jV, seriesColor as jW, setAnimationEmphasis as jX, setAnimationEntrance as jY, setAnimationExit as jZ, setAxis as j_, removeNode as ja, removeTableElementRow as jb, removeSeries as jc, renderToCanvas as jd, reorderAnimationDown as je, reorderAnimationUp as jf, replaceInSlides as jg, replaceMatch as jh, requestPresentationFullscreen as ji, resizeElement as jj, resolveCaptionTracks as jk, resolveChartKind as jl, resolveFontVariant as jm, resolveHyperlinkHref as jn, resolveInteractiveElementId as jo, resolveMediaSrc as jp, resolveOleType as jq, resolveParagraphBullet as jr, resolvePresenterNotes as js, resolveProfileInitial as jt, resolveRegionCode as ju, resolveSlideAutoAdvanceMs as jv, resolvePalette as jw, resolveThemeCatalogEntry as jx, resolveTransitionDuration as jy, revealedElementStyles as jz, AiChatPanelComponent as k, textStyleOf as k$, setAxisTitleStyle as k0, setCategoryLabel as k1, setCellText as k2, setColorScheme as k3, setDataLabels as k4, setDataPointExplosion as k5, setDataPointFill as k6, setDataPointLabel as k7, setDataPointMarker as k8, setDelay as k9, shouldUseSvgWarp as kA, showDirectionPicker as kB, showsTemplateAffordance as kC, signatureCountLabel as kD, signatureKey as kE, signatureTimestamp as kF, signerName as kG, statusLabel as kH, slideNumberOf as kI, smartArtNodes as kJ, paletteColour as kK, snapToGridStep as kL, splitCursorCell as kM, splitMergedCell as kN, statusKind as kO, statusLabel$1 as kP, storeAudienceContent as kQ, stringFromEvent$5 as kR, strokeColorOf as kS, strokeToInkElement as kT, strokeWidthOf as kU, styleShadowFilter as kV, textAdvancedPatch as kW, textAdvancedStateFromStyle as kX, textAdvancedStateOf as kY, textColorOf as kZ, textDirectionPatch as k_, setDirection as ka, setDuration as kb, setElementPosition as kc, setGridlineStyle as kd, setLayout as ke, setLegend as kf, setNodeStyle as kg, setNodeText as kh, setRepeatCount as ki, setRepeatMode as kj, setSequence as kk, setSeriesChartType as kl, setSeriesColor as km, setSeriesErrorBars as kn, setSeriesMarker as ko, setSeriesName as kp, setSeriesTrendline as kq, setSeriesValue as kr, setStyle as ks, setTimingCurve as kt, setTitle as ku, setTrigger as kv, setTriggerShapeId as kw, shapeStylePatch as kx, sheetAfterNavigate as ky, shouldBlockClickAdvance as kz, AiChatService as l, textStylePatch as l0, themeStyle as l1, themeToCssVars as l2, thumbnailHeight as l3, thumbnailZoom as l4, toggleCommentResolvedInList as l5, toggleNodeBold as l6, toggleNodeItalic as l7, toggleSheet as l8, topLevelNodeCount as l9, transformSelectedTextCase as la, translationsEn as lb, ungroupElements as lc, updateElementById as ld, updateGlowPatch as le, updateGradientStopPatch as lf, updateInnerShadowPatch as lg, updateOuterShadowPatch as lh, updateReflectionPatch as li, vAlignPatch as lj, validatePassword as lk, validatePrintSettings as ll, validateRoomId as lm, valueToY as ln, vermilionDarkColors as lo, vermilionDarkTheme as lp, vermilionLightColors as lq, vermilionLightTheme as lr, vermilionRadius as ls, waypointsToPathD as lt, worstStatus as lu, zoomTargetSlideIndex as lv, AiComposerComponent as m, AiFocusBarComponent as n, AiFocusHighlightOverlayComponent as o, AiMessageListComponent as p, AiPanelStore as q, AiProposalCardComponent as r, AiSettingsSectionComponent as s, toChatSummary as t, AiToolCallCardComponent as u, AnimationAuthorPanelComponent as v, AnimationPanelComponent as w, AnimationPlaybackService as x, AutosaveService as y, CURSOR_PALETTE as z };
120846
- //# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-y5x5Ro5v.mjs.map
122920
+ export { CustomShowsComponent as $, ALIGN_OPTIONS as A, BroadcastDialogComponent as B, CHART_EDITOR_STYLES as C, CanvasFitService as D, ChartAxisOptionsComponent as E, ChartAxisStyleOptionsComponent as F, ChartComboTypeOptionsComponent as G, ChartDataEditorComponent as H, ChartDataLabelOptionsComponent as I, ChartDatapointMarkerOptionsComponent as J, ChartDatapointOptionsComponent as K, ChartDisplayOptionsComponent as L, ChartElementViewComponent as M, ChartErrorBarOptionsComponent as N, ChartMarkerOptionsComponent as O, ChartPartSelectionService as P, ChartPrimitivesComponent as Q, ChartRendererComponent as R, ChartTrendlineOptionsComponent as S, CollaborationCursorsComponent as T, CollaborationService as U, ColorChangedImageComponent as V, CommentsPanelComponent as W, CommentsService as X, ComparePanelComponent as Y, ConnectorRendererComponent as Z, ConnectorTextOverlayComponent as _, ANIMATION_PRESET_CATEGORIES as a, LoadContentService as a$, DATA_TABLE_HEADER_H as a0, DATA_TABLE_KEY_W as a1, DATA_TABLE_PADDING as a2, DATA_TABLE_ROW_H as a3, DEFAULT_BOUNDS as a4, DEFAULT_BROADCAST_SERVER_URL as a5, DEFAULT_CANVAS_HEIGHT as a6, DEFAULT_CANVAS_WIDTH as a7, DEFAULT_COLOR_SCHEME as a8, DEFAULT_FILL_COLOR as a9, EquationEditorDialogComponent as aA, EquationRendererComponent as aB, EquationTemplateGalleryComponent as aC, ExportProgressModalComponent as aD, ExportService as aE, FieldContextService as aF, FindBarComponent as aG, FindReplaceBarComponent as aH, FollowModeBarComponent as aI, FontEmbeddingListComponent as aJ, FontEmbeddingPanelComponent as aK, GALLERY_THEME_PRESETS as aL, GradientPickerComponent as aM, HANDOUT_OPTIONS as aN, HeaderFooterDialogComponent as aO, HyperlinkDialogComponent as aP, ImagePropertiesPanelComponent as aQ, InkDrawingService as aR, InkRendererComponent as aS, InsertSmartArtDialogComponent as aT, InspectorPaneHeaderComponent as aU, InspectorPanelComponent as aV, IsMobileService as aW, KeepAnnotationsDialogComponent as aX, LOCALE_CATALOG as aY, LONG_PRESS_DURATION_MS as aZ, LONG_PRESS_MOVE_TOLERANCE_PX as a_, DEFAULT_LAYOUT as aa, DEFAULT_PALETTE$1 as ab, DEFAULT_PATTERN_FILL_PRESET as ac, DEFAULT_PRINT_SETTINGS as ad, DEFAULT_SLIDE_BACKGROUND as ae, DEFAULT_STROKE_COLOR as af, DEFAULT_STYLE as ag, DEFAULT_TABLE_ROW_HEIGHT as ah, DEFAULT_TEXT_COLOR$1 as ai, DEFAULT_VIEWER_PROFILE as aj, DIRECTIONAL_PRESETS as ak, DIRECTION_OPTIONS as al, DocumentPropertiesCardComponent as am, EMBEDDED_FONTS_STYLE_ID as an, EMPHASIS_PRESETS as ao, ENTRANCE_PRESETS as ap, TEMPLATES as aq, EXIT_PRESETS as ar, EditorContextMenuComponent as as, EditorHistory as at, EditorStateService as au, EditorToolbarComponent as av, EffectsPanelComponent as aw, ElementRendererComponent as ax, EmbeddedFontsService as ay, EncryptedFileDialogComponent as az, AUDIENCE_HASH as b, RibbonHyperlinkButtonComponent as b$, LocalPresencePublisher as b0, MAX_ZOOM_SCALE as b1, MIN_ZOOM_SCALE as b2, MOTION_PATH_COLUMNS as b3, MediaPreviewComponent as b4, MediaPropertiesPanelComponent as b5, MediaRendererComponent as b6, MediaTrimTimelineComponent as b7, MobileBottomBarComponent as b8, MobileMenuSheetComponent as b9, PresentationSubtitleBarComponent as bA, PresentationToolbarComponent as bB, PresentationTransitionOverlayComponent as bC, PresenterViewComponent as bD, PresenterWindowService as bE, PrintDialogComponent as bF, PrintService as bG, PrintSettingsPanelComponent as bH, PropertiesDialogComponent as bI, REPEAT_MODE_OPTIONS as bJ, RESIZE_HANDLES as bK, RULER_FONT_SIZE as bL, RULER_THICKNESS as bM, ReadingViewOverlayComponent as bN, RemoteSelectionOverlayComponent as bO, RibbonAnimationGalleryComponent as bP, RibbonAnimationsSectionComponent as bQ, RibbonArrangeSectionComponent as bR, RibbonColorPopoverComponent as bS, RibbonComponent as bT, RibbonDesignSectionComponent as bU, RibbonDrawSectionComponent as bV, RibbonDrawingGroupComponent as bW, RibbonEditingSectionComponent as bX, RibbonFileSectionComponent as bY, RibbonFontControlsComponent as bZ, RibbonHomeSectionComponent as b_, MobilePresenterViewComponent as ba, MobileSheetComponent as bb, MobileSlidesSheetComponent as bc, MobileToolbarComponent as bd, ModalDialogComponent as be, Model3DRendererComponent as bf, NotesHandoutCardComponent as bg, NotesPanelComponent as bh, NotesToolbarComponent as bi, OleRendererComponent as bj, OutlineViewOverlayComponent as bk, POWER_POINT_VIEWER_PROVIDERS as bl, PRESENTER_CHANNEL_NAME as bm, PRESENTER_MSG_ORIGIN as bn, PRESENTER_TIMER_SEGMENT_MS as bo, PX_PER_CM as bp, PX_PER_INCH as bq, PasswordProtectionDialogComponent as br, PasswordStrengthMeterComponent as bs, PowerPointViewerComponent as bt, PresentToolbarAutoHide as bu, PresentationAnnotationOverlayComponent as bv, PresentationAnnotationsService as bw, PresentationOverlayComponent as bx, PresentationPropertiesPanelComponent as by, PresentationSettingsCardComponent as bz, AUDIENCE_NONCE_KEY as c, TableDataEditorComponent as c$, RibbonInsertFieldsComponent as c0, RibbonInsertSectionComponent as c1, RibbonMotionPathGalleryComponent as c2, RibbonParagraphControlsComponent as c3, RibbonPrimaryRowComponent as c4, RibbonReviewSectionComponent as c5, RibbonShapeExtrasComponent as c6, RibbonSlideshowSectionComponent as c7, RibbonTransitionsSectionComponent as c8, RibbonViewSectionComponent as c9, SlideBackgroundCardComponent as cA, SlideCanvasComponent as cB, SlideDefaultInspectorComponent as cC, SlideDiffChangesComponent as cD, SlideDiffRowComponent as cE, SlideDiffThumbnailsComponent as cF, SlideSizeCardComponent as cG, SlideSorterOverlayComponent as cH, SlideThemeOverridePanelComponent as cI, SlideTransitionCardComponent as cJ, SlidesPanelComponent as cK, SmartArt3DRendererComponent as cL, SmartArt3DService as cM, SmartArtPreviewComponent as cN, SmartArtPropertiesComponent as cO, SmartArtRendererComponent as cP, StatusBarComponent as cQ, TABLE_STRUCTURE_TOGGLES as cR, TEXT_3D_BOTTOM_BEVEL_KEYS as cS, TEXT_3D_TOP_BEVEL_KEYS as cT, TEXT_DIRECTION_OPTIONS$1 as cU, THEME_CATALOG as cV, TIMING_CURVE_OPTIONS as cW, TRIGGER_OPTIONS as cX, TYPE_LABELS as cY, TableCellAdvancedFillComponent as cZ, TableCellFormattingComponent as c_, RulerGuidesService as ca, SEQUENCE_OPTIONS as cb, SEVERITY_GROUPS as cc, SEVERITY_LABELS as cd, SHORTCUT_REFERENCE_ITEMS as ce, SLIDE_TRANSITION_KEYFRAMES as cf, DEFAULT_PALETTE as cg, PALETTES$1 as ch, SMART_ART_COLOR_SCHEMES as ci, SMART_ART_STYLE_OPTIONS as cj, SUB_ITEM_LABEL as ck, SVG_WARP_PRESETS as cl, SWIPE_MAX_VERTICAL_PX as cm, SWIPE_THRESHOLD_PX as cn, SelectionPaneComponent as co, SetUpSlideShowDialogComponent as cp, SettingsAppearanceTabComponent as cq, SettingsDialogComponent as cr, SettingsLanguageTabComponent as cs, ShareDialogComponent as ct, ShortcutPanelComponent as cu, ShowOptionsFieldsetComponent as cv, ShowSlidesFieldsetComponent as cw, SignatureStrippedDialogComponent as cx, SignaturesPanelComponent as cy, SignaturesService as cz, AVATAR_COLOR_SWATCHES as d, asMediaElement as d$, TablePropertiesComponent as d0, TableRendererComponent as d1, TableResizeOverlayComponent as d2, TableSelectionService as d3, TagsCardComponent as d4, Text3DBevelSectionComponent as d5, Text3DPanelComponent as d6, TextAdvancedPanelComponent as d7, ThemeEditorFieldsComponent as d8, ThemeGalleryComponent as d9, ViewerTouchGesturesService as dA, ViewerZoomService as dB, WEBM_MIME_CANDIDATES as dC, WriteBackScheduler as dD, ZoomNavigationService as dE, ZoomRendererComponent as dF, ZoomTargetService as dG, addCategory as dH, addCommentToList as dI, addGradientStopPatch as dJ, addItem as dK, addSeries as dL, addSubItem as dM, advanceStep as dN, affordanceElements as dO, aiToggleVisible as dP, alignPatch as dQ, animationFor as dR, animationPresetLabelKey as dS, annotationMapToInkInserts as dT, applyAcceptedDiff as dU, applyAnimationPreset as dV, applyFindReplacements as dW, applyFormatToElement as dX, applyMove as dY, applyResize as dZ, applyTableStylePreset as d_, ThemeSelectorCardComponent as da, TitleBarComponent as db, TitleBarSearchComponent as dc, TransitionDirectionPickerComponent as dd, TransitionPreviewComponent as de, VALIGN_OPTIONS as df, VIEWER_THEME as dg, VersionHistoryPanelComponent as dh, ViewerCanvasEditingService as di, ViewerCollabCursorService as dj, ViewerCollaborationSessionService as dk, ViewerCompareService as dl, ViewerCustomShowsService as dm, ViewerDialogsService as dn, ViewerDocumentPropertiesService as dp, ViewerExportService as dq, ViewerExtraDialogsComponent as dr, ViewerFileIOService as ds, ViewerFindReplaceService as dt, ViewerFormatPainterService as du, ViewerInspectorPanelService as dv, ViewerKeyboardService as dw, ViewerMobileSheetService as dx, ViewerPresentationModeService as dy, ViewerThemeGalleryService as dz, AccessibilityPanelComponent as e, canUngroupSelection as e$, assignUserColor as e0, attachTouchGestures as e1, beginNodeEdit as e2, bevelSizePatch as e3, boolFromEvent as e4, bringForward as e5, bringToFront as e6, buildBarActions as e7, buildBroadcastConfig as e8, buildBroadcastViewerUrl as e9, buildModel3DViewModel as eA, buildOleActionModel as eB, buildOleInfoRows as eC, buildPatternFillCss as eD, buildPrintHtmlDocument as eE, buildPropertiesPatch as eF, buildRegionMapViewModel as eG, buildSaveSlides as eH, buildShareUrl as eI, buildSmartArtInsertElement as eJ, buildSmartArtNodes as eK, buildStockViewModel as eL, buildSurfaceViewModel as eM, buildTableViewModel as eN, buildTreemapViewModel as eO, buildTrimFragment as eP, buildWaterfallViewModel as eQ, buildZeroLine as eR, buildZoomContainerStyle as eS, buildZoomViewModel as eT, bulletIndentPx as eU, canAddTopLevelNode as eV, canGroupSelection as eW, canRemoveTopLevelNode as eX, canSetStrokeWidth as eY, canStartBroadcast as eZ, canStartShare as e_, buildCategoryLabels as ea, buildCellParagraphs as eb, buildChartViewModel as ec, buildChatLogExport as ed, buildChatLogMarkdown as ee, buildChromeStyle as ef, buildClearHyperlinkPatch as eg, buildClickGroups as eh, buildColStyles as ei, buildCollaborationConfig as ej, buildComboViewModel as ek, buildCssGradientFromShapeStyle as el, buildDuotoneFilter as em, buildDuotoneFilterId as en, buildEmbeddedFontStyles as eo, buildEquationElement as ep, buildEquationSegment as eq, buildFallbackViewModel as er, buildFontFaceRule as es, buildGradientFillCss as et, buildGridlinesAndLabels as eu, buildHyperlinkPatch as ev, buildInkContainerStyle as ew, buildInkStrokes as ex, buildLegend as ey, buildModel3DContainerStyle as ez, AccessibilityService as f, createCustomShow as f$, canUseClipboard as f0, captionDisplayText as f1, cellRunStyle as f2, cellStyleToStyleMap as f3, cellTdStyle as f4, changeCountLabel as f5, changeIcon as f6, characterSpacingPatch as f7, checkFontAvailable as f8, clampCursorPosition as f9, computeHandoutLayout as fA, computeIsMobile as fB, computeIsTablet as fC, computeLinePoints as fD, computeLinearRegression as fE, computePageCount as fF, computePieLayout as fG, computePieSlicePath as fH, computePieSlices as fI, computePlotLayout as fJ, computeRSquared as fK, computeRadarPoints as fL, computeScatterDots as fM, computeSelectionBoxes as fN, computeSingleSelected as fO, computeSlideIndices as fP, computeSnap as fQ, computeStackedBarRects as fR, computeStackedValueRange as fS, computeTextLines as fT, computeTrendlinePrimitives as fU, computeValueRange as fV, convertOmmlToMathMl as fW, copyFormatFromElement as fX, countAccessibilityIssues as fY, countAnnotationStrokes as fZ, createAngularAiBridge as f_, clampGifDimensions as fa, clampIndex as fb, clampNotesFontSize as fc, clampScale as fd, clampStep as fe, clearAllLocalViewerData as ff, clearAudienceContent as fg, cn as fh, collectAccessibilityIssues as fi, collectElementText as fj, collectSlideText as fk, collectStoredChats as fl, collectUsedFontFamilies as fm, columnWidthStyle as fn, commitNodeText as fo, computeAlign as fp, computeAxisTitlePrimitives as fq, computeBarRects as fr, computeBubbleRadius as fs, computeCornerHandle as ft, computeDataTablePrimitives as fu, computeDistribute as fv, computeDrawingViewBox as fw, computeErrorBarPrimitives as fx, computeFocusTargets as fy, computeHandleBoxes as fz, AccountPageComponent as g, getDuotoneFilterDef as g$, createSwipeDismissDrag as g0, createWebrtcBundle as g1, createWebsocketBundle as g2, cssObjectToStyleMap as g3, currentColorScheme as g4, currentLayout as g5, currentStyle as g6, defaultCssVars as g7, defaultRadius as g8, defaultThemeColors as g9, eyedropperAvailable as gA, fillColorOf as gB, findInSlides as gC, findOwningSlideIndex as gD, findSlideIndexByElementId as gE, firstVisibleIndex as gF, fitPolynomial as gG, fitZoom as gH, focusTargetChips as gI, fontMimeForFormat as gJ, fontSizeOf as gK, formatAutoNumber as gL, formatAxisValue as gM, formatBytes as gN, formatCursorLabel as gO, formatElapsed as gP, formatFileSize as gQ, formatPropertyDate as gR, formatTime as gS, fpsToFrameIntervalMs as gT, generateBroadcastRoomId as gU, generateCommentId as gV, generateCustomShowId as gW, generatePressureCircles as gX, generateTicks as gY, getClrChangeParams as gZ, getContainerStyle as g_, deleteElementsByIds as ga, deleteVersion as gb, demoteNode as gc, deriveModel3DBlobUrl as gd, derivePresenceList as ge, describeSmartArtBounds as gf, disableGlowPatch as gg, disableInnerShadowPatch as gh, disableOuterShadowPatch as gi, disableReflectionPatch as gj, disableSoftEdgePatch as gk, duplicateElementById as gl, durationOf as gm, effectsStateOf as gn, enableGlowPatch as go, enableInnerShadowPatch as gp, enableOuterShadowPatch as gq, enableReflectionPatch as gr, enableSoftEdgePatch as gs, encodeGif as gt, estimatePageCount as gu, evenColumnWidths as gv, evenRowHeights as gw, exitPresentationFullscreen as gx, exportAiChatLogs as gy, extractPathPoints as gz, ActionSettingsPanelComponent as h, lastVisibleIndex as h$, getImageSrc as h0, getLocalStorageUsageSummary as h1, getOleAriaLabel as h2, getOleBadgeLabel as h3, getOleDisplayName as h4, getOleDownloadFileName as h5, getOleTypeColor as h6, getOleTypeLabel as h7, getPasswordStrength as h8, getPatternSvg as h9, hasVisibleSlideAfter as hA, headerLabel as hB, imageDimensions as hC, inkViewBox as hD, insertTableElementColumn as hE, insertTableElementRow as hF, interpolateWidth as hG, isAudienceTab as hH, isBold as hI, isBrowserOpenableMime as hJ, isChildNode as hK, isElementInteractive as hL, isInjectableUrl as hM, isItalic as hN, isPpactionUrl as hO, isPresenterMessage as hP, isSigned as hQ, isTextElement as hR, isTwoTableFocus as hS, isUnderline as hT, isUrlSafe as hU, isValidRoomId as hV, isViewportBackgroundPressTarget as hW, isZoomActivationKey as hX, issueTrackKey as hY, issueTypeLabel as hZ, keyToLabel as h_, getPlaceholderStyle as ha, getVersions as hb, getResolvedShapeClipPath as hc, getResolvedShapeClipPathFor as hd, getShapeFillStrokeStyle as he, getSlideBackgroundStyle as hf, getSlideTransitionAnimations as hg, getSmartArtNodeBounds as hh, getSpeechRecognitionCtor as hi, getTextBlockStyle as hj, getTextWarp as hk, getTouchDistance as hl, getWarpCategory as hm, getWarpPath as hn, gradientStateFromStyle as ho, gradientStateOf as hp, gradientStatePatch as hq, gridColumns as hr, groupElements as hs, groupIssuesBySeverity as ht, hasAnimation as hu, hasCopyableFormat as hv, hasExistingLink as hw, hasExitedFullscreen as hx, hasGradientFill as hy, hasPressureVariation as hz, AdvancedChartEditorComponent as i, promoteNode as i$, latexToMathml as i0, linePointsToSvgString as i1, lineSpacingPatch as i2, loadAudienceContent as i3, mergeCaptionResults as i4, mergeDown as i5, mergeRight as i6, mergeSelection as i7, moveElementBy as i8, moveNodeDown as i9, ommlToMathml as iA, ooxmlDashToCssBorderStyle as iB, openNativeEyeDropper as iC, overallStatus as iD, paletteColor as iE, parseAudienceNonce as iF, parseNodeTextarea as iG, partitionSlides as iH, patchChartData as iI, patchChartStyle as iJ, patchTableData as iK, patchTextStyle as iL, patternPresetOptions as iM, pendingElementStyles as iN, pickColorByClickFallback as iO, pickFile as iP, pickSupportedMimeType as iQ, planGifFrames as iR, planVideoSegments as iS, pointsToSvgPathD as iT, presenceToCursors as iU, presenterTimerProgress as iV, presetByLayout as iW, presetsForCategory as iX, pressuresToWidths as iY, prevVisibleIndex as iZ, projectDrawingShapes as i_, moveNodeUp as ia, msToFrameDelayCs as ib, narrowToCircle as ic, narrowToPolygon as id, narrowToRect as ie, newChartElement as ig, newEquationElement as ih, newPresetShapeElement as ii, newShapeElement as ij, newSmartArtElement as ik, newTableElement as il, newTextElement as im, nextVisibleIndex as io, nodeBold as ip, nodeEditBox as iq, nodeFillColor as ir, nodeFontColor as is, nodeIdFromKey as it, nodeItalic as iu, nodeStyle as iv, normalizeFontFormat as iw, normalizeSlidesPerPage as ix, normalizeValue as iy, numFromEvent as iz, AiChangeOverlayComponent as j, setAnimationExit as j$, provideViewerTheme as j0, radarAngle as j1, radarRingPoints as j2, readAsDataUrl as j3, recordWebm as j4, redistributeColumnWidth as j5, removeAnimation as j6, removeCategory as j7, removeTableElementColumn as j8, removeCommentFromList as j9, resolveTransitionDuration as jA, revealedElementStyles as jB, routeOrthogonalConnector as jC, rowStyle as jD, rulerDragToGuidePosition as jE, rulerHighlight as jF, rulerStripTicks as jG, sampleColorFromSlide as jH, sanitizeColor as jI, sanitizeSlideIndex as jJ, sanitizeUserName as jK, saveViewerProfile as jL, scanAvailableFonts as jM, searchSlides as jN, seedBroadcastFields as jO, seedHyperlinkDraft as jP, seedPropertiesDraft as jQ, seedShareFields as jR, segmentFrameCount as jS, selectValue$2 as jT, sendBackward as jU, sendToBack as jV, sequentialColorScale as jW, serializeWriteBack as jX, seriesColor as jY, setAnimationEmphasis as jZ, setAnimationEntrance as j_, removeElementAnimation as ja, removeGradientStopPatch as jb, removeNode as jc, removeTableElementRow as jd, removeSeries as je, renderToCanvas as jf, reorderAnimationDown as jg, reorderAnimationUp as jh, replaceInSlides as ji, replaceMatch as jj, requestPresentationFullscreen as jk, resizeElement as jl, resolveCaptionTracks as jm, resolveChartKind as jn, resolveFontVariant as jo, resolveHyperlinkHref as jp, resolveInteractiveElementId as jq, resolveMediaSrc as jr, resolveOleType as js, resolveParagraphBullet as jt, resolvePresenterNotes as ju, resolveProfileInitial as jv, resolveRegionCode as jw, resolveSlideAutoAdvanceMs as jx, resolvePalette as jy, resolveThemeCatalogEntry as jz, AiChatPanelComponent as k, textColorOf as k$, setAxis as k0, setAxisLogScale as k1, setAxisTitleStyle as k2, setCategoryLabel as k3, setCellText as k4, setColorScheme as k5, setDataLabels as k6, setDataPointExplosion as k7, setDataPointFill as k8, setDataPointLabel as k9, sheetAfterNavigate as kA, shouldBlockClickAdvance as kB, shouldUseSvgWarp as kC, showDirectionPicker as kD, showsTemplateAffordance as kE, signatureCountLabel as kF, signatureKey as kG, signatureTimestamp as kH, signerName as kI, statusLabel as kJ, slideNumberOf as kK, smartArtNodes as kL, paletteColour as kM, snapToGridStep as kN, splitCursorCell as kO, splitMergedCell as kP, statusKind as kQ, statusLabel$1 as kR, storeAudienceContent as kS, stringFromEvent$5 as kT, strokeColorOf as kU, strokeToInkElement as kV, strokeWidthOf as kW, styleShadowFilter as kX, textAdvancedPatch as kY, textAdvancedStateFromStyle as kZ, textAdvancedStateOf as k_, setDataPointMarker as ka, setDelay as kb, setDirection as kc, setDuration as kd, setElementPosition as ke, setGridlineStyle as kf, setLayout as kg, setLegend as kh, setNodeStyle as ki, setNodeText as kj, setRepeatCount as kk, setRepeatMode as kl, setSequence as km, setSeriesChartType as kn, setSeriesColor as ko, setSeriesErrorBars as kp, setSeriesMarker as kq, setSeriesName as kr, setSeriesTrendline as ks, setSeriesValue as kt, setStyle as ku, setTimingCurve as kv, setTitle as kw, setTrigger as kx, setTriggerShapeId as ky, shapeStylePatch as kz, AiChatService as l, textDirectionPatch as l0, textStyleOf as l1, textStylePatch as l2, themeStyle as l3, themeToCssVars as l4, thumbnailHeight as l5, thumbnailZoom as l6, toggleCommentResolvedInList as l7, toggleNodeBold as l8, toggleNodeItalic as l9, toggleSheet as la, topLevelNodeCount as lb, transformSelectedTextCase as lc, translationsEn as ld, ungroupElements as le, updateElementById as lf, updateGlowPatch as lg, updateGradientStopPatch as lh, updateInnerShadowPatch as li, updateOuterShadowPatch as lj, updateReflectionPatch as lk, vAlignPatch as ll, validatePassword as lm, validatePrintSettings as ln, validateRoomId as lo, valueToY as lp, vermilionDarkColors as lq, vermilionDarkTheme as lr, vermilionLightColors as ls, vermilionLightTheme as lt, vermilionRadius as lu, waypointsToPathD as lv, worstStatus as lw, zoomTargetSlideIndex as lx, AiComposerComponent as m, AiFocusBarComponent as n, AiFocusHighlightOverlayComponent as o, AiMessageListComponent as p, AiPanelStore as q, AiProposalCardComponent as r, AiSettingsSectionComponent as s, toChatSummary as t, AiToolCallCardComponent as u, AnimationAuthorPanelComponent as v, AnimationPanelComponent as w, AnimationPlaybackService as x, AutosaveService as y, CURSOR_PALETTE as z };
122921
+ //# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-FnZONBKb.mjs.map