pptx-angular-viewer 3.19.1 → 3.20.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.
@@ -8,7 +8,7 @@ import DOMPurify from 'dompurify';
8
8
  import { z } from 'zod';
9
9
  import * as mcp from 'pptx-viewer-mcp';
10
10
  import * as mcpSchemas from 'pptx-viewer-mcp/schemas';
11
- 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, LucideSave, LucidePlay, LucidePrinter, LucideFileDown, LucideSearch, LucideStar, LucideMoveRight, LucideMousePointerClick, LucidePaintbrush, LucideClock3, LucidePencil, LucideType, LucideMinus, LucideSpline, LucideArrowLeft, LucideFilePlus2, LucideFolderOpen, LucideHome, LucideInfo, LucideSettings, LucideUpload, LucideUserRound, LucideAArrowUp, LucideAArrowDown, LucideRemoveFormatting, LucideList, LucideListOrdered, LucideListIndentDecrease, LucideListIndentIncrease, LucideTextAlignJustify, LucideClipboardPaste, LucideFolderPlus, LucideLayoutGrid, LucideLayoutTemplate, LucideScissors, LucideLink, LucideImage, LucideVideo, LucideDatabase, LucidePanelLeft, LucideEllipsis, LucideEye, LucideEyeOff, LucideColumns2, LucideCheck, LucideBug, LucideSend, LucideCrosshair, LucideGitMerge, LucidePin, LucidePinOff, LucideHistory, LucideMessageSquarePlus, LucideWrench, LucideLoaderCircle, LucideTriangleAlert, LucideShapes, LucidePalette, LucideTable, LucideChartColumn, LucideMove, LucideNavigation, LucideFilm, LucideBot, LucideUser } from '@lucide/angular';
11
+ import { LucideX, LucideSquare, LucideCircle, LucideSlash, LucideCopy, LucideTrash2, LucideChevronsUp, LucideChevronsDown, LucideArrowUp, LucideArrowDown, LucideGroup, LucideUngroup, LucideTextAlignStart, LucideTextAlignCenter, LucideTextAlignEnd, LucideChevronUp, LucideChevronDown, LucideAlignHorizontalSpaceAround, LucideAlignVerticalSpaceAround, LucidePlus, LucidePaintbrush, LucideFilter, LucideLayers, LucideMessageSquare, 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, LucideSave, LucidePlay, LucidePrinter, LucideFileDown, LucideSearch, LucideStar, LucideMoveRight, LucideMousePointerClick, LucideClock3, LucidePencil, LucideType, LucideMinus, LucideSpline, LucideArrowLeft, LucideFilePlus2, LucideFolderOpen, LucideHome, LucideInfo, LucideSettings, LucideUpload, LucideUserRound, LucideAArrowUp, LucideAArrowDown, LucideRemoveFormatting, LucideList, LucideListOrdered, LucideListIndentDecrease, LucideListIndentIncrease, LucideTextAlignJustify, LucideClipboardPaste, LucideFolderPlus, LucideLayoutGrid, LucideLayoutTemplate, LucideScissors, LucideLink, LucideImage, LucideVideo, LucideDatabase, LucidePanelLeft, LucideEllipsis, LucideEye, LucideEyeOff, LucideColumns2, LucideCheck, LucideBug, LucideSend, LucideCrosshair, LucideGitMerge, LucidePin, LucidePinOff, LucideHistory, LucideMessageSquarePlus, LucideWrench, LucideLoaderCircle, LucideTriangleAlert, LucideShapes, LucidePalette, LucideTable, LucideChartColumn, LucideMove, LucideNavigation, LucideFilm, LucideBot, LucideUser } from '@lucide/angular';
12
12
  import { XMLParser } from 'fast-xml-parser';
13
13
  import JSZip from 'jszip';
14
14
  import { DomSanitizer } from '@angular/platform-browser';
@@ -4974,6 +4974,78 @@ function resolveGroupChildFill(child, parentGroupFill) {
4974
4974
  return getComputedFillStyle(child, parentGroupFill);
4975
4975
  }
4976
4976
 
4977
+ /**
4978
+ * The exact algebraic inverse of {@link getElementTransform} (from
4979
+ * `element-style-transform.ts`), restricted to rotation + flip (no skew: a
4980
+ * skewed image fill with `rotWithShape="0"` is rare enough that an exact
4981
+ * shear inverse is not worth the added complexity yet).
4982
+ *
4983
+ * Derivation: `getElementTransform` emits the CSS list
4984
+ * `rotate(θ) scaleX(fx) scaleY(fy)`, i.e. the matrix product `R * Sx * Sy`
4985
+ * (CSS applies the RIGHTMOST function to the point first). Its inverse is
4986
+ * `Sy⁻¹ * Sx⁻¹ * R⁻¹ = Sy * Sx * R(-θ)` (flips are self-inverse), which as a
4987
+ * CSS list is `scaleY(fy) scaleX(fx) rotate(-θ deg)` - order matters, and
4988
+ * differs from the forward transform's order.
4989
+ *
4990
+ * @param element - The element whose `rotation`/`flipHorizontal`/`flipVertical` are read.
4991
+ * @returns A CSS transform string, or `undefined` if no counter-transform is needed.
4992
+ */
4993
+ function getFrameInverseTransform(element) {
4994
+ const transforms = [];
4995
+ if (element.flipVertical) {
4996
+ transforms.push('scaleY(-1)');
4997
+ }
4998
+ if (element.flipHorizontal) {
4999
+ transforms.push('scaleX(-1)');
5000
+ }
5001
+ if (element.rotation) {
5002
+ transforms.push(`rotate(${-element.rotation}deg)`);
5003
+ }
5004
+ return transforms.length > 0 ? transforms.join(' ') : undefined;
5005
+ }
5006
+ /**
5007
+ * CSS `transform` to apply to a picture's / image fill's CONTENT layer (the
5008
+ * `<img>`, or a `background-image` layer) so it renders upright/unmirrored,
5009
+ * fixed to the page frame, when the owning element's image fill explicitly
5010
+ * disables `rotWithShape` (`ShapeStyle.fillImageRotWithShape === false`).
5011
+ *
5012
+ * `undefined` (apply nothing; keep today's behaviour) when:
5013
+ * - the element has no shape styling, or its fill is not an image fill with
5014
+ * `rotWithShape` explicitly false (the default, `true`/unset, means the
5015
+ * fill rotates/flips WITH the shape, which is what already happens when
5016
+ * the content layer has no transform of its own), or
5017
+ * - the shape has no rotation/flip to counteract in the first place.
5018
+ *
5019
+ * Callers MUST keep the shape's own geometry mask (clip-path/border-radius)
5020
+ * on the STATIONARY frame container (never on this same content layer): the
5021
+ * container inherits the shape's rotate/flip so the visible clipped silhouette
5022
+ * still matches PowerPoint, while the content layer's own counter-transform
5023
+ * only affects its pixels, not the ancestor's clip region. A binding whose
5024
+ * content layer sometimes also carries its own copy of that clip-path (as a
5025
+ * micro-optimisation for the untransformed case) must suppress that copy
5026
+ * whenever this function returns a value, or the clip-path would double-flip
5027
+ * along with the content.
5028
+ *
5029
+ * Not applied when the content layer already carries a competing transform
5030
+ * (an `a:srcRect` source-crop translate/scale, see `getImageFitStyle`): the
5031
+ * two transforms' origins conflict (crop uses `top left`, this uses the
5032
+ * element's own `center`), so combining them is left as a follow-up; the far
5033
+ * more common crop takes priority. Pass `hasCompetingTransform: true` to skip
5034
+ * in that case.
5035
+ *
5036
+ * @param element - The element to inspect (reads `element.shapeStyle.fillImageRotWithShape`).
5037
+ * @param hasCompetingTransform - True when the content layer already has its own transform for an `a:srcRect` crop.
5038
+ */
5039
+ function getImageFillCounterTransform(element, hasCompetingTransform = false) {
5040
+ if (hasCompetingTransform || !hasShapeProperties(element)) {
5041
+ return undefined;
5042
+ }
5043
+ if (element.shapeStyle?.fillImageRotWithShape !== false) {
5044
+ return undefined;
5045
+ }
5046
+ return getFrameInverseTransform(element);
5047
+ }
5048
+
4977
5049
  /**
4978
5050
  * OOXML drawing-colour resolution: colour-choice parsing (srgb/scrgb/sys/scheme/
4979
5051
  * hsl/preset), the 26 colour transforms, scheme inheritance, and alpha
@@ -13432,12 +13504,79 @@ function compileEquationTemplateMathMl() {
13432
13504
  const NICE_STEPS = [1, 2, 2.5, 5, 10];
13433
13505
  /** The fraction of the data span added as headroom beyond the last data point. */
13434
13506
  const HEADROOM = 0.05;
13507
+ /**
13508
+ * Plot-area points of vertical room PowerPoint reserves per gridline before it
13509
+ * asks for another one, and the ceiling/floor on how many it will ever ask
13510
+ * for. See {@link axisTargetIntervals}'s doc comment for the PowerPoint COM
13511
+ * measurements these were fit to.
13512
+ */
13513
+ const POINTS_PER_GRIDLINE = 20;
13514
+ const MIN_TARGET_INTERVALS = 1;
13515
+ const MAX_TARGET_INTERVALS = 10;
13516
+ /**
13517
+ * Pixels per point in the "px" unit `computePlotLayout` (and every renderer in
13518
+ * this engine) works in: 96 px per inch, 72 points per inch, so 4/3 px per pt.
13519
+ * Mirrors `EMU_PER_PIXEL` (core/constants.ts): `EMU_PER_POINT / EMU_PER_PIXEL`
13520
+ * reduces to the same 4/3.
13521
+ */
13522
+ const PX_PER_POINT$1 = 4 / 3;
13523
+ /** {@link niceValueAxisBounds}'s `targetIntervals` when no plot height is known. */
13524
+ const DEFAULT_TARGET_INTERVALS = 4;
13435
13525
  /**
13436
13526
  * How close the smaller end of the data has to be to the larger before the axis
13437
13527
  * stops anchoring at zero. Below 5/6, zero anchoring keeps the bars readable;
13438
13528
  * above it, the data is a narrow band and zero would squash it flat.
13439
13529
  */
13440
13530
  const ZERO_ANCHOR_RATIO = 5 / 6;
13531
+ /**
13532
+ * How many gridline intervals PowerPoint's automatic value-axis scale aims
13533
+ * for, given the chart's plot-area height in the "px" unit `computePlotLayout`
13534
+ * returns (96 px/inch; see `PX_PER_POINT` above).
13535
+ *
13536
+ * This is NOT a universal constant (see this module's original doc comment,
13537
+ * which admitted as much: "matches the axes PowerPoint draws for a chart of
13538
+ * ORDINARY height"): a short chart gets fewer, coarser gridlines than a tall
13539
+ * one, up to a point, because PowerPoint only has room to label so many
13540
+ * without the axis text crowding.
13541
+ *
13542
+ * Measured via PowerPoint COM automation (`New-Object -ComObject
13543
+ * PowerPoint.Application`, `Shapes.AddChart2`, reading back
13544
+ * `chart.Axes(2).MinimumScale/.MaximumScale/.MajorUnit`): building a fresh
13545
+ * clustered-column chart at increasing shape heights with the SAME 8-point,
13546
+ * single-category data (values 1.2-3.2, the exact case reported against this
13547
+ * engine, which this engine rendered 0-4 while PowerPoint drew 0-3.5):
13548
+ *
13549
+ * | chart height | plot height | PowerPoint axis | gridlines |
13550
+ * |--------------|-------------|------------------|-----------|
13551
+ * | 100pt | ~33pt | 0 to 5, unit 5 | 1 |
13552
+ * | 150pt | ~83pt | 0 to 4, unit 1 | 4 |
13553
+ * | 200pt | ~133pt | 0 to 3.5, unit 0.5 | 7 |
13554
+ * | 300pt | ~233pt | 0 to 3.5, unit 0.5 | 7 |
13555
+ * | 1000pt | ~933pt | 0 to 3.5, unit 0.5 | 7 (unchanged) |
13556
+ *
13557
+ * Cross-checked against two more datasets (0-52 and 0-9) at the same set of
13558
+ * heights to separate "the target interval count" from "which nice-number
13559
+ * bucket a particular span happens to round into": PowerPoint never asked for
13560
+ * more than about 10 intervals even at a 1000pt-tall chart, it only asks for
13561
+ * FEWER on a plot area too short to fit that many labels. That saturation
13562
+ * (not a straight line) is why this is clamped at the top, not just scaled.
13563
+ *
13564
+ * Modelled as one gridline interval per ~20pt of plot height (roughly one
13565
+ * axis-label line plus padding), clamped to `[1, 10]`. This will not be exact
13566
+ * for every data range: the actual PowerPoint algorithm also nudges the count
13567
+ * by how the span happens to snap to a nice number (see the module doc
13568
+ * comment), which this simplified model does not attempt to reproduce. It is
13569
+ * a large improvement over hard-coding the short-chart answer (4) for every
13570
+ * chart regardless of size, which is the bug this was added to fix.
13571
+ */
13572
+ function axisTargetIntervals(plotHeightPx) {
13573
+ if (!Number.isFinite(plotHeightPx) || plotHeightPx <= 0) {
13574
+ return DEFAULT_TARGET_INTERVALS;
13575
+ }
13576
+ const plotHeightPt = plotHeightPx / PX_PER_POINT$1;
13577
+ const raw = Math.round(plotHeightPt / POINTS_PER_GRIDLINE);
13578
+ return Math.min(MAX_TARGET_INTERVALS, Math.max(MIN_TARGET_INTERVALS, raw));
13579
+ }
13441
13580
  /** The smallest nice step at or above `value`. */
13442
13581
  function niceAxisStep(value) {
13443
13582
  if (!Number.isFinite(value) || value <= 0) {
@@ -13462,10 +13601,13 @@ function snap(value, unit, direction) {
13462
13601
  * @param dataMin Smallest plotted value.
13463
13602
  * @param dataMax Largest plotted value.
13464
13603
  * @param targetIntervals Roughly how many gridline steps to aim for. The snap to
13465
- * a round unit means the result is usually one fewer. Four matches the axes
13466
- * PowerPoint draws for a chart of ordinary height.
13604
+ * a round unit means the result is usually one fewer. Defaults to 4 only
13605
+ * when the caller has no idea how tall the chart's plot area is; pass
13606
+ * {@link axisTargetIntervals}`(plotHeightPx)` instead whenever that height is
13607
+ * known; see that function's doc comment for why a fixed constant undershot
13608
+ * real PowerPoint on any chart of ordinary-or-larger size.
13467
13609
  */
13468
- function niceValueAxisBounds(dataMin, dataMax, targetIntervals = 4) {
13610
+ function niceValueAxisBounds(dataMin, dataMax, targetIntervals = DEFAULT_TARGET_INTERVALS) {
13469
13611
  if (!Number.isFinite(dataMin) || !Number.isFinite(dataMax)) {
13470
13612
  return { min: 0, max: 1, majorUnit: 0.5 };
13471
13613
  }
@@ -14059,13 +14201,19 @@ function paletteColor$1(index, styleId, colorPalette) {
14059
14201
  * sensibly, padded, and rounded out to whole major units. See
14060
14202
  * `chart-axis-nice.ts` for the rules. Running the axis to the raw data maximum
14061
14203
  * instead put the top gridline on whatever the tallest bar happened to be.
14204
+ *
14205
+ * @param plotHeightPx The chart's plot-area height (same "px" unit
14206
+ * `computePlotLayout` returns), when the caller has it. PowerPoint's real
14207
+ * gridline count depends on how much vertical room the axis has to label
14208
+ * them in; omitting this falls back to a constant tuned for a short chart
14209
+ * (see `axisTargetIntervals`'s doc comment).
14062
14210
  */
14063
- function computeValueRange$1(series) {
14211
+ function computeValueRange$1(series, plotHeightPx) {
14064
14212
  const allValues = series.flatMap((s) => s.values);
14065
14213
  if (allValues.length === 0) {
14066
14214
  return { min: 0, max: 1, span: 1 };
14067
14215
  }
14068
- const { min, max, majorUnit } = niceValueAxisBounds(Math.min(...allValues), Math.max(...allValues));
14216
+ const { min, max, majorUnit } = niceValueAxisBounds(Math.min(...allValues), Math.max(...allValues), plotHeightPx !== undefined ? axisTargetIntervals(plotHeightPx) : undefined);
14069
14217
  return { min, max, span: Math.max(max - min, Number.EPSILON), majorUnit };
14070
14218
  }
14071
14219
  /**
@@ -14195,13 +14343,23 @@ function resolveCategoryLabels(chartData) {
14195
14343
  * SVG. PowerPoint decks routinely write `<c:spPr><a:noFill/></c:spPr>` on
14196
14344
  * `c:chartSpace` and `c:plotArea` precisely so the chart floats on the slide;
14197
14345
  * the wash boxed every such chart into a visible grey panel that is not in the
14198
- * source. Honouring the authored fill is the fix, and keeping the wash as the
14199
- * fallback leaves charts that say nothing looking exactly as they did.
14200
- */
14201
- /**
14202
- * The wash the bindings have always painted when a chart declares no fill of
14203
- * its own. Kept as the default so an unstyled chart still reads as a distinct
14204
- * surface against the slide.
14346
+ * source. Honouring the authored fill (an explicit `<a:noFill/>`) was the
14347
+ * first fix, but a chart with NO `c:spPr` at all (the common case: only a
14348
+ * deliberately-styled chart writes one) still fell back to the same synthetic
14349
+ * wash, which is just as wrong: real PowerPoint never paints a highlight box
14350
+ * behind an unstyled chart, it is fully transparent like the `noFill` case.
14351
+ * Measured against a real-world deck (a plain single-series bar chart with no
14352
+ * `c:chartSpace/c:spPr` at all) PowerPoint's own export shows no grey
14353
+ * anywhere; this module now treats "no fill recorded" the same whether the
14354
+ * source said `noFill` explicitly or said nothing, matching `plotAreaFill`
14355
+ * below, which never had a synthetic default in the first place.
14356
+ */
14357
+ /**
14358
+ * The wash the bindings used to paint whenever a chart declared no fill of
14359
+ * its own. No longer used as an automatic default for real chart data (see
14360
+ * the module doc comment); kept only for `buildFallbackViewModel`'s
14361
+ * unsupported/empty-chart placeholder, which has no chart data to read a fill
14362
+ * from and still wants a visible box to represent "a chart goes here".
14205
14363
  */
14206
14364
  const DEFAULT_CHART_AREA_FILL = '#0f172a11';
14207
14365
  /** Resolve one recorded fill value to an SVG `fill`, or `undefined` to skip. */
@@ -14213,10 +14371,11 @@ function resolve(fill, fallback) {
14213
14371
  }
14214
14372
  /**
14215
14373
  * SVG `fill` for the chart-area rect, or `undefined` when nothing should be
14216
- * painted (the source declared `a:noFill`).
14374
+ * painted: the source declared `a:noFill`, or (matching real PowerPoint)
14375
+ * simply never wrote a `c:chartSpace/c:spPr` at all.
14217
14376
  */
14218
14377
  function chartAreaFill(chartData) {
14219
- return resolve(chartData?.style?.chartAreaFill, DEFAULT_CHART_AREA_FILL);
14378
+ return resolve(chartData?.style?.chartAreaFill, undefined);
14220
14379
  }
14221
14380
  /**
14222
14381
  * SVG `fill` for the plot-area rect, or `undefined` when nothing should be
@@ -14621,15 +14780,24 @@ function computeLogValueRange(series, logBase) {
14621
14780
  logBase,
14622
14781
  };
14623
14782
  }
14624
- /** Compute a value range using one parsed value axis's scale and bounds. */
14625
- function computeValueRangeForAxis(series, axis) {
14783
+ /**
14784
+ * Compute a value range using one parsed value axis's scale and bounds.
14785
+ *
14786
+ * @param plotHeightPx The chart's plot-area height, when known; forwarded to
14787
+ * `computeValueRange`'s automatic-scale gridline count (see
14788
+ * `axisTargetIntervals`'s doc comment). Not used on the log-scale branch,
14789
+ * which snaps to powers of the log base regardless of chart size.
14790
+ */
14791
+ function computeValueRangeForAxis(series, axis, plotHeightPx) {
14626
14792
  const requestedLogBase = axis?.logBase ?? 10;
14627
14793
  const logBase = axis?.logScale && Number.isFinite(requestedLogBase) && requestedLogBase > 1
14628
14794
  ? requestedLogBase
14629
14795
  : axis?.logScale
14630
14796
  ? 10
14631
14797
  : undefined;
14632
- const automatic = logBase ? computeLogValueRange(series, logBase) : computeValueRange$1(series);
14798
+ const automatic = logBase
14799
+ ? computeLogValueRange(series, logBase)
14800
+ : computeValueRange$1(series, plotHeightPx);
14633
14801
  const validMin = typeof axis?.min === 'number' && Number.isFinite(axis.min) && (!logBase || axis.min > 0)
14634
14802
  ? axis.min
14635
14803
  : undefined;
@@ -14725,11 +14893,15 @@ function generateMinorAxisTicks(range, axis) {
14725
14893
  function findLogAxis(axes) {
14726
14894
  return axes?.find((axis) => axis.axisType === 'valAx' && axis.logScale);
14727
14895
  }
14728
- /** Compute the primary value range for a chart. */
14729
- function computeValueRangeForChart(series, axes) {
14896
+ /**
14897
+ * Compute the primary value range for a chart.
14898
+ *
14899
+ * @param plotHeightPx See `computeValueRangeForAxis`'s parameter of the same name.
14900
+ */
14901
+ function computeValueRangeForChart(series, axes, plotHeightPx) {
14730
14902
  const primaryAxis = axes?.find((axis) => axis.axisType === 'valAx' && axis.axPos === 'l') ??
14731
14903
  axes?.find((axis) => axis.axisType === 'valAx' && axis.axPos !== 'r');
14732
- return computeValueRangeForAxis(series, primaryAxis);
14904
+ return computeValueRangeForAxis(series, primaryAxis, plotHeightPx);
14733
14905
  }
14734
14906
 
14735
14907
  // ── Display units ────────────────────────────────────────────────
@@ -14797,12 +14969,34 @@ function hasSecondaryValueAxis(axes) {
14797
14969
  }
14798
14970
  return axes.some((a) => a.axisType === 'valAx' && a.axPos === 'r');
14799
14971
  }
14800
- /** Check whether any axis in the list is a secondary category axis (position "t"). */
14972
+ function categoryAxes(axes) {
14973
+ return axes?.filter((a) => a.axisType === 'catAx' || a.axisType === 'dateAx') ?? [];
14974
+ }
14975
+ /**
14976
+ * Check whether the chart has a genuine SECOND category axis positioned at the
14977
+ * top (a combo chart with one bottom + one top category axis). A single
14978
+ * category axis that itself carries `axPos="t"` (the only axis, just
14979
+ * relocated) is NOT a secondary axis: see `isPrimaryCategoryAxisAtTop`.
14980
+ */
14801
14981
  function hasSecondaryCategoryAxis(axes) {
14802
- if (!axes) {
14982
+ const cats = categoryAxes(axes);
14983
+ return cats.length > 1 && cats.some((a) => a.axPos === 't');
14984
+ }
14985
+ /**
14986
+ * Check whether the chart's PRIMARY (and, in the common case, only) category
14987
+ * axis is positioned at the top of the plot area (`c:catAx/c:axPos val="t"`).
14988
+ * Mirrors the axis selection in `chart-category-axis.ts`'s
14989
+ * `primaryCategoryAxis`: prefer a non-top axis when one exists (the top one is
14990
+ * then the genuine secondary axis, handled separately), otherwise fall back to
14991
+ * the first category axis, which is the only one there is.
14992
+ */
14993
+ function isPrimaryCategoryAxisAtTop(axes) {
14994
+ const cats = categoryAxes(axes);
14995
+ if (cats.length === 0) {
14803
14996
  return false;
14804
14997
  }
14805
- return axes.some((a) => (a.axisType === 'catAx' || a.axisType === 'dateAx') && a.axPos === 't');
14998
+ const primary = cats.find((a) => a.axPos !== 't') ?? cats[0];
14999
+ return primary.axPos === 't';
14806
15000
  }
14807
15001
  /** Get the secondary value axis formatting, if present. */
14808
15002
  function getSecondaryValueAxis(axes) {
@@ -14823,6 +15017,7 @@ function computeLayoutOptions(axes, dataTable, seriesCount) {
14823
15017
  return {
14824
15018
  hasSecondaryValueAxis: hasSecondaryValueAxis(axes),
14825
15019
  hasSecondaryCategoryAxis: hasSecondaryCategoryAxis(axes),
15020
+ categoryAxisAtTop: isPrimaryCategoryAxisAtTop(axes),
14826
15021
  hasDataTable: Boolean(dataTable),
14827
15022
  dataTableRowCount: dataTable ? seriesCount : undefined,
14828
15023
  };
@@ -14935,6 +15130,160 @@ function shade(hex, amount) {
14935
15130
  return rgbToHex$3(r * (1 - amount), g * (1 - amount), b * (1 - amount));
14936
15131
  }
14937
15132
 
15133
+ /**
15134
+ * chart-marker-shape.ts: framework-agnostic marker-symbol primitive builder for
15135
+ * line / scatter / area chart data points.
15136
+ *
15137
+ * OOXML line/scatter series carry a `c:marker` with a `c:symbol` (circle,
15138
+ * diamond, square, triangle, star, x, plus, dot, dash, none, ...) and an
15139
+ * optional `c:size` (2-72 points). The base view-model historically drew every
15140
+ * data point as a fixed-radius circle, ignoring both. This helper resolves the
15141
+ * parsed marker into the correct `SvgPrimitive` at the requested size, and
15142
+ * returns `null` for `symbol === 'none'` so no dot is drawn.
15143
+ *
15144
+ * @module chart-marker-shape
15145
+ */
15146
+ /**
15147
+ * PowerPoint's automatic marker-symbol cycle: the fixed shape sequence a
15148
+ * "Line/Scatter with Markers" chart cycles through when a series' `c:marker`
15149
+ * carries no `c:symbol` (only its fill/line colour is "automatic"; the shape
15150
+ * itself was assumed to always be a circle before this was measured).
15151
+ *
15152
+ * COM-measured ground truth (`PowerPoint.Application`, a stacked
15153
+ * Line-with-Markers chart, N series each with `<c:marker><c:spPr>.../></c:marker>`
15154
+ * and no `c:symbol`, `SeriesCollection(i).MarkerStyle` read back after a real
15155
+ * save/reopen round trip): idx 0..8 resolved to
15156
+ * Diamond, Square, Triangle, X, Star, Circle, Plus, Dot, Dash - a clean
15157
+ * 9-element cycle with no partial/ambiguous entries across 9 independent
15158
+ * series (this is ALSO what the chart-style-driven, explicit-symbol path a
15159
+ * modern "Line with Markers" preset writes on save resolves every series to a
15160
+ * uniform Circle, so that path is unrelated: this cycle is specifically
15161
+ * what PowerPoint's renderer falls back to for a chart with no `c:symbol` at
15162
+ * all). Confirmed against a real-world deck: series `c:idx="1"`/`"2"` (a
15163
+ * gapped, 1-based pair, not `0`/`1`) resolved to Square/Triangle exactly as
15164
+ * this cycle predicts (`AUTOMATIC_MARKER_CYCLE[1]` / `[2]`).
15165
+ */
15166
+ const AUTOMATIC_MARKER_CYCLE = [
15167
+ 'diamond',
15168
+ 'square',
15169
+ 'triangle',
15170
+ 'x',
15171
+ 'star',
15172
+ 'circle',
15173
+ 'plus',
15174
+ 'dot',
15175
+ 'dash',
15176
+ ];
15177
+ /**
15178
+ * Resolve the automatic marker shape for a series with no authored
15179
+ * `c:symbol`, keyed by the series' `c:idx` (or its array position when the
15180
+ * chart has none): {@link AUTOMATIC_MARKER_CYCLE} repeats every 9 series.
15181
+ */
15182
+ function automaticMarkerSymbol(seriesIdx) {
15183
+ const length = AUTOMATIC_MARKER_CYCLE.length;
15184
+ const normalized = ((seriesIdx % length) + length) % length;
15185
+ return AUTOMATIC_MARKER_CYCLE[normalized];
15186
+ }
15187
+ /** Resolve the drawn radius (px) from the parsed point size or the default. */
15188
+ function markerRadius(size, defaultRadius) {
15189
+ if (size === undefined || !Number.isFinite(size) || size <= 0) {
15190
+ return defaultRadius;
15191
+ }
15192
+ // OOXML marker size is a point diameter; treat 1pt ~ 1px at chart scale.
15193
+ return size / 2;
15194
+ }
15195
+ /** Build the SVG points string for a regular/irregular polygon vertex list. */
15196
+ function polygonPoints(vertices) {
15197
+ return vertices.map(([x, y]) => `${x.toFixed(2)},${y.toFixed(2)}`).join(' ');
15198
+ }
15199
+ function starVertices(cx, cy, r) {
15200
+ const inner = r * 0.5;
15201
+ const out = [];
15202
+ for (let i = 0; i < 10; i++) {
15203
+ const radius = i % 2 === 0 ? r : inner;
15204
+ const angle = -Math.PI / 2 + (Math.PI * i) / 5;
15205
+ out.push([cx + radius * Math.cos(angle), cy + radius * Math.sin(angle)]);
15206
+ }
15207
+ return out;
15208
+ }
15209
+ /**
15210
+ * Build the marker primitive for one data point, honouring `symbol` and `size`.
15211
+ * Returns `null` when `symbol === 'none'` (draw nothing).
15212
+ */
15213
+ function buildMarkerPrimitive(input) {
15214
+ const { symbol, cx, cy, fill, part } = input;
15215
+ if (symbol === 'none') {
15216
+ return null;
15217
+ }
15218
+ const r = markerRadius(input.size, input.defaultRadius);
15219
+ const stroke = fill;
15220
+ switch (symbol) {
15221
+ case 'square':
15222
+ return { kind: 'rect', x: cx - r, y: cy - r, w: r * 2, h: r * 2, fill, part };
15223
+ case 'dash':
15224
+ return { kind: 'rect', x: cx - r, y: cy - r * 0.32, w: r * 2, h: r * 0.64, fill, part };
15225
+ case 'diamond':
15226
+ return {
15227
+ kind: 'polygon',
15228
+ points: polygonPoints([
15229
+ [cx, cy - r],
15230
+ [cx + r, cy],
15231
+ [cx, cy + r],
15232
+ [cx - r, cy],
15233
+ ]),
15234
+ fill,
15235
+ stroke,
15236
+ strokeWidth: 0,
15237
+ part,
15238
+ };
15239
+ case 'triangle':
15240
+ return {
15241
+ kind: 'polygon',
15242
+ points: polygonPoints([
15243
+ [cx, cy - r],
15244
+ [cx + r * 0.9, cy + r * 0.75],
15245
+ [cx - r * 0.9, cy + r * 0.75],
15246
+ ]),
15247
+ fill,
15248
+ stroke,
15249
+ strokeWidth: 0,
15250
+ part,
15251
+ };
15252
+ case 'star':
15253
+ return {
15254
+ kind: 'polygon',
15255
+ points: polygonPoints(starVertices(cx, cy, r)),
15256
+ fill,
15257
+ stroke,
15258
+ strokeWidth: 0,
15259
+ part,
15260
+ };
15261
+ case 'plus':
15262
+ return {
15263
+ kind: 'path',
15264
+ d: `M${(cx - r).toFixed(2)},${cy.toFixed(2)} L${(cx + r).toFixed(2)},${cy.toFixed(2)} M${cx.toFixed(2)},${(cy - r).toFixed(2)} L${cx.toFixed(2)},${(cy + r).toFixed(2)}`,
15265
+ fill: 'none',
15266
+ stroke,
15267
+ strokeWidth: Math.max(1, r * 0.4),
15268
+ part,
15269
+ };
15270
+ case 'x':
15271
+ return {
15272
+ kind: 'path',
15273
+ d: `M${(cx - r).toFixed(2)},${(cy - r).toFixed(2)} L${(cx + r).toFixed(2)},${(cy + r).toFixed(2)} M${(cx + r).toFixed(2)},${(cy - r).toFixed(2)} L${(cx - r).toFixed(2)},${(cy + r).toFixed(2)}`,
15274
+ fill: 'none',
15275
+ stroke,
15276
+ strokeWidth: Math.max(1, r * 0.4),
15277
+ part,
15278
+ };
15279
+ case 'dot':
15280
+ return { kind: 'circle', cx, cy, r: Math.max(r * 0.6, 1), fill, part };
15281
+ default:
15282
+ // circle / auto / picture / undefined -> filled circle.
15283
+ return { kind: 'circle', cx, cy, r, fill, part };
15284
+ }
15285
+ }
15286
+
14938
15287
  /**
14939
15288
  * chart-datapoint-style.ts: framework-agnostic resolution of per-data-point
14940
15289
  * formatting overrides (`c:dPt`).
@@ -14995,11 +15344,25 @@ function resolveDataPointExplosion(series, pointIndex) {
14995
15344
  * looked at `series.marker`, so editing a point marker changed the file and
14996
15345
  * left the canvas untouched. Resolving it here fixes every binding at once,
14997
15346
  * since they all paint the same shared view model.
15347
+ *
15348
+ * A resolved symbol of `'auto'` (or a marker present with no `c:symbol` at
15349
+ * all, which the core parser also normalises to `'auto'`) is resolved to a
15350
+ * concrete shape via {@link automaticMarkerSymbol}, keyed by `series.idx`
15351
+ * (falling back to `seriesPosition`, the series' array index, when the chart
15352
+ * carries no `c:idx`). A series/point with NO marker element at all
15353
+ * (`marker` and `point` both `undefined`) keeps `symbol: undefined`
15354
+ * unchanged: that is a chart type that draws no marker at all, not an
15355
+ * automatic one, and must not start cycling shapes.
14998
15356
  */
14999
- function resolveDataPointMarker(series, pointIndex) {
15357
+ function resolveDataPointMarker(series, pointIndex, seriesPosition = 0) {
15000
15358
  const point = findDataPoint(series, pointIndex)?.marker;
15359
+ const hasMarker = point !== undefined || series.marker !== undefined;
15360
+ const rawSymbol = point?.symbol ?? series.marker?.symbol;
15361
+ const symbol = hasMarker && (rawSymbol === undefined || rawSymbol === 'auto')
15362
+ ? automaticMarkerSymbol(series.idx ?? seriesPosition)
15363
+ : rawSymbol;
15001
15364
  return {
15002
- symbol: point?.symbol ?? series.marker?.symbol,
15365
+ symbol,
15003
15366
  size: point?.size ?? series.marker?.size,
15004
15367
  fill: point?.spPr?.fillColor ?? series.marker?.spPr?.fillColor,
15005
15368
  };
@@ -15028,6 +15391,26 @@ function upsertDataPoint(dataPoints, point) {
15028
15391
  }
15029
15392
  return next;
15030
15393
  }
15394
+ // Picture-fill resolution (c:pictureOptions, the bare-a:blipFill
15395
+ // impliedPicture case, and bar3D face targeting) lives in
15396
+ // chart-datapoint-picture-resolve.ts, split out to keep this file within the
15397
+ // repo's ~300-LOC limit: `resolveActiveDataPointPicture`,
15398
+ // `resolveDataPointPictureFill`, `resolveBarFaceTargets`, `DataPointPictureFill`,
15399
+ // `BarPictureFace`, `BarFaceTargets`.
15400
+
15401
+ /**
15402
+ * chart-datapoint-picture-resolve.ts: framework-agnostic resolution of a
15403
+ * data point's (or its series') picture fill into a paintable descriptor.
15404
+ *
15405
+ * Split out of `chart-datapoint-style.ts` to keep that file within the
15406
+ * repo's ~300-LOC limit: that file resolves per-point formatting in
15407
+ * general (fill colour, marker, explosion); this one owns specifically the
15408
+ * picture-fill half (`c:pictureOptions` AND the bare-`a:blipFill`
15409
+ * `impliedPicture` case, plus `bar3D` face targeting), which had grown into
15410
+ * its own coherent concern.
15411
+ *
15412
+ * @module chart-datapoint-picture-resolve
15413
+ */
15031
15414
  /** PowerPoint's own default picture-fill format when `c:pictureFormat` is absent. */
15032
15415
  const DEFAULT_PICTURE_FORMAT = 'stretch';
15033
15416
  /**
@@ -15036,14 +15419,28 @@ const DEFAULT_PICTURE_FORMAT = 'stretch';
15036
15419
  * `c:ser/c:pictureOptions` (C2-G9 series-level half). A point with its own
15037
15420
  * picture wins OUTRIGHT (not merged field-by-field with the series' flags),
15038
15421
  * matching how a `c:dPt/c:spPr` fully replaces the series' formatting
15039
- * elsewhere in this resolver ({@link resolveDataPointFill}).
15422
+ * elsewhere in this resolver (`resolveDataPointFill`, `chart-datapoint-style.ts`).
15423
+ *
15424
+ * `impliedPicture` (a bare `a:blipFill` with no `c:pictureOptions` sibling,
15425
+ * see `PptxChartDataPoint.impliedPicture`'s doc comment) is checked at the
15426
+ * SAME tier as `picture`, once `picture` itself did not resolve an image: a
15427
+ * point's own fill (explicit flags or implied) still wins outright over the
15428
+ * series', and vice versa.
15040
15429
  */
15041
15430
  function resolveActiveDataPointPicture(series, pointIndex) {
15042
- const pointPicture = findDataPoint(series, pointIndex)?.picture;
15043
- if (pointPicture?.imageUrl) {
15431
+ const point = findDataPoint(series, pointIndex);
15432
+ const pointPicture = point?.picture?.imageUrl
15433
+ ? point.picture
15434
+ : point?.impliedPicture?.imageUrl
15435
+ ? point.impliedPicture
15436
+ : undefined;
15437
+ if (pointPicture) {
15044
15438
  return pointPicture;
15045
15439
  }
15046
- return series.picture?.imageUrl ? series.picture : undefined;
15440
+ if (series.picture?.imageUrl) {
15441
+ return series.picture;
15442
+ }
15443
+ return series.impliedPicture?.imageUrl ? series.impliedPicture : undefined;
15047
15444
  }
15048
15445
  /**
15049
15446
  * Resolve which 3-D bar/column faces a picture fill paints
@@ -15101,6 +15498,7 @@ function resolveDataPointPictureFill(series, pointIndex, seriesIndex, face) {
15101
15498
  ...(format !== 'stretch' && picture.pictureStackUnit !== undefined
15102
15499
  ? { tileHeightPx: chartFontPx(picture.pictureStackUnit) }
15103
15500
  : {}),
15501
+ ...(picture.opacity !== undefined ? { opacity: picture.opacity } : {}),
15104
15502
  };
15105
15503
  }
15106
15504
 
@@ -15187,7 +15585,11 @@ function applyDataPointPictureFills(chartData, elementId, primitives) {
15187
15585
  }
15188
15586
  const patternId = `${elementId}-${resolved.patternId}`;
15189
15587
  defs.push(buildPictureFillPatternDef(patternId, resolved.imageUrl, resolved.format, primitive, resolved.tileHeightPx));
15190
- return { ...primitive, fill: `url(#${patternId})` };
15588
+ return {
15589
+ ...primitive,
15590
+ fill: `url(#${patternId})`,
15591
+ ...(resolved.opacity !== undefined ? { opacity: resolved.opacity } : {}),
15592
+ };
15191
15593
  });
15192
15594
  return { primitives: next, defs };
15193
15595
  }
@@ -15687,10 +16089,70 @@ function computeChartLegendLayout(vm) {
15687
16089
  fontWeight: textStyle?.bold ? 'bold' : 'normal',
15688
16090
  fontStyle: textStyle?.italic ? 'italic' : 'normal',
15689
16091
  fontFamily: textStyle?.fontFamily,
16092
+ ...(entry.lineSwatch ? { lineSwatch: entry.lineSwatch } : {}),
15690
16093
  };
15691
16094
  });
15692
16095
  }
15693
16096
 
16097
+ /** Legend swatch line width in px: a fixed icon size, independent of the plotted series' own stroke width. */
16098
+ const LEGEND_LINE_SWATCH_WIDTH = 2;
16099
+ /** Legend swatch marker radius in px: a fixed icon size, independent of the series' own `c:marker/c:size`. */
16100
+ const LEGEND_MARKER_RADIUS = 3;
16101
+ /** Vertical centre of the swatch box (matches the rect swatch's `y: -7, height: 10`). */
16102
+ const SWATCH_CENTER_Y = -2;
16103
+ /** Swatch box width in px (matches the rect swatch's `width: 10`). */
16104
+ const SWATCH_WIDTH = 10;
16105
+ /**
16106
+ * Which chart kinds get a line-style legend swatch instead of a filled rect.
16107
+ *
16108
+ * Deliberately narrow: only kinds whose whole plot is a drawn line/marker
16109
+ * (line, scatter). A filled radar or an area chart still reads as a filled
16110
+ * region in PowerPoint's own legend, so they keep the rect swatch; a
16111
+ * bar+line combo resolves its line series individually (see
16112
+ * `chart-combo.ts`), not through this whole-chart check.
16113
+ */
16114
+ function resolveLegendSwatchKind(kind) {
16115
+ return kind === 'line' || kind === 'scatter' ? 'line' : 'rect';
16116
+ }
16117
+ /**
16118
+ * Build the line-style legend swatch primitives for one series.
16119
+ *
16120
+ * The marker uses the series' OWN marker (`c:ser/c:marker`), never a `c:dPt`
16121
+ * per-point override: a legend sample represents the whole series, matching
16122
+ * PowerPoint. `pointIndex: -1` is not a real data point, so
16123
+ * {@link resolveDataPointMarker}'s `c:dPt` lookup never matches it. The marker
16124
+ * is always drawn at {@link LEGEND_MARKER_RADIUS}, ignoring the series' own
16125
+ * `c:marker/c:size`, so the legend icon stays a consistent small size
16126
+ * regardless of how large the plotted markers are.
16127
+ */
16128
+ function buildLineLegendSwatch(series, color, seriesPosition = 0) {
16129
+ const resolved = resolveDataPointMarker(series, -1, seriesPosition), primitives = [];
16130
+ if (!series.lineNoFill) {
16131
+ primitives.push({
16132
+ kind: 'line',
16133
+ x1: 0,
16134
+ y1: SWATCH_CENTER_Y,
16135
+ x2: SWATCH_WIDTH,
16136
+ y2: SWATCH_CENTER_Y,
16137
+ stroke: color,
16138
+ strokeWidth: LEGEND_LINE_SWATCH_WIDTH,
16139
+ });
16140
+ }
16141
+ const marker = buildMarkerPrimitive({
16142
+ symbol: resolved.symbol,
16143
+ // Deliberately ignore the series' authored size (see doc comment above).
16144
+ size: undefined,
16145
+ cx: SWATCH_WIDTH / 2,
16146
+ cy: SWATCH_CENTER_Y,
16147
+ fill: resolved.fill ?? color,
16148
+ defaultRadius: LEGEND_MARKER_RADIUS,
16149
+ });
16150
+ if (marker) {
16151
+ primitives.push(marker);
16152
+ }
16153
+ return { primitives };
16154
+ }
16155
+
15694
16156
  const DEFAULT_COLOR$3 = '#64748b';
15695
16157
  function dashArray(style, width) {
15696
16158
  if (!style || style === 'solid') {
@@ -15835,8 +16297,12 @@ function seriesColor(series, index, colorPalette) {
15835
16297
  /**
15836
16298
  * Automatic Y-axis range, on PowerPoint's terms. See `chart-axis-nice.ts`; this
15837
16299
  * mirrors `computeValueRange` in `chart-helpers.ts`.
16300
+ *
16301
+ * @param plotHeightPx The chart's plot-area height (`PlotLayout.plotHeight`),
16302
+ * when the caller has it; see `axisTargetIntervals`'s doc comment for why
16303
+ * this changes the gridline count PowerPoint's automatic scale picks.
15838
16304
  */
15839
- function computeValueRange(series) {
16305
+ function computeValueRange(series, plotHeightPx) {
15840
16306
  let dataMin = Number.POSITIVE_INFINITY, dataMax = Number.NEGATIVE_INFINITY;
15841
16307
  for (const item of series) {
15842
16308
  for (const value of item.values) {
@@ -15851,14 +16317,16 @@ function computeValueRange(series) {
15851
16317
  if (dataMin === Number.POSITIVE_INFINITY) {
15852
16318
  return { min: 0, max: 1, span: 1 };
15853
16319
  }
15854
- const { min, max, majorUnit } = niceValueAxisBounds(dataMin, dataMax);
16320
+ const { min, max, majorUnit } = niceValueAxisBounds(dataMin, dataMax, plotHeightPx !== undefined ? axisTargetIntervals(plotHeightPx) : undefined);
15855
16321
  return { min, max, span: Math.max(max - min, Number.EPSILON), majorUnit };
15856
16322
  }
15857
16323
  /**
15858
16324
  * Value range for a stacked bar: the per-category sums, then the same automatic
15859
16325
  * scale as any other value axis.
16326
+ *
16327
+ * @param plotHeightPx See `computeValueRange`'s parameter of the same name.
15860
16328
  */
15861
- function computeStackedValueRange(series, catCount) {
16329
+ function computeStackedValueRange(series, catCount, plotHeightPx) {
15862
16330
  let maxSum = 0, minSum = 0;
15863
16331
  for (let ci = 0; ci < catCount; ci++) {
15864
16332
  let pos = 0, neg = 0;
@@ -15874,7 +16342,7 @@ function computeStackedValueRange(series, catCount) {
15874
16342
  maxSum = Math.max(maxSum, pos);
15875
16343
  minSum = Math.min(minSum, neg);
15876
16344
  }
15877
- const { min, max, majorUnit } = niceValueAxisBounds(Math.min(minSum, 0), Math.max(maxSum, 0));
16345
+ const { min, max, majorUnit } = niceValueAxisBounds(Math.min(minSum, 0), Math.max(maxSum, 0), plotHeightPx !== undefined ? axisTargetIntervals(plotHeightPx) : undefined);
15878
16346
  return { min, max, span: Math.max(max - min, Number.EPSILON), majorUnit };
15879
16347
  }
15880
16348
  /**
@@ -15937,6 +16405,46 @@ function buildMarkTooltip(seriesName, categoryLabel, value, numberFormat) {
15937
16405
  return label.length > 0 ? `${label}: ${formatted}` : formatted;
15938
16406
  }
15939
16407
 
16408
+ /**
16409
+ * Build the base legend entries + anchor position for a chart view model.
16410
+ *
16411
+ * @param swatchKind Which legend entries draw a line + marker sample instead
16412
+ * of the default filled rect (see `chart-legend-swatch.ts`). A single value
16413
+ * applies to every entry (the whole-chart line/scatter case); an array
16414
+ * resolves per series index (a bar+line combo, where only the line series
16415
+ * get the line sample). Defaults to `'rect'` for every existing caller that
16416
+ * predates line-aware legends.
16417
+ */
16418
+ function buildLegend(series, colorPalette, svgWidth, legendPos, svgHeight, plotTop, swatchKind = 'rect') {
16419
+ const legend = series.map((s, i) => {
16420
+ const color = seriesColor(s, i, colorPalette), kind = Array.isArray(swatchKind) ? (swatchKind[i] ?? 'rect') : swatchKind;
16421
+ return {
16422
+ color,
16423
+ label: s.name,
16424
+ ...(kind === 'line' ? { lineSwatch: buildLineLegendSwatch(s, color, i) } : {}),
16425
+ };
16426
+ });
16427
+ let legendX = svgWidth / 2, legendY = svgHeight - 8, legendAnchor = 'middle';
16428
+ // `tr` shares `'r'`'s coordinates (a right-aligned column starting at
16429
+ // plotTop): that is already "top-right corner"; it just does not reserve
16430
+ // plot-area space the way a reserved `'r'` legend does (see computePlotLayout).
16431
+ const side = resolveLegendPlacement(legendPos).side;
16432
+ if (side === 'r') {
16433
+ legendX = svgWidth - 75;
16434
+ legendY = plotTop;
16435
+ legendAnchor = 'start';
16436
+ }
16437
+ else if (side === 'l') {
16438
+ legendX = 4;
16439
+ legendY = plotTop;
16440
+ legendAnchor = 'start';
16441
+ }
16442
+ else if (side === 't') {
16443
+ legendY = 28;
16444
+ }
16445
+ return { legend, legendX, legendY, legendAnchor };
16446
+ }
16447
+
15940
16448
  /**
15941
16449
  * chart-view-model-bars.ts: bar / column rectangle and line-point geometry of
15942
16450
  * the chart engine. Split out of `chart-view-model.ts`, which re-exports
@@ -16933,119 +17441,6 @@ function smoothLinePath(points) {
16933
17441
  return d;
16934
17442
  }
16935
17443
 
16936
- /**
16937
- * chart-marker-shape.ts: framework-agnostic marker-symbol primitive builder for
16938
- * line / scatter / area chart data points.
16939
- *
16940
- * OOXML line/scatter series carry a `c:marker` with a `c:symbol` (circle,
16941
- * diamond, square, triangle, star, x, plus, dot, dash, none, ...) and an
16942
- * optional `c:size` (2-72 points). The base view-model historically drew every
16943
- * data point as a fixed-radius circle, ignoring both. This helper resolves the
16944
- * parsed marker into the correct `SvgPrimitive` at the requested size, and
16945
- * returns `null` for `symbol === 'none'` so no dot is drawn.
16946
- *
16947
- * @module chart-marker-shape
16948
- */
16949
- /** Resolve the drawn radius (px) from the parsed point size or the default. */
16950
- function markerRadius(size, defaultRadius) {
16951
- if (size === undefined || !Number.isFinite(size) || size <= 0) {
16952
- return defaultRadius;
16953
- }
16954
- // OOXML marker size is a point diameter; treat 1pt ~ 1px at chart scale.
16955
- return size / 2;
16956
- }
16957
- /** Build the SVG points string for a regular/irregular polygon vertex list. */
16958
- function polygonPoints(vertices) {
16959
- return vertices.map(([x, y]) => `${x.toFixed(2)},${y.toFixed(2)}`).join(' ');
16960
- }
16961
- function starVertices(cx, cy, r) {
16962
- const inner = r * 0.5;
16963
- const out = [];
16964
- for (let i = 0; i < 10; i++) {
16965
- const radius = i % 2 === 0 ? r : inner;
16966
- const angle = -Math.PI / 2 + (Math.PI * i) / 5;
16967
- out.push([cx + radius * Math.cos(angle), cy + radius * Math.sin(angle)]);
16968
- }
16969
- return out;
16970
- }
16971
- /**
16972
- * Build the marker primitive for one data point, honouring `symbol` and `size`.
16973
- * Returns `null` when `symbol === 'none'` (draw nothing).
16974
- */
16975
- function buildMarkerPrimitive(input) {
16976
- const { symbol, cx, cy, fill, part } = input;
16977
- if (symbol === 'none') {
16978
- return null;
16979
- }
16980
- const r = markerRadius(input.size, input.defaultRadius);
16981
- const stroke = fill;
16982
- switch (symbol) {
16983
- case 'square':
16984
- return { kind: 'rect', x: cx - r, y: cy - r, w: r * 2, h: r * 2, fill, part };
16985
- case 'dash':
16986
- return { kind: 'rect', x: cx - r, y: cy - r * 0.32, w: r * 2, h: r * 0.64, fill, part };
16987
- case 'diamond':
16988
- return {
16989
- kind: 'polygon',
16990
- points: polygonPoints([
16991
- [cx, cy - r],
16992
- [cx + r, cy],
16993
- [cx, cy + r],
16994
- [cx - r, cy],
16995
- ]),
16996
- fill,
16997
- stroke,
16998
- strokeWidth: 0,
16999
- part,
17000
- };
17001
- case 'triangle':
17002
- return {
17003
- kind: 'polygon',
17004
- points: polygonPoints([
17005
- [cx, cy - r],
17006
- [cx + r * 0.9, cy + r * 0.75],
17007
- [cx - r * 0.9, cy + r * 0.75],
17008
- ]),
17009
- fill,
17010
- stroke,
17011
- strokeWidth: 0,
17012
- part,
17013
- };
17014
- case 'star':
17015
- return {
17016
- kind: 'polygon',
17017
- points: polygonPoints(starVertices(cx, cy, r)),
17018
- fill,
17019
- stroke,
17020
- strokeWidth: 0,
17021
- part,
17022
- };
17023
- case 'plus':
17024
- return {
17025
- kind: 'path',
17026
- d: `M${(cx - r).toFixed(2)},${cy.toFixed(2)} L${(cx + r).toFixed(2)},${cy.toFixed(2)} M${cx.toFixed(2)},${(cy - r).toFixed(2)} L${cx.toFixed(2)},${(cy + r).toFixed(2)}`,
17027
- fill: 'none',
17028
- stroke,
17029
- strokeWidth: Math.max(1, r * 0.4),
17030
- part,
17031
- };
17032
- case 'x':
17033
- return {
17034
- kind: 'path',
17035
- d: `M${(cx - r).toFixed(2)},${(cy - r).toFixed(2)} L${(cx + r).toFixed(2)},${(cy + r).toFixed(2)} M${(cx + r).toFixed(2)},${(cy - r).toFixed(2)} L${(cx - r).toFixed(2)},${(cy + r).toFixed(2)}`,
17036
- fill: 'none',
17037
- stroke,
17038
- strokeWidth: Math.max(1, r * 0.4),
17039
- part,
17040
- };
17041
- case 'dot':
17042
- return { kind: 'circle', cx, cy, r: Math.max(r * 0.6, 1), fill, part };
17043
- default:
17044
- // circle / auto / picture / undefined -> filled circle.
17045
- return { kind: 'circle', cx, cy, r, fill, part };
17046
- }
17047
- }
17048
-
17049
17444
  /**
17050
17445
  * Chart-element pixel box a manual `c:dLbl/c:layout` drag is measured
17051
17446
  * against. Exported for `chart-cartesian-bubbles.ts`, which shares this
@@ -17073,8 +17468,8 @@ function xyMarkTooltip(seriesName, xVal, yVal, numberFormat) {
17073
17468
  * fewer points than the series declares and the override must still land on the
17074
17469
  * point the author picked.
17075
17470
  */
17076
- function pushMarker(out, series, pointIndex, cx, cy, fill, defaultRadius, part, opacity, title) {
17077
- const marker = resolveDataPointMarker(series, pointIndex), m = buildMarkerPrimitive({
17471
+ function pushMarker(out, series, pointIndex, cx, cy, fill, defaultRadius, part, opacity, title, seriesPosition = part.seriesIndex ?? 0) {
17472
+ const marker = resolveDataPointMarker(series, pointIndex, seriesPosition), m = buildMarkerPrimitive({
17078
17473
  symbol: marker.symbol,
17079
17474
  size: marker.size,
17080
17475
  cx,
@@ -17635,14 +18030,31 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
17635
18030
  varyColorsSingle = chartData.varyColors === true && series.length === 1;
17636
18031
  if (grouping === 'clustered') {
17637
18032
  const seriesCount = Math.max(series.length, 1), barGroupWidth = layout.plotWidth / Math.max(catCount, 1),
18033
+ // Honour c:overlap (% overlap between adjacent series). overlap=0 reproduces
18034
+ // the original side-by-side layout exactly. Read before singleBarWidth: a
18035
+ // gapWidth-based bar width must size itself so the OVERLAPPED cluster (not
18036
+ // seriesCount side-by-side bars) fills the gap-reduced group width; see the
18037
+ // comment below.
18038
+ overlap = chartData.barOverlap ?? 0,
17638
18039
  // Honour c:gapWidth (gap between clusters, % of a bar width) when parsed;
17639
18040
  // otherwise keep the legacy 0.7-of-group heuristic byte-for-byte.
17640
- singleBarWidth = chartData.barGapWidth !== undefined
17641
- ? barGroupWidth / (seriesCount + Math.max(chartData.barGapWidth, 0) / 100)
17642
- : (barGroupWidth * 0.7) / seriesCount,
17643
- // Honour c:overlap (% overlap between adjacent series). overlap=0 reproduces
17644
- // the original side-by-side layout exactly.
17645
- overlap = chartData.barOverlap ?? 0, step = singleBarWidth * (1 - overlap / 100), clusterWidth = singleBarWidth + step * (seriesCount - 1), groupOffset = (barGroupWidth - clusterWidth) / 2;
18041
+ //
18042
+ // COM-verified ground truth (PowerPoint Object 16, single category, six
18043
+ // series, gapWidth=5, overlap=23): dividing by seriesCount alone (the
18044
+ // pre-existing formula) sizes every bar as if the cluster were laid out
18045
+ // SIDE BY SIDE, then shrinks it further by overlap when computing `step`
18046
+ // below - so at high overlap the bars render far too NARROW (in the
18047
+ // limit, overlap=100 should make every bar in a cluster the same width
18048
+ // as a single-series bar, independent of seriesCount, since they fully
18049
+ // coincide; the old formula kept shrinking by 1/seriesCount regardless).
18050
+ // The fix: first shrink the group width by the gap (exactly as before),
18051
+ // then divide by how many bar-widths the OVERLAPPED cluster actually
18052
+ // spans (`1 + (seriesCount - 1) * (1 - overlap / 100)`, the same
18053
+ // relationship `clusterWidth` below re-derives from `step`), not by the
18054
+ // raw series count.
18055
+ overlapSpan = 1 + (seriesCount - 1) * (1 - overlap / 100), singleBarWidth = chartData.barGapWidth !== undefined
18056
+ ? barGroupWidth / ((1 + Math.max(chartData.barGapWidth, 0) / 100) * overlapSpan)
18057
+ : (barGroupWidth * 0.7) / seriesCount, step = singleBarWidth * (1 - overlap / 100), clusterWidth = singleBarWidth + step * (seriesCount - 1), groupOffset = (barGroupWidth - clusterWidth) / 2;
17646
18058
  for (let displayIndex = 0; displayIndex < catCount; displayIndex++) {
17647
18059
  const sourceIndex = sourceIndices[displayIndex] ?? displayIndex;
17648
18060
  for (let si = 0; si < series.length; si++) {
@@ -19074,11 +19486,15 @@ function barFill(chartData, series, seriesIndex, pointIndex) {
19074
19486
  * clustered / stacked / percentStacked geometry with the axes transposed.
19075
19487
  */
19076
19488
  function buildHorizontalBarViewModel(element, chartData, categoryLabels) {
19077
- const layout = computePlotLayout(element.width, element.height, chartData, true), catCount = Math.max(categoryLabels.length, 1), series = chartData.series, grouping = chartData.grouping ?? 'clustered', isStacked = grouping === 'stacked' || grouping === 'percentStacked', isPercent = grouping === 'percentStacked', range = isPercent
19489
+ const layout = computePlotLayout(element.width, element.height, chartData, true), catCount = Math.max(categoryLabels.length, 1), series = chartData.series, grouping = chartData.grouping ?? 'clustered', isStacked = grouping === 'stacked' || grouping === 'percentStacked', isPercent = grouping === 'percentStacked',
19490
+ // The value axis runs along the WIDTH here (categories run down the left,
19491
+ // values along the bottom: this builder transposes the column engine), so
19492
+ // the gridline-count budget is keyed off plotWidth, not plotHeight.
19493
+ range = isPercent
19078
19494
  ? { min: 0, max: 100, span: 100 }
19079
19495
  : isStacked
19080
- ? computeStackedValueRange(series, catCount)
19081
- : computeValueRangeForChart(series, chartData.axes), { gridlines, axisLabels } = buildTransposedValueAxis(range, layout), zeroX = valueToX(0, range, layout.plotLeft, layout.plotRight), zeroLine = range.min < 0 && range.max > 0
19496
+ ? computeStackedValueRange(series, catCount, layout.plotWidth)
19497
+ : computeValueRangeForChart(series, chartData.axes, layout.plotWidth), { gridlines, axisLabels } = buildTransposedValueAxis(range, layout), zeroX = valueToX(0, range, layout.plotLeft, layout.plotRight), zeroLine = range.min < 0 && range.max > 0
19082
19498
  ? {
19083
19499
  kind: 'line',
19084
19500
  x1: zeroX,
@@ -19090,9 +19506,14 @@ function buildHorizontalBarViewModel(element, chartData, categoryLabels) {
19090
19506
  }
19091
19507
  : undefined, primitives = [], dataLabels = [], showLabels = chartData.style?.hasDataLabels, band = layout.plotHeight / catCount;
19092
19508
  if (!isStacked) {
19093
- const seriesCount = Math.max(series.length, 1), singleBarHeight = chartData.barGapWidth !== undefined
19094
- ? band / (seriesCount + Math.max(chartData.barGapWidth, 0) / 100)
19095
- : (band * 0.7) / seriesCount, overlap = chartData.barOverlap ?? 0, step = singleBarHeight * (1 - overlap / 100), clusterHeight = singleBarHeight + step * (seriesCount - 1), groupOffset = (band - clusterHeight) / 2;
19509
+ const seriesCount = Math.max(series.length, 1),
19510
+ // See chart-cartesian-bars.ts's `buildBars` (clustered branch) for the
19511
+ // COM-verified reasoning: the bar's own size must divide the gap-reduced
19512
+ // band by how many bar-heights the OVERLAPPED cluster spans, not by the
19513
+ // raw series count, or a high-overlap cluster renders far too thin.
19514
+ overlap = chartData.barOverlap ?? 0, overlapSpan = 1 + (seriesCount - 1) * (1 - overlap / 100), singleBarHeight = chartData.barGapWidth !== undefined
19515
+ ? band / ((1 + Math.max(chartData.barGapWidth, 0) / 100) * overlapSpan)
19516
+ : (band * 0.7) / seriesCount, step = singleBarHeight * (1 - overlap / 100), clusterHeight = singleBarHeight + step * (seriesCount - 1), groupOffset = (band - clusterHeight) / 2;
19096
19517
  for (let ci = 0; ci < catCount; ci++) {
19097
19518
  for (let si = 0; si < series.length; si++) {
19098
19519
  const val = series[si].values[ci] ?? 0, y = layout.plotTop + band * ci + groupOffset + step * si, valX = valueToX(val, range, layout.plotLeft, layout.plotRight), x = Math.min(zeroX, valX), w = Math.max(Math.abs(valX - zeroX), 1);
@@ -19789,11 +20210,11 @@ function computeTrendlinePrimitives(chartData, catCount, layout, range, mode = '
19789
20210
  * @module chart-overlays
19790
20211
  */
19791
20212
 
19792
- function stackedRange(chartData, catCount, isPercent) {
20213
+ function stackedRange(chartData, catCount, isPercent, plotHeightPx) {
19793
20214
  if (isPercent) {
19794
20215
  return { min: 0, max: 100, span: 100 };
19795
20216
  }
19796
- return computeStackedValueRange(chartData.series, catCount);
20217
+ return computeStackedValueRange(chartData.series, catCount, plotHeightPx);
19797
20218
  }
19798
20219
  /**
19799
20220
  * Build the enriched cartesian view-model for bar / line / area / scatter /
@@ -19844,13 +20265,13 @@ function buildCartesianViewModel(element, chartData, categoryLabels, kind) {
19844
20265
  // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
19845
20266
  const primaryRange = isStacked
19846
20267
  ? {
19847
- ...stackedRange(chartData, catCount, isPercent),
20268
+ ...stackedRange(chartData, catCount, isPercent, layout.plotHeight),
19848
20269
  ...(primaryAxis?.orientation === 'maxMin' ? { reverseOrder: true } : {}),
19849
20270
  }
19850
- : computeValueRangeForChart(primaryPlotSeries.length > 0 ? primaryPlotSeries : chartData.series, chartData.axes);
20271
+ : computeValueRangeForChart(primaryPlotSeries.length > 0 ? primaryPlotSeries : chartData.series, chartData.axes, layout.plotHeight);
19851
20272
  // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
19852
20273
  const secondaryRange = useSecondary && secondaryPlotSeries.length > 0
19853
- ? computeValueRangeForAxis(secondaryPlotSeries, chartData.axes?.find((axis) => axis.axisType === 'valAx' && axis.axPos === 'r'))
20274
+ ? computeValueRangeForAxis(secondaryPlotSeries, chartData.axes?.find((axis) => axis.axisType === 'valAx' && axis.axPos === 'r'), layout.plotHeight)
19854
20275
  : undefined;
19855
20276
  // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
19856
20277
  const axisRes = buildCartesianAxes(chartData, layout, primaryRange, secondaryRange, catCount);
@@ -19863,7 +20284,7 @@ function buildCartesianViewModel(element, chartData, categoryLabels, kind) {
19863
20284
  // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
19864
20285
  const legendPos = chartData.style?.legendPosition ?? 'b';
19865
20286
  // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
19866
- const { legend, legendX, legendY, legendAnchor } = buildLegend(chartData.series, chartData.colorPalette, layout.svgWidth, legendPos, layout.svgHeight, layout.plotTop);
20287
+ const { legend, legendX, legendY, legendAnchor } = buildLegend(chartData.series, chartData.colorPalette, layout.svgWidth, legendPos, layout.svgHeight, layout.plotTop, resolveLegendSwatchKind(kind));
19867
20288
  let plot;
19868
20289
  if (kind === 'bar') {
19869
20290
  plot = buildBars(chartData, catCount, layout, primaryRange, secondaryRange, secondaryIdx, isStacked ? (isPercent ? 'percentStacked' : 'stacked') : 'clustered', sourceIndices);
@@ -20075,9 +20496,9 @@ function buildComboViewModel(element, chartData, categoryLabels) {
20075
20496
  const primaryAxisId = getPrimaryValueAxisId(chartData.axes);
20076
20497
  const primaryAxis = chartData.axes?.find((axis) => axis.axisId === primaryAxisId);
20077
20498
  const secondaryAxisFormatting = getSecondaryValueAxis(chartData.axes);
20078
- const primaryRange = computeValueRangeForAxis(primarySeries, primaryAxis);
20499
+ const primaryRange = computeValueRangeForAxis(primarySeries, primaryAxis, layout.plotHeight);
20079
20500
  const secondaryRange = secondary.length > 0
20080
- ? computeValueRangeForAxis(secondary.map((entry) => entry.series), secondaryAxisFormatting)
20501
+ ? computeValueRangeForAxis(secondary.map((entry) => entry.series), secondaryAxisFormatting, layout.plotHeight)
20081
20502
  : undefined;
20082
20503
  const primaryCategoryAxis = chartData.axes?.find((axis) => (axis.axisType === 'catAx' || axis.axisType === 'dateAx') &&
20083
20504
  axis.axisId === primaryAxis?.crossAxisId);
@@ -20096,7 +20517,11 @@ function buildComboViewModel(element, chartData, categoryLabels) {
20096
20517
  const horizontalAxis = buildCartesianHorizontalAxis(chartData, categoryLabels, layout, 'combo', primaryRange, secondaryRange);
20097
20518
  const sourceIndices = horizontalAxis.sourceIndices;
20098
20519
  const legendPos = chartData.style?.legendPosition ?? 'b';
20099
- const { legend, legendX, legendY, legendAnchor } = buildLegend(chartData.series, chartData.colorPalette, layout.svgWidth, legendPos, layout.svgHeight, layout.plotTop);
20520
+ // The combo layout below is fixed: series 0 is the bar, every other series
20521
+ // is a line (see `barSeries`/`lineSeries` just below), so the legend swatch
20522
+ // follows the same split (bar -> rect, line -> line+marker sample).
20523
+ const comboSwatchKinds = chartData.series.map((_s, i) => i === 0 ? 'rect' : 'line');
20524
+ const { legend, legendX, legendY, legendAnchor } = buildLegend(chartData.series, chartData.colorPalette, layout.svgWidth, legendPos, layout.svgHeight, layout.plotTop, comboSwatchKinds);
20100
20525
  const primitives = [];
20101
20526
  const dataLabels = [];
20102
20527
  // Drop / hi-low / up-down helper lines, drawn behind the combo marks.
@@ -20232,7 +20657,7 @@ function buildStockViewModel(element, chartData, categoryLabels) {
20232
20657
  // Through `computeValueRangeForChart`, not the bare linear helper: a stock
20233
20658
  // chart is as entitled to a log or display-unit value axis as any other
20234
20659
  // cartesian kind, and the bare helper silently ignores `c:scaling`.
20235
- const range = computeValueRangeForChart(chartData.series, chartData.axes);
20660
+ const range = computeValueRangeForChart(chartData.series, chartData.axes, layout.plotHeight);
20236
20661
  const valueAxis = chartData.axes?.find((axis) => axis.axisType === 'valAx' && axis.axPos !== 'r');
20237
20662
  const categoryAxis = chartData.axes?.find((axis) => (axis.axisType === 'catAx' || axis.axisType === 'dateAx') &&
20238
20663
  axis.axisId === valueAxis?.crossAxisId);
@@ -21053,22 +21478,26 @@ function buildSunburstViewModel(element, chartData, categoryLabels) {
21053
21478
  }
21054
21479
 
21055
21480
  /**
21056
- * Filter deleted entries and attach per-entry text-style overrides.
21057
- * Returns the input array unchanged (same reference) when there is nothing to
21058
- * apply, so callers with no `c:legendEntry` overrides pay no extra cost.
21481
+ * Filter deleted entries and attach per-entry text-style overrides, falling
21482
+ * back to the chart-level legend default (`PptxChartStyle.legendTextStyle`,
21483
+ * itself `c:legend/c:txPr` or `c:chartSpace/c:txPr`) for any entry that has no
21484
+ * `c:legendEntry/c:txPr` override of its own. Returns the input array
21485
+ * unchanged (same reference) when there is nothing to apply, so a chart with
21486
+ * neither kind of override pays no extra cost.
21059
21487
  */
21060
- function applyLegendEntryOverrides(legend, entries) {
21061
- if (!entries || entries.length === 0) {
21488
+ function applyLegendEntryOverrides(legend, entries, defaultTextStyle) {
21489
+ if ((!entries || entries.length === 0) && !defaultTextStyle) {
21062
21490
  return legend;
21063
21491
  }
21064
- const byIndex = new Map(entries.map((entry) => [entry.index, entry]));
21492
+ const byIndex = new Map((entries ?? []).map((entry) => [entry.index, entry]));
21065
21493
  const result = [];
21066
21494
  legend.forEach((item, index) => {
21067
21495
  const override = byIndex.get(index);
21068
21496
  if (override?.deleted) {
21069
21497
  return;
21070
21498
  }
21071
- result.push(override?.textStyle ? { ...item, textStyle: override.textStyle } : item);
21499
+ const textStyle = override?.textStyle ?? defaultTextStyle;
21500
+ result.push(textStyle ? { ...item, textStyle } : item);
21072
21501
  });
21073
21502
  return result;
21074
21503
  }
@@ -21467,7 +21896,7 @@ function buildSurfaceWallPanels(cols, rows, cellSize, zScale, offsetX, offsetY,
21467
21896
  */
21468
21897
  function buildIsometricSurfaceViewModel(element, chartData, categoryLabels) {
21469
21898
  const layout = computePlotLayout(element.width, element.height, chartData, false);
21470
- const range = computeValueRange(chartData.series);
21899
+ const range = computeValueRange(chartData.series, layout.plotHeight);
21471
21900
  const catCount = Math.max(categoryLabels.length, 1);
21472
21901
  const seriesCount = chartData.series.length;
21473
21902
  // Grid cell count (vertices = cells + 1 in each dimension).
@@ -21613,7 +22042,7 @@ function buildIsometricSurfaceViewModel(element, chartData, categoryLabels) {
21613
22042
  */
21614
22043
  function buildFlatSurfaceViewModel(element, chartData, categoryLabels) {
21615
22044
  const layout = computePlotLayout(element.width, element.height, chartData, false);
21616
- const range = computeValueRange(chartData.series);
22045
+ const range = computeValueRange(chartData.series, layout.plotHeight);
21617
22046
  const catCount = Math.max(categoryLabels.length, 1);
21618
22047
  const seriesCount = chartData.series.length;
21619
22048
  const cellW = layout.plotWidth / Math.max(catCount - 1, 1);
@@ -22712,9 +23141,12 @@ function buildPieViewModel(element, chartData, categoryLabels, isDoughnut) {
22712
23141
  }
22713
23142
 
22714
23143
  /**
22715
- * chart-view-model-layout.ts: plot-area layout and chart chrome (gridlines,
22716
- * axis / category labels, legend placement) of the chart engine. Split out of
22717
- * `chart-view-model.ts`, which re-exports everything here.
23144
+ * chart-view-model-layout.ts: plot-area layout math for the chart engine
23145
+ * (the plot inset, legend/title/data-table/secondary-axis reservations, and
23146
+ * `c:plotArea/c:layout/c:manualLayout` overrides). Chart chrome (gridlines,
23147
+ * axis / category labels) lives in `chart-view-model-chrome.ts`; legend
23148
+ * building lives in `chart-legend-build.ts`. All three are re-exported by
23149
+ * `chart-view-model.ts`.
22718
23150
  *
22719
23151
  * @module chart-view-model-layout
22720
23152
  */
@@ -22753,6 +23185,16 @@ function computePlotLayout(elementWidth, elementHeight, chartData, hasAxes, opti
22753
23185
  // a 475x174 frame got a 475x180 viewBox, squeezing y by 0.967).
22754
23186
  const svgWidth = Math.max(1, elementWidth), svgHeight = Math.max(1, elementHeight);
22755
23187
  let plotLeft = hasAxes ? 48 : 8, plotTop = 8, plotRight = svgWidth - 8, plotBottom = svgHeight - (hasAxes ? categoryAxisBand(chartData) : 8);
23188
+ // `c:catAx/c:axPos val="t"` on the PRIMARY (and, in the common case, only)
23189
+ // category axis: PowerPoint draws that axis's tick labels above the plot,
23190
+ // not below it. Reserving the label band at the bottom regardless of
23191
+ // `axPos` (the historical behaviour here) leaves no room at the top, so the
23192
+ // labels get drawn outside the chart's own SVG bounds and clip against its
23193
+ // edge. Move the reservation to match where the labels actually land.
23194
+ if (hasAxes && options?.categoryAxisAtTop) {
23195
+ plotBottom = svgHeight - 8;
23196
+ plotTop += categoryAxisBand(chartData);
23197
+ }
22756
23198
  const style = chartData.style, legendPos = style?.legendPosition ?? 'b';
22757
23199
  if (style?.hasTitle) {
22758
23200
  plotTop += 20;
@@ -22808,121 +23250,6 @@ function computePlotLayout(elementWidth, elementHeight, chartData, hasAxes, opti
22808
23250
  plotHeight,
22809
23251
  };
22810
23252
  }
22811
- // ─────────────────────────────────────────────────────────────────────────────
22812
- // Chrome helpers
22813
- // ─────────────────────────────────────────────────────────────────────────────
22814
- const GRIDLINE_COLOR$1 = '#e2e8f0';
22815
- const AXIS_LABEL_COLOR = '#64748b';
22816
- const ZERO_LINE_COLOR = '#94a3b8';
22817
- const TICK_COUNT$1 = 5;
22818
- /**
22819
- * Tick values for a range: one per major unit when the automatic scale supplied
22820
- * one (it snapped the bounds to whole multiples, so this lands on round numbers
22821
- * exactly as PowerPoint does), otherwise an even division of the span.
22822
- * Exported for the transposed (horizontal-bar) axis builder.
22823
- */
22824
- function axisTickValues(range) {
22825
- const unit = range.majorUnit;
22826
- if (unit !== undefined && Number.isFinite(unit) && unit > 0 && !range.logScale) {
22827
- const steps = Math.round((range.max - range.min) / unit);
22828
- if (steps >= 1 && steps <= 100) {
22829
- return Array.from({ length: steps + 1 }, (_unused, index) => range.min + unit * index);
22830
- }
22831
- }
22832
- return Array.from({ length: TICK_COUNT$1 + 1 }, (_unused, index) => range.min + (range.span / TICK_COUNT$1) * index);
22833
- }
22834
- function buildGridlinesAndLabels(range, layout,
22835
- /**
22836
- * `false` keeps the tick labels but draws no gridlines: a value axis whose
22837
- * `c:majorGridlines` is absent (see `shouldRenderMajorGridlines`).
22838
- */
22839
- showMajorGridlines = true) {
22840
- const gridlines = [], axisLabels = [];
22841
- for (const val of axisTickValues(range)) {
22842
- const y = valueToY(val, range, layout.plotTop, layout.plotBottom);
22843
- if (showMajorGridlines) {
22844
- gridlines.push({
22845
- kind: 'line',
22846
- x1: layout.plotLeft,
22847
- y1: y,
22848
- x2: layout.plotRight,
22849
- y2: y,
22850
- stroke: GRIDLINE_COLOR$1,
22851
- strokeWidth: 1,
22852
- });
22853
- }
22854
- axisLabels.push({
22855
- kind: 'text',
22856
- x: layout.plotLeft - 4,
22857
- y,
22858
- text: formatAxisValue(val),
22859
- fontSize: DEFAULT_CHART_TEXT_PX,
22860
- fill: AXIS_LABEL_COLOR,
22861
- textAnchor: 'end',
22862
- dominantBaseline: 'central',
22863
- });
22864
- }
22865
- return { gridlines, axisLabels };
22866
- }
22867
- function buildZeroLine(range, layout) {
22868
- if (range.min >= 0 || range.max <= 0) {
22869
- return undefined;
22870
- }
22871
- const y = valueToY(0, range, layout.plotTop, layout.plotBottom);
22872
- return {
22873
- kind: 'line',
22874
- x1: layout.plotLeft,
22875
- y1: y,
22876
- x2: layout.plotRight,
22877
- y2: y,
22878
- stroke: ZERO_LINE_COLOR,
22879
- strokeWidth: 1,
22880
- };
22881
- }
22882
- function buildCategoryLabels(categoryLabels, layout, catSpacing) {
22883
- const catCount = Math.max(categoryLabels.length, 1);
22884
- return categoryLabels.map((label, i) => {
22885
- const x = catSpacing === 'bar'
22886
- ? layout.plotLeft + (layout.plotWidth / catCount) * (i + 0.5)
22887
- : catCount > 1
22888
- ? layout.plotLeft + (layout.plotWidth / (catCount - 1)) * i
22889
- : layout.plotLeft + layout.plotWidth / 2;
22890
- return {
22891
- kind: 'text',
22892
- x,
22893
- y: layout.plotBottom + 12,
22894
- text: label,
22895
- fontSize: DEFAULT_CHART_TEXT_PX,
22896
- fill: AXIS_LABEL_COLOR,
22897
- textAnchor: 'middle',
22898
- };
22899
- });
22900
- }
22901
- function buildLegend(series, colorPalette, svgWidth, legendPos, svgHeight, plotTop) {
22902
- const legend = series.map((s, i) => ({
22903
- color: seriesColor(s, i, colorPalette),
22904
- label: s.name,
22905
- }));
22906
- let legendX = svgWidth / 2, legendY = svgHeight - 8, legendAnchor = 'middle';
22907
- // `tr` shares `'r'`'s coordinates (a right-aligned column starting at
22908
- // plotTop): that is already "top-right corner"; it just does not reserve
22909
- // plot-area space the way a reserved `'r'` legend does (see computePlotLayout).
22910
- const side = resolveLegendPlacement(legendPos).side;
22911
- if (side === 'r') {
22912
- legendX = svgWidth - 75;
22913
- legendY = plotTop;
22914
- legendAnchor = 'start';
22915
- }
22916
- else if (side === 'l') {
22917
- legendX = 4;
22918
- legendY = plotTop;
22919
- legendAnchor = 'start';
22920
- }
22921
- else if (side === 't') {
22922
- legendY = 28;
22923
- }
22924
- return { legend, legendX, legendY, legendAnchor };
22925
- }
22926
23253
 
22927
23254
  /**
22928
23255
  * chart-view-model-radar.ts: the radar / spider view-model builder. Split out
@@ -23924,14 +24251,20 @@ function buildWaterfallSteps(values, options) {
23924
24251
  }
23925
24252
  return steps;
23926
24253
  }
23927
- /** Compute a value range containing every cumulative and absolute bar endpoint. */
23928
- function computeWaterfallRange(steps) {
24254
+ /**
24255
+ * Compute a value range containing every cumulative and absolute bar endpoint.
24256
+ *
24257
+ * @param plotHeightPx The chart's plot-area height, when known; see
24258
+ * `axisTargetIntervals`'s doc comment for why this changes the automatic
24259
+ * scale's gridline count.
24260
+ */
24261
+ function computeWaterfallRange(steps, plotHeightPx) {
23929
24262
  return computeValueRange([
23930
24263
  {
23931
24264
  name: 'Waterfall range',
23932
24265
  values: steps.flatMap((step) => [step.startValue, step.endValue]),
23933
24266
  },
23934
- ]);
24267
+ ], plotHeightPx);
23935
24268
  }
23936
24269
 
23937
24270
  /**
@@ -23974,7 +24307,7 @@ function buildWaterfallViewModel(element, chartData, categoryLabels) {
23974
24307
  const series = chartData.series[0];
23975
24308
  const values = series?.values ?? [];
23976
24309
  const steps = buildWaterfallSteps(values, series?.waterfallOptions);
23977
- const range = computeWaterfallRange(steps);
24310
+ const range = computeWaterfallRange(steps, layout.plotHeight);
23978
24311
  const catCount = Math.max(categoryLabels.length, values.length, 1);
23979
24312
  const barWidth = (layout.plotWidth / catCount) * 0.6;
23980
24313
  const gap = (layout.plotWidth / catCount) * 0.2;
@@ -24173,7 +24506,7 @@ function withDataPointPictureFills(vm, chartData, elementId) {
24173
24506
  * `chart-legend-entries.ts`.
24174
24507
  */
24175
24508
  function withLegendEntries(vm, chartData) {
24176
- const legend = applyLegendEntryOverrides(vm.legend, chartData.style?.legendEntries);
24509
+ const legend = applyLegendEntryOverrides(vm.legend, chartData.style?.legendEntries, chartData.style?.legendTextStyle);
24177
24510
  return legend === vm.legend ? vm : { ...vm, legend };
24178
24511
  }
24179
24512
  /**
@@ -24258,6 +24591,107 @@ function buildFlatViewModel(element, chartData, categoryLabels, kind) {
24258
24591
  return buildCartesianViewModel(element, chartData, categoryLabels, kind);
24259
24592
  }
24260
24593
 
24594
+ /**
24595
+ * chart-view-model-chrome.ts: chart chrome (value-axis gridlines/labels, zero
24596
+ * line, category labels) shared by the cartesian chart kinds. Split out of
24597
+ * `chart-view-model-layout.ts` to keep that module's plot layout math under
24598
+ * the repo's 300-LOC file guideline; re-exported by `chart-view-model.ts`.
24599
+ * Legend building (`buildLegend`) lives in `chart-legend-build.ts`, not here.
24600
+ *
24601
+ * @module chart-view-model-chrome
24602
+ */
24603
+ /* eslint-disable one-var -- this module predates the rule and combining every
24604
+ sibling `const`/`let` in a function into one comma-list (oxlint's own
24605
+ `--fix` cannot do this safely once a non-declaration statement sits between
24606
+ them) would churn geometry code far beyond this change's scope. */
24607
+ const GRIDLINE_COLOR$1 = '#e2e8f0';
24608
+ const AXIS_LABEL_COLOR = '#64748b';
24609
+ const ZERO_LINE_COLOR = '#94a3b8';
24610
+ const TICK_COUNT$1 = 5;
24611
+ /**
24612
+ * Tick values for a range: one per major unit when the automatic scale supplied
24613
+ * one (it snapped the bounds to whole multiples, so this lands on round numbers
24614
+ * exactly as PowerPoint does), otherwise an even division of the span.
24615
+ * Exported for the transposed (horizontal-bar) axis builder.
24616
+ */
24617
+ function axisTickValues(range) {
24618
+ const unit = range.majorUnit;
24619
+ if (unit !== undefined && Number.isFinite(unit) && unit > 0 && !range.logScale) {
24620
+ const steps = Math.round((range.max - range.min) / unit);
24621
+ if (steps >= 1 && steps <= 100) {
24622
+ return Array.from({ length: steps + 1 }, (_unused, index) => range.min + unit * index);
24623
+ }
24624
+ }
24625
+ return Array.from({ length: TICK_COUNT$1 + 1 }, (_unused, index) => range.min + (range.span / TICK_COUNT$1) * index);
24626
+ }
24627
+ function buildGridlinesAndLabels(range, layout,
24628
+ /**
24629
+ * `false` keeps the tick labels but draws no gridlines: a value axis whose
24630
+ * `c:majorGridlines` is absent (see `shouldRenderMajorGridlines`).
24631
+ */
24632
+ showMajorGridlines = true) {
24633
+ const gridlines = [], axisLabels = [];
24634
+ for (const val of axisTickValues(range)) {
24635
+ const y = valueToY(val, range, layout.plotTop, layout.plotBottom);
24636
+ if (showMajorGridlines) {
24637
+ gridlines.push({
24638
+ kind: 'line',
24639
+ x1: layout.plotLeft,
24640
+ y1: y,
24641
+ x2: layout.plotRight,
24642
+ y2: y,
24643
+ stroke: GRIDLINE_COLOR$1,
24644
+ strokeWidth: 1,
24645
+ });
24646
+ }
24647
+ axisLabels.push({
24648
+ kind: 'text',
24649
+ x: layout.plotLeft - 4,
24650
+ y,
24651
+ text: formatAxisValue(val),
24652
+ fontSize: DEFAULT_CHART_TEXT_PX,
24653
+ fill: AXIS_LABEL_COLOR,
24654
+ textAnchor: 'end',
24655
+ dominantBaseline: 'central',
24656
+ });
24657
+ }
24658
+ return { gridlines, axisLabels };
24659
+ }
24660
+ function buildZeroLine(range, layout) {
24661
+ if (range.min >= 0 || range.max <= 0) {
24662
+ return undefined;
24663
+ }
24664
+ const y = valueToY(0, range, layout.plotTop, layout.plotBottom);
24665
+ return {
24666
+ kind: 'line',
24667
+ x1: layout.plotLeft,
24668
+ y1: y,
24669
+ x2: layout.plotRight,
24670
+ y2: y,
24671
+ stroke: ZERO_LINE_COLOR,
24672
+ strokeWidth: 1,
24673
+ };
24674
+ }
24675
+ function buildCategoryLabels(categoryLabels, layout, catSpacing) {
24676
+ const catCount = Math.max(categoryLabels.length, 1);
24677
+ return categoryLabels.map((label, i) => {
24678
+ const x = catSpacing === 'bar'
24679
+ ? layout.plotLeft + (layout.plotWidth / catCount) * (i + 0.5)
24680
+ : catCount > 1
24681
+ ? layout.plotLeft + (layout.plotWidth / (catCount - 1)) * i
24682
+ : layout.plotLeft + layout.plotWidth / 2;
24683
+ return {
24684
+ kind: 'text',
24685
+ x,
24686
+ y: layout.plotBottom + 12,
24687
+ text: label,
24688
+ fontSize: DEFAULT_CHART_TEXT_PX,
24689
+ fill: AXIS_LABEL_COLOR,
24690
+ textAnchor: 'middle',
24691
+ };
24692
+ });
24693
+ }
24694
+
24261
24695
  /**
24262
24696
  * chart-view-model-types.ts: the framework-neutral descriptor types of the
24263
24697
  * chart engine (plot layout, interactive part refs, SVG primitives, legend
@@ -24309,7 +24743,9 @@ function buildFlatViewModel(element, chartData, categoryLabels, kind) {
24309
24743
  *
24310
24744
  * chart-view-model-types descriptor types (primitives, view-model, layout)
24311
24745
  * chart-view-model-scale palette, value ranges, axis value formatting
24312
- * chart-view-model-layout plot layout, gridlines, labels, legend placement
24746
+ * chart-view-model-layout plot layout math (inset + reservations)
24747
+ * chart-view-model-chrome gridlines, axis/category labels
24748
+ * chart-legend-build legend entries (colour/line swatch) + anchor position
24313
24749
  * chart-view-model-bars bar / column rects, line points
24314
24750
  * chart-view-model-points pie, scatter, bubble, radar geometry
24315
24751
  * chart-view-model-kinds supported kinds + preserveAspectRatio
@@ -25487,27 +25923,36 @@ function setChartCellValue(data, seriesIndex, categoryIndex, raw) {
25487
25923
  * Hide a currently-visible series (PowerPoint's Chart Filters "uncheck a
25488
25924
  * series"): moves it out of {@link PptxChartData.series} into
25489
25925
  * {@link PptxChartData.filteredSeries}, keeping its data and identity. The
25490
- * chart must keep at least one visible series. `idx`/`order` on the new
25491
- * filtered entry take the series' position among ALL series (visible and
25492
- * already-filtered combined), matching how PowerPoint numbers them.
25926
+ * chart must keep at least one visible series.
25927
+ *
25928
+ * The new filtered entry's `idx`/`order` reuse the series' OWN `c:idx` when
25929
+ * it has one (every real OOXML `c:ser` does): this value is what
25930
+ * {@link restoreFilteredSeries} and the Chart Filters popover
25931
+ * (`chart-quick-actions.ts`'s `buildFilters`) key a series' position on, so a
25932
+ * chart with non-sequential idx values (a filtered series in the original
25933
+ * file, or one authored idx 1/2 rather than 0/1, both real-world cases)
25934
+ * keeps its true identity instead of being renumbered to "the smallest idx
25935
+ * not yet taken," which used to reorder the Chart Filters list and lose a
25936
+ * restored series' original position. Only a source series with no idx at
25937
+ * all (a synthetic/malformed chart) falls back to that smallest-free-slot
25938
+ * scheme, to guarantee every idx in the combined visible+filtered set stays
25939
+ * distinct.
25493
25940
  */
25494
25941
  function hideChartSeries(data, seriesIndex) {
25495
25942
  if (data.series.length <= 1 || seriesIndex < 0 || seriesIndex >= data.series.length) {
25496
25943
  return null;
25497
25944
  }
25498
25945
  const series = data.series[seriesIndex];
25499
- // The smallest idx not already used by another VISIBLE series or an
25500
- // already-filtered one. With nothing else filtered this is just
25501
- // `seriesIndex` itself (PowerPoint numbers idx/order 0..N-1 across visible
25502
- // and filtered series together); a prior filtered series can force a
25503
- // higher one, keeping every idx distinct.
25504
- const takenIndices = new Set([
25505
- ...data.series.map((_, i) => i).filter((i) => i !== seriesIndex),
25506
- ...(data.filteredSeries ?? []).map((f) => f.idx),
25507
- ]);
25508
- let idx = 0;
25509
- while (takenIndices.has(idx)) {
25510
- idx++;
25946
+ let idx = series.idx;
25947
+ if (idx === undefined) {
25948
+ const taken = new Set([
25949
+ ...data.series.map((_, i) => i).filter((i) => i !== seriesIndex),
25950
+ ...(data.filteredSeries ?? []).map((f) => f.idx),
25951
+ ]);
25952
+ idx = 0;
25953
+ while (taken.has(idx)) {
25954
+ idx++;
25955
+ }
25511
25956
  }
25512
25957
  const filteredEntry = {
25513
25958
  idx,
@@ -25516,6 +25961,11 @@ function hideChartSeries(data, seriesIndex) {
25516
25961
  categories: data.categories,
25517
25962
  values: series.values,
25518
25963
  ...(series.uniqueId ? { uniqueId: series.uniqueId } : {}),
25964
+ // Full snapshot so restoreFilteredSeries can bring back everything
25965
+ // (fill/line formatting, marker, data labels, ...), not just name and
25966
+ // values: this used to silently reset a series with a picture fill
25967
+ // or any other non-default formatting to plain defaults on restore.
25968
+ originalSeries: series,
25519
25969
  };
25520
25970
  return {
25521
25971
  ...data,
@@ -25526,11 +25976,15 @@ function hideChartSeries(data, seriesIndex) {
25526
25976
  /**
25527
25977
  * Restore a series PowerPoint's Chart Filters hid (the reverse of
25528
25978
  * {@link hideChartSeries}): moves it from {@link PptxChartData.filteredSeries}
25529
- * back into {@link PptxChartData.series}. When the filtered entry itself
25530
- * carries no cached name (PowerPoint auto-generated one, e.g. "Series 3"),
25531
- * falls back to {@link PptxChartData.filteredSeriesTitle}; when it carries no
25532
- * cached categories, falls back to {@link PptxChartData.filteredCategoryTitle}
25533
- * or the chart's current categories.
25979
+ * back into {@link PptxChartData.series}, reinserted at the position its
25980
+ * `idx` puts it among the currently-visible series (not always appended
25981
+ * last, which used to leave a restored series permanently reordered to the
25982
+ * end of the chart, its legend, and its plot/stacking order). When the
25983
+ * filtered entry itself carries no cached name (PowerPoint auto-generated
25984
+ * one, e.g. "Series 3"), falls back to {@link PptxChartData.filteredSeriesTitle};
25985
+ * when it carries no cached categories, falls back to
25986
+ * {@link PptxChartData.filteredCategoryTitle} or the chart's current
25987
+ * categories.
25534
25988
  */
25535
25989
  function restoreFilteredSeries(data, filteredIndex) {
25536
25990
  const filtered = data.filteredSeries?.[filteredIndex];
@@ -25542,15 +25996,33 @@ function restoreFilteredSeries(data, filteredIndex) {
25542
25996
  const values = filtered.values ??
25543
25997
  (filtered.categories ?? data.filteredCategoryTitle)?.map(() => 0) ??
25544
25998
  categories.map(() => 0);
25545
- const restored = {
25546
- name,
25547
- values,
25548
- ...(filtered.uniqueId ? { uniqueId: filtered.uniqueId } : {}),
25549
- };
25999
+ // A series hidden by hideChartSeries this session carries a full
26000
+ // originalSeries snapshot: use it as-is (fill/line formatting, marker,
26001
+ // data labels, everything) rather than the bare name/values reconstruction
26002
+ // below, which used to reset a series with a picture fill or any other
26003
+ // non-default formatting to plain defaults on restore. A filtered series
26004
+ // that came from parsing the file (already hidden when opened) has no
26005
+ // snapshot to draw on, since the parser only reads name/cat/val/uniqueId
26006
+ // off the real c15:ser node; that case keeps the bare reconstruction.
26007
+ const restored = filtered.originalSeries
26008
+ ? { ...filtered.originalSeries, idx: filtered.idx }
26009
+ : {
26010
+ name,
26011
+ values,
26012
+ idx: filtered.idx,
26013
+ ...(filtered.uniqueId ? { uniqueId: filtered.uniqueId } : {}),
26014
+ };
26015
+ // The first currently-visible series whose own idx sorts after the
26016
+ // restored one; splicing in there reproduces its original relative
26017
+ // position. A series with no idx info at all falls back to its array
26018
+ // position, matching hideChartSeries's own fallback.
26019
+ const insertAt = data.series.findIndex((s, i) => (s.idx ?? i) > filtered.idx);
26020
+ const nextSeries = [...data.series];
26021
+ nextSeries.splice(insertAt === -1 ? nextSeries.length : insertAt, 0, restored);
25550
26022
  const remainingFiltered = (data.filteredSeries ?? []).filter((_, i) => i !== filteredIndex);
25551
26023
  return {
25552
26024
  ...data,
25553
- series: [...data.series, restored],
26025
+ series: nextSeries,
25554
26026
  ...(remainingFiltered.length > 0
25555
26027
  ? { filteredSeries: remainingFiltered }
25556
26028
  : { filteredSeries: undefined }),
@@ -25581,6 +26053,248 @@ function setDataLabelsRangeCache(series, pointIndex, text) {
25581
26053
  };
25582
26054
  }
25583
26055
 
26056
+ /** The curated preset list: (id, label key, source `c:style/@val`). */
26057
+ const CHART_STYLE_PRESETS = [
26058
+ { id: 'colorful', labelKey: 'pptx.chart.styleColorful', styleId: 2 },
26059
+ { id: 'monochrome', labelKey: 'pptx.chart.styleMonochrome', styleId: 10 },
26060
+ { id: 'colorfulLight', labelKey: 'pptx.chart.styleColorfulLight', styleId: 18 },
26061
+ { id: 'colorfulDark', labelKey: 'pptx.chart.styleColorfulDark', styleId: 26 },
26062
+ { id: 'mutedDark', labelKey: 'pptx.chart.styleMutedDark', styleId: 34 },
26063
+ { id: 'pastel', labelKey: 'pptx.chart.stylePastel', styleId: 42 },
26064
+ ];
26065
+ function paletteEquals(a, b) {
26066
+ if (!b || a.length !== b.length) {
26067
+ return false;
26068
+ }
26069
+ return a.every((c, i) => c.toLowerCase() === b[i]?.toLowerCase());
26070
+ }
26071
+ /** Resolve the id -> styleId map entry, or `undefined` for an unknown id. */
26072
+ function findPreset(id) {
26073
+ return CHART_STYLE_PRESETS.find((p) => p.id === id);
26074
+ }
26075
+ /**
26076
+ * Build the "Chart Styles" gallery descriptor for the currently-selected
26077
+ * chart: every preset's resolved colours plus whether it is the one
26078
+ * currently applied (its resolved palette matches `chartData.colorPalette`).
26079
+ */
26080
+ function buildChartStylePresets(chartData) {
26081
+ return CHART_STYLE_PRESETS.map(({ id, labelKey, styleId }) => {
26082
+ const colors = getChartStylePalette(styleId);
26083
+ return {
26084
+ id,
26085
+ labelKey,
26086
+ colors,
26087
+ applied: paletteEquals(colors, chartData.colorPalette),
26088
+ };
26089
+ });
26090
+ }
26091
+ /** Drop a data point's own fill override, keeping every other field. */
26092
+ function clearDataPointFill(point) {
26093
+ const { spPr, marker, ...rest } = point;
26094
+ const nextSpPr = spPr && { ...spPr, fillColor: undefined };
26095
+ const nextMarker = marker?.spPr && {
26096
+ ...marker,
26097
+ spPr: { ...marker.spPr, fillColor: undefined },
26098
+ };
26099
+ return { ...rest, ...(spPr && { spPr: nextSpPr }), ...(nextMarker && { marker: nextMarker }) };
26100
+ }
26101
+ /** Drop a series' own colour overrides (and its points'), keeping shape/symbol/size. */
26102
+ function clearSeriesColor(series) {
26103
+ const { color: _color, marker, dataPoints, ...rest } = series;
26104
+ const nextMarker = marker?.spPr && { ...marker, spPr: { ...marker.spPr, fillColor: undefined } };
26105
+ return {
26106
+ ...rest,
26107
+ ...(marker && { marker: nextMarker ?? marker }),
26108
+ ...(dataPoints && { dataPoints: dataPoints.map(clearDataPointFill) }),
26109
+ };
26110
+ }
26111
+ /**
26112
+ * Apply a "Chart Styles" preset by id: resolves its palette via
26113
+ * `getChartStylePalette`, writes it onto `colorPalette` (also mirroring
26114
+ * `style.styleId`), and clears every series'/data-point's own explicit fill
26115
+ * so the new palette is not immediately shadowed by pre-existing colours
26116
+ * (see this module's header). Returns `null` for an unknown preset id so a
26117
+ * caller can no-op instead of silently clearing the chart's palette.
26118
+ */
26119
+ function applyChartStylePreset(chartData, presetId) {
26120
+ const preset = findPreset(presetId);
26121
+ if (!preset) {
26122
+ return null;
26123
+ }
26124
+ const colors = [...getChartStylePalette(preset.styleId)];
26125
+ return {
26126
+ ...chartData,
26127
+ colorPalette: colors,
26128
+ style: { ...chartData.style, styleId: preset.styleId },
26129
+ series: chartData.series.map(clearSeriesColor),
26130
+ };
26131
+ }
26132
+
26133
+ const AXIS_TYPES_WITH_TITLES = [
26134
+ 'catAx',
26135
+ 'valAx',
26136
+ 'dateAx',
26137
+ ];
26138
+ /** Default axis title text PowerPoint inserts when a title is turned on with none set. */
26139
+ const DEFAULT_AXIS_TITLE_TEXT = 'Axis Title';
26140
+ /**
26141
+ * Set/clear `deleted` on every category/value/date axis (PowerPoint's "Axes"
26142
+ * checklist item hides the whole axis, not just its gridlines/title).
26143
+ */
26144
+ function chartAxesVisibilityPatch(chartData, show) {
26145
+ const axes = (chartData.axes ?? []).map((axis) => AXIS_TYPES_WITH_TITLES.includes(axis.axisType) ? { ...axis, deleted: !show } : axis);
26146
+ return { axes };
26147
+ }
26148
+ /** Whether at least one axis is currently visible (not `deleted`). */
26149
+ function chartAxesVisibilityState(chartData) {
26150
+ const relevant = (chartData.axes ?? []).filter((axis) => AXIS_TYPES_WITH_TITLES.includes(axis.axisType));
26151
+ return relevant.length === 0 || relevant.some((axis) => !axis.deleted);
26152
+ }
26153
+ /**
26154
+ * Set/clear `titleText` on every category/value axis. Turning off blanks the
26155
+ * text (PowerPoint's own "None" removes the title box entirely, which this
26156
+ * codebase models as absent/empty `titleText`, see `chart-axis-render.ts`);
26157
+ * turning on with no existing text fills in the same placeholder PowerPoint
26158
+ * inserts ("Axis Title").
26159
+ */
26160
+ function chartAxisTitlesPatch(chartData, show) {
26161
+ const axes = (chartData.axes ?? []).map((axis) => {
26162
+ if (!AXIS_TYPES_WITH_TITLES.includes(axis.axisType)) {
26163
+ return axis;
26164
+ }
26165
+ if (!show) {
26166
+ return { ...axis, titleText: undefined };
26167
+ }
26168
+ return { ...axis, titleText: axis.titleText || DEFAULT_AXIS_TITLE_TEXT };
26169
+ });
26170
+ return { axes };
26171
+ }
26172
+ /** Whether at least one axis currently shows a non-empty title. */
26173
+ function chartAxisTitlesState(chartData) {
26174
+ return (chartData.axes ?? []).some((axis) => AXIS_TYPES_WITH_TITLES.includes(axis.axisType) && Boolean(axis.titleText));
26175
+ }
26176
+ /**
26177
+ * Apply one "Chart Elements" checklist toggle. The single entry point every
26178
+ * binding's quick-action popover calls on click; see this module's header.
26179
+ */
26180
+ function applyChartElementToggle(chartData, key, checked) {
26181
+ switch (key) {
26182
+ case 'title':
26183
+ return patchChartData$1(chartData, { style: { ...chartData.style, hasTitle: checked } });
26184
+ case 'legend':
26185
+ return patchChartData$1(chartData, { style: { ...chartData.style, hasLegend: checked } });
26186
+ case 'dataLabels':
26187
+ return patchChartData$1(chartData, { style: { ...chartData.style, hasDataLabels: checked } });
26188
+ case 'gridlines':
26189
+ return patchChartData$1(chartData, chartGridlinesPatch(chartData, checked));
26190
+ case 'axes':
26191
+ return patchChartData$1(chartData, chartAxesVisibilityPatch(chartData, checked));
26192
+ case 'axisTitles':
26193
+ return patchChartData$1(chartData, chartAxisTitlesPatch(chartData, checked));
26194
+ default:
26195
+ return chartData;
26196
+ }
26197
+ }
26198
+
26199
+ /** Button edge length, in the same units as the caller's `selectionBox`. */
26200
+ const CHART_QUICK_ACTION_BUTTON_SIZE = 24;
26201
+ /** Gap between stacked buttons. */
26202
+ const BUTTON_GAP = 6;
26203
+ /** Gap between the chart's selection box and the button column, matching PowerPoint's offset. */
26204
+ const OUTSIDE_GAP = 8;
26205
+ const BUTTON_ORDER = [
26206
+ { id: 'elements', labelKey: 'pptx.chart.quickElements' },
26207
+ { id: 'styles', labelKey: 'pptx.chart.quickStyles' },
26208
+ { id: 'filters', labelKey: 'pptx.chart.quickFilters' },
26209
+ ];
26210
+ function buildButtons(selectionBox, showFilters) {
26211
+ const x = selectionBox.x + selectionBox.width + OUTSIDE_GAP;
26212
+ return BUTTON_ORDER.filter((entry) => entry.id !== 'filters' || showFilters).map((entry, i) => ({
26213
+ ...entry,
26214
+ x,
26215
+ y: selectionBox.y + i * (CHART_QUICK_ACTION_BUTTON_SIZE + BUTTON_GAP),
26216
+ size: CHART_QUICK_ACTION_BUTTON_SIZE,
26217
+ }));
26218
+ }
26219
+ function buildElements(chartData) {
26220
+ const items = [
26221
+ { key: 'title', labelKey: 'pptx.chart.showTitle', checked: chartData.style?.hasTitle ?? false },
26222
+ {
26223
+ key: 'legend',
26224
+ labelKey: 'pptx.chart.showLegend',
26225
+ checked: chartData.style?.hasLegend ?? false,
26226
+ },
26227
+ {
26228
+ key: 'gridlines',
26229
+ labelKey: 'pptx.chart.showGridlines',
26230
+ checked: chartGridlinesState(chartData),
26231
+ },
26232
+ {
26233
+ key: 'dataLabels',
26234
+ labelKey: 'pptx.chart.showDataLabels',
26235
+ checked: chartData.style?.hasDataLabels ?? false,
26236
+ },
26237
+ ];
26238
+ if (chartData.axes && chartData.axes.length > 0) {
26239
+ items.push({
26240
+ key: 'axes',
26241
+ labelKey: 'pptx.chart.quickAxes',
26242
+ checked: chartAxesVisibilityState(chartData),
26243
+ });
26244
+ items.push({
26245
+ key: 'axisTitles',
26246
+ labelKey: 'pptx.chart.quickAxisTitles',
26247
+ checked: chartAxisTitlesState(chartData),
26248
+ });
26249
+ }
26250
+ return items;
26251
+ }
26252
+ function buildFilters(chartData) {
26253
+ const visibleSeries = chartData.series.map((series, index) => ({
26254
+ key: `visible-${index}`,
26255
+ name: series.name,
26256
+ visible: true,
26257
+ seriesIndex: index,
26258
+ sortKey: series.idx ?? index,
26259
+ }));
26260
+ const filtered = (chartData.filteredSeries ?? []).map((entry, index) => ({
26261
+ key: `filtered-${index}`,
26262
+ name: entry.name ?? chartData.filteredSeriesTitle ?? `Series ${index + 1}`,
26263
+ visible: false,
26264
+ filteredIndex: index,
26265
+ sortKey: entry.idx,
26266
+ }));
26267
+ // PowerPoint's Chart Filters list never reorders rows as they're (un)checked:
26268
+ // every series keeps its original position, keyed by its persistent c:idx
26269
+ // (falling back to array position for a chart with no idx info at all),
26270
+ // not by which of the two underlying arrays (series/filteredSeries)
26271
+ // currently holds it. Concatenating visible-then-filtered instead made a
26272
+ // hidden series jump to the bottom of the list (reported as "unticking
26273
+ // some of them changes the ordering").
26274
+ const series = [...visibleSeries, ...filtered]
26275
+ .sort((a, b) => a.sortKey - b.sortKey)
26276
+ .map(({ sortKey: _sortKey, ...rest }) => rest);
26277
+ return { visible: series.length > 1, series };
26278
+ }
26279
+ /**
26280
+ * Build the quick-action descriptor for the currently-selected element.
26281
+ * Returns `null` when the quick actions should not render (nothing selected,
26282
+ * the selection is not a chart, or the chart has no data yet).
26283
+ */
26284
+ function buildChartQuickActionsDescriptor(input) {
26285
+ if (!input.isChartSelected || !input.chartData) {
26286
+ return null;
26287
+ }
26288
+ const { chartData, selectionBox } = input;
26289
+ const filters = buildFilters(chartData);
26290
+ return {
26291
+ buttons: buildButtons(selectionBox, filters.visible),
26292
+ elements: buildElements(chartData),
26293
+ filters,
26294
+ styles: { presets: buildChartStylePresets(chartData) },
26295
+ };
26296
+ }
26297
+
25584
26298
  const TWO_PI = Math.PI * 2;
25585
26299
  /** Keep the dragged span strictly inside (0, 2*PI) so the share never hits 0 or 1. */
25586
26300
  const ANGLE_EPSILON = TWO_PI * 0.001;
@@ -25684,9 +26398,12 @@ function buildStackedDragGeometry(element, chartData, seriesIndex, pointIndex) {
25684
26398
  if (!draggedValues || pointIndex < 0 || pointIndex >= draggedValues.length) {
25685
26399
  return null;
25686
26400
  }
25687
- const percent = grouping === 'percentStacked', catCount = Math.max(chartData.categories.length, ...seriesValues.map((values) => values.length), pointIndex + 1), plots = computeStackedSeriesPlots(seriesValues, catCount, percent), baseValue = plots[seriesIndex]?.base[pointIndex] ?? 0, otherAbsSum = seriesValues.reduce((sum, values, si) => (si === seriesIndex ? sum : sum + Math.abs(values[pointIndex] ?? 0)), 0), range = percent
26401
+ const percent = grouping === 'percentStacked', catCount = Math.max(chartData.categories.length, ...seriesValues.map((values) => values.length), pointIndex + 1), plots = computeStackedSeriesPlots(seriesValues, catCount, percent), baseValue = plots[seriesIndex]?.base[pointIndex] ?? 0, otherAbsSum = seriesValues.reduce((sum, values, si) => (si === seriesIndex ? sum : sum + Math.abs(values[pointIndex] ?? 0)), 0), layoutOpts = computeLayoutOptions(chartData.axes, chartData.dataTable, chartData.series.length), layout = computePlotLayout(element.width, element.height, chartData, true, layoutOpts),
26402
+ // Computed from the SAME `layout.plotHeight` `chart-cartesian.ts`'s
26403
+ // `stackedRange` uses, so a drag never disagrees with what is on screen.
26404
+ range = percent
25688
26405
  ? { min: 0, max: 100, span: 100 }
25689
- : computeStackedValueRange(chartData.series, catCount), layoutOpts = computeLayoutOptions(chartData.axes, chartData.dataTable, chartData.series.length), layout = computePlotLayout(element.width, element.height, chartData, true, layoutOpts);
26406
+ : computeStackedValueRange(chartData.series, catCount, layout.plotHeight);
25690
26407
  return {
25691
26408
  range,
25692
26409
  plotTop: layout.plotTop,
@@ -48661,6 +49378,17 @@ const translationsEn = {
48661
49378
  'pptx.chart.chartFilters': 'Chart filters',
48662
49379
  'pptx.chart.hideSeries': 'Hide {{name}}',
48663
49380
  'pptx.chart.showSeries': 'Show {{name}}',
49381
+ 'pptx.chart.quickElements': 'Chart Elements',
49382
+ 'pptx.chart.quickStyles': 'Chart Styles',
49383
+ 'pptx.chart.quickFilters': 'Chart Filters',
49384
+ 'pptx.chart.quickAxes': 'Axes',
49385
+ 'pptx.chart.quickAxisTitles': 'Axis Titles',
49386
+ 'pptx.chart.styleColorful': 'Colorful',
49387
+ 'pptx.chart.styleMonochrome': 'Monochrome',
49388
+ 'pptx.chart.styleColorfulLight': 'Colorful Light',
49389
+ 'pptx.chart.styleColorfulDark': 'Colorful Dark',
49390
+ 'pptx.chart.styleMutedDark': 'Muted Dark',
49391
+ 'pptx.chart.stylePastel': 'Pastel',
48664
49392
  'pptx.chart.valueFromCells': 'Value from cells: {{name}}',
48665
49393
  'pptx.chart.removeCategory': 'Remove category',
48666
49394
  'pptx.chart.axes': 'Axes',
@@ -77006,7 +77734,9 @@ function buildSurfaceChart3DData(chartData, categoryLabels, options) {
77006
77734
  if (seriesCount === 0 || catCount === 0) {
77007
77735
  return null;
77008
77736
  }
77009
- const range = computeValueRange(chartData.series);
77737
+ // See the matching comment in `bar-chart-3d-data.ts`: the element height
77738
+ // stands in for a plot-area height this 3D scene doesn't separately compute.
77739
+ const range = computeValueRange(chartData.series, options.height);
77010
77740
  const heightMap = new Float32Array(seriesCount * catCount);
77011
77741
  const colorMap = new Float32Array(seriesCount * catCount * 3);
77012
77742
  // Raw (un-normalised) values, kept alongside the [0,1] heightMap so the
@@ -78147,11 +78877,16 @@ function buildBarChart3DData(chartData, categoryLabels, options) {
78147
78877
  const isPercent = grouping === 'percentStacked';
78148
78878
  const horizontal = chartData.barDirection === 'bar';
78149
78879
  const points = buildPoints$1(chartData, cols, isPercent);
78880
+ // No separate plot-area inset exists for the 3D scene the way the flat SVG
78881
+ // engine's `computePlotLayout` reserves one, so the full element height
78882
+ // stands in for it; see `axisTargetIntervals`'s doc comment for why this
78883
+ // only makes the automatic scale saturate at its gridline cap slightly
78884
+ // sooner; it does not change the "ordinary chart" result this was fixed for.
78150
78885
  const range = isPercent
78151
78886
  ? { min: 0, max: 100, span: 100 }
78152
78887
  : grouping === 'stacked'
78153
- ? computeStackedValueRange(chartData.series, cols)
78154
- : computeValueRange(chartData.series);
78888
+ ? computeStackedValueRange(chartData.series, cols, options.height)
78889
+ : computeValueRange(chartData.series, options.height);
78155
78890
  const depthPercent = chartData.view3D?.depthPercent;
78156
78891
  const boxes = layoutBarChart3D(points, cols, rows, range, grouping, depthPercent, horizontal);
78157
78892
  return {
@@ -78338,7 +79073,9 @@ function buildCartesianLine3DSceneData(chartData, categoryLabels, options) {
78338
79073
  return null;
78339
79074
  }
78340
79075
  const points = buildPoints(chartData, cols);
78341
- const range = computeValueRange(chartData.series);
79076
+ // See the matching comment in `bar-chart-3d-data.ts`: the element height
79077
+ // stands in for a plot-area height this 3D scene doesn't separately compute.
79078
+ const range = computeValueRange(chartData.series, options.height);
78342
79079
  const depthPercent = chartData.view3D?.depthPercent;
78343
79080
  const series = layoutCartesianLine3DSeries(points, cols, rows, range, depthPercent);
78344
79081
  return {
@@ -103932,6 +104669,26 @@ function segmentFrameCount(durationMs, fps) {
103932
104669
  }
103933
104670
  return Math.max(1, Math.ceil(durationMs / (1000 / fps)));
103934
104671
  }
104672
+ /**
104673
+ * Stop every track on a `canvas.captureStream()` `MediaStream`.
104674
+ *
104675
+ * `MediaRecorder.stop()` only stops the recorder; the underlying capture
104676
+ * stream it was constructed from keeps its track live and compositing frames
104677
+ * from the source canvas indefinitely otherwise. Every binding's `recordWebm`
104678
+ * driver must call this once recording is done - on success, on a
104679
+ * `MediaRecorder` error, and on `AbortSignal` cancellation alike - or each
104680
+ * video export leaks one live capture track for the rest of the page's
104681
+ * lifetime, degrading a session that exports more than once. Pass
104682
+ * `recorder.stream` (every `MediaRecorder` exposes the stream it was built
104683
+ * from) so callers need not keep a separate reference to the stream.
104684
+ *
104685
+ * @param stream - The `MediaStream` to stop, typically `recorder.stream`.
104686
+ */
104687
+ function stopCaptureStream(stream) {
104688
+ for (const track of stream.getTracks()) {
104689
+ track.stop();
104690
+ }
104691
+ }
103935
104692
 
103936
104693
  /**
103937
104694
  * CRC-32 (PNG's checksum, ISO 3309 / ITU-T V.42 polynomial 0xEDB88320) pure
@@ -106873,7 +107630,7 @@ function createLocalStorageBackend(namespace) {
106873
107630
  /** Try IndexedDB first; fall back to localStorage on any failure. */
106874
107631
  async function resolveBackend(dbName, namespace) {
106875
107632
  try {
106876
- const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-BKazjZCl.mjs');
107633
+ const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-amzrYVHP.mjs');
106877
107634
  const db = await openChatDb(dbName);
106878
107635
  return createIdbBackend(db);
106879
107636
  }
@@ -117545,6 +118302,457 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
117545
118302
  type: Injectable
117546
118303
  }] });
117547
118304
 
118305
+ // ─────────────────────────────────────────────────────────────────────────────
118306
+ // Direct part-editing gate
118307
+ // ─────────────────────────────────────────────────────────────────────────────
118308
+ /**
118309
+ * May this chart's individual parts (title, series, data points) be entered
118310
+ * for direct on-canvas editing?
118311
+ *
118312
+ * Pure so it is unit-testable without a full Angular injection context:
118313
+ * `ChartElementViewComponent`'s constructor runs an `effect()` that needs a
118314
+ * `ChangeDetectionScheduler` this package's TestBed-free suite doesn't
118315
+ * provide. G8: `a:graphicFrameLocks/@noDrilldown` forbids the drill-down,
118316
+ * even when the chart is otherwise selected + editable.
118317
+ */
118318
+ function chartCanEditParts(editable, isSelected, hasEditor, element) {
118319
+ return editable && isSelected && hasEditor && canDrillDown(element);
118320
+ }
118321
+ // ─────────────────────────────────────────────────────────────────────────────
118322
+ // Value-drag commit gate
118323
+ // ─────────────────────────────────────────────────────────────────────────────
118324
+ /**
118325
+ * The chart data a finished drag should commit, or null when nothing should
118326
+ * be committed (cancelled, or the press never became a drag).
118327
+ */
118328
+ function chartDragCommitData(session, commit) {
118329
+ if (!commit || !session?.moved) {
118330
+ return null;
118331
+ }
118332
+ return session.lastData;
118333
+ }
118334
+ /**
118335
+ * Same gate as {@link chartDragCommitData}, for a pie/radar/stacked mark drag
118336
+ * ({@link ChartMarkDragState} carries the same `moved`/`lastData` shape as the
118337
+ * cartesian value-drag session, just resolved through a different geometry).
118338
+ */
118339
+ function chartMarkDragCommitData(session, commit) {
118340
+ if (!commit || !session?.moved) {
118341
+ return null;
118342
+ }
118343
+ return session.lastData;
118344
+ }
118345
+ // ─────────────────────────────────────────────────────────────────────────────
118346
+ // 3D value-drag commit data (bar3D/line3D/area3D/pie3D/surface3D: no drag
118347
+ // state machine of their own, the shared three.js scene reports the
118348
+ // live/final value directly)
118349
+ // ─────────────────────────────────────────────────────────────────────────────
118350
+ /**
118351
+ * The chart data a 3D scene's `onValueDragPreview`/`onValueDragCommit` should
118352
+ * apply, or `null` when the part carries no point index (a series-level part,
118353
+ * or a part shape the 3D scenes never actually emit for a value drag) or there
118354
+ * is no chart data to update against.
118355
+ *
118356
+ * Pure so it is unit-testable without a WebGL context: `BarChart3DRendererComponent`
118357
+ * / `LineChart3DRendererComponent` / `AreaChart3DRendererComponent` /
118358
+ * `PieChart3DRendererComponent` / `SurfaceChart3DRendererComponent` forward
118359
+ * their `valueDragPreview`/`valueDragCommit` outputs here via
118360
+ * `ChartElementViewComponent`.
118361
+ */
118362
+ function chart3DPointValueUpdate(chartData, part, value) {
118363
+ if (!chartData || part.pointIndex === undefined) {
118364
+ return null;
118365
+ }
118366
+ return withChartPointValue(chartData, part.seriesIndex, part.pointIndex, value);
118367
+ }
118368
+ /**
118369
+ * Commit an on-canvas chart edit through the editor's normal element-update
118370
+ * path (the exact channel the inspector uses: one history snapshot per call).
118371
+ * No-op without an editor or when the element is not on any slide.
118372
+ *
118373
+ * `templateSlideId` is the id of the slide the hosting canvas renders (from
118374
+ * `SLIDE_CONTEXT`); it resolves template (master/layout) chart elements, which
118375
+ * live in the per-slide template store rather than in `slides[].elements`.
118376
+ */
118377
+ function commitChartElementData(editor, elementId, chartData, templateSlideId) {
118378
+ if (!editor) {
118379
+ return;
118380
+ }
118381
+ const slideIndex = findOwningSlideIndex(editor.slides(), elementId, templateSlideId);
118382
+ if (slideIndex < 0) {
118383
+ return;
118384
+ }
118385
+ editor.updateElement(slideIndex, elementId, { chartData });
118386
+ }
118387
+ // ─────────────────────────────────────────────────────────────────────────────
118388
+ // Interaction stylesheet (shared base rules + Angular's own badge/editor CSS)
118389
+ // ─────────────────────────────────────────────────────────────────────────────
118390
+ const STYLE_ELEMENT_ID = 'pptx-ng-chart-interaction-styles';
118391
+ /**
118392
+ * Angular-only interaction CSS, injected once into `document.head` alongside
118393
+ * the shared `[data-chart-part]` / selected-mark rules from
118394
+ * `ensureChartInteractionStyles` (`pptx-viewer-shared`). Component styles are
118395
+ * view-encapsulated in Angular, so they cannot reach into the chart
118396
+ * renderer's SVG or style the badge/title-input this component projects
118397
+ * next to it.
118398
+ */
118399
+ const INTERACTION_CSS = `
118400
+ .pptx-ng-chart-view { position: relative; width: 100%; height: 100%; }
118401
+ .pptx-ng-chart-drag-badge { position: absolute; top: 4px; right: 4px; z-index: 10; border-radius: 4px; background: rgba(37, 99, 235, 0.9); padding: 2px 6px; font-size: 10px; font-weight: 500; color: #fff; pointer-events: none; }
118402
+ .pptx-ng-chart-title-input { position: absolute; left: 50%; top: 2px; z-index: 10; width: 60%; transform: translateX(-50%); border: 1px solid #94a3b8; border-radius: 4px; background: #fff; padding: 2px 4px; text-align: center; font-size: 11px; color: #0f172a; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); }
118403
+ `;
118404
+ /**
118405
+ * Inject the interaction stylesheets for chart part hit targets: the shared
118406
+ * base rules (singleton, shared across all five bindings) plus Angular's own
118407
+ * badge/title-input CSS (singleton, this binding only).
118408
+ */
118409
+ function ensureChartInteractionStyles() {
118410
+ ensureChartInteractionStyles$1();
118411
+ if (typeof document === 'undefined' || document.getElementById(STYLE_ELEMENT_ID)) {
118412
+ return;
118413
+ }
118414
+ const style = document.createElement('style');
118415
+ style.id = STYLE_ELEMENT_ID;
118416
+ style.textContent = INTERACTION_CSS;
118417
+ document.head.appendChild(style);
118418
+ }
118419
+
118420
+ /**
118421
+ * chart-quick-actions-overlay.component.ts: PowerPoint's three floating
118422
+ * quick-action icons ("Chart Elements" +, "Chart Styles" paintbrush, "Chart
118423
+ * Filters" funnel) shown just outside a selected chart's top-right corner.
118424
+ *
118425
+ * Selector: `pptx-chart-quick-actions-overlay`
118426
+ *
118427
+ * Mounted inside `SlideCanvasComponent`'s template as a sibling of the
118428
+ * resize/rotate/adjust handle blocks, inside the same scaled
118429
+ * `.pptx-ng-canvas-stage`. Angular has no CSS inverse-scale variable (unlike
118430
+ * React's `--pptx-handle-inverse-scale`): this component follows the SAME
118431
+ * convention `selection-geometry.ts`'s `computeHandleBoxes`/
118432
+ * `computeCornerHandle` already use, computing every screen-constant
118433
+ * quantity (button size, gaps) as `screenPx / zoom` while leaving the
118434
+ * element's own x/y/width/height in raw slide px.
118435
+ *
118436
+ * All decision-making (which buttons render, checklist/gallery/filter state)
118437
+ * comes from the shared `buildChartQuickActionsDescriptor`; this component
118438
+ * only renders three buttons plus whichever popover is open, and commits
118439
+ * through `commitChartElementData` (the same on-canvas commit helper
118440
+ * `ChartElementViewComponent` uses for mark-drag/inline-title edits), so
118441
+ * undo/redo and the save round-trip are shared with every other on-canvas
118442
+ * chart edit. See the shared module's header for scope notes (Series-only
118443
+ * Chart Filters, no trendline/error-bar/up-down-bars rows).
118444
+ *
118445
+ * @module angular-viewer/chart-quick-actions-overlay
118446
+ */
118447
+ class ChartQuickActionsOverlayComponent {
118448
+ constructor() {
118449
+ this.element = input.required(/* @ts-ignore */
118450
+ ...(ngDevMode ? [{ debugName: "element" }] : /* istanbul ignore next */ []));
118451
+ this.canEdit = input(true, /* @ts-ignore */
118452
+ ...(ngDevMode ? [{ debugName: "canEdit" }] : /* istanbul ignore next */ []));
118453
+ /** Effective stage zoom, so screen-constant sizes/gaps divide it out. */
118454
+ this.zoom = input(1, /* @ts-ignore */
118455
+ ...(ngDevMode ? [{ debugName: "zoom" }] : /* istanbul ignore next */ []));
118456
+ this.CHART_QUICK_ACTION_BUTTON_SIZE = CHART_QUICK_ACTION_BUTTON_SIZE;
118457
+ this.editor = inject(EditorStateService, { optional: true });
118458
+ this.slideContext = inject(SLIDE_CONTEXT, { optional: true });
118459
+ this.hostRef = inject((ElementRef));
118460
+ this.open = signal(null, /* @ts-ignore */
118461
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
118462
+ /** The stage scale, guarded against 0. */
118463
+ this.scale = computed(() => this.zoom() || 1, /* @ts-ignore */
118464
+ ...(ngDevMode ? [{ debugName: "scale" }] : /* istanbul ignore next */ []));
118465
+ /**
118466
+ * The descriptor's geometry, computed against a SCREEN-space box (the
118467
+ * element's box multiplied by the stage scale): every constant the shared
118468
+ * function adds (gaps, button size) is then correctly screen-constant, and
118469
+ * {@link screenPx} divides each resulting quantity back down to the slide
118470
+ * px this component renders in, mirroring `selection-geometry.ts`'s
118471
+ * `computeHandleBoxes`.
118472
+ */
118473
+ this.descriptor = computed(() => {
118474
+ const el = this.element();
118475
+ const scale = this.scale();
118476
+ return buildChartQuickActionsDescriptor({
118477
+ isChartSelected: true,
118478
+ chartData: el.chartData,
118479
+ selectionBox: {
118480
+ x: el.x * scale,
118481
+ y: el.y * scale,
118482
+ width: el.width * scale,
118483
+ height: el.height * scale,
118484
+ },
118485
+ });
118486
+ }, /* @ts-ignore */
118487
+ ...(ngDevMode ? [{ debugName: "descriptor" }] : /* istanbul ignore next */ []));
118488
+ }
118489
+ /** Close whichever popover is open on an outside click, mirroring every other binding's overlay. */
118490
+ onDocumentMouseDown(event) {
118491
+ if (this.open() && !this.hostRef.nativeElement.contains(event.target)) {
118492
+ this.open.set(null);
118493
+ }
118494
+ }
118495
+ /** Convert a screen-space quantity (from {@link descriptor}) back to slide px. */
118496
+ screenPx(value) {
118497
+ return value / this.scale();
118498
+ }
118499
+ toggle(id) {
118500
+ this.open.update((current) => (current === id ? null : id));
118501
+ }
118502
+ commit(chartData) {
118503
+ commitChartElementData(this.editor, this.element().id, chartData, this.slideContext?.slideId() ?? null);
118504
+ }
118505
+ onElementToggle(key, event) {
118506
+ const chartData = this.element().chartData;
118507
+ if (!chartData) {
118508
+ return;
118509
+ }
118510
+ const checked = event.target.checked;
118511
+ this.commit(applyChartElementToggle(chartData, key, checked));
118512
+ }
118513
+ onStylePreset(presetId) {
118514
+ const chartData = this.element().chartData;
118515
+ if (!chartData) {
118516
+ return;
118517
+ }
118518
+ const next = applyChartStylePreset(chartData, presetId);
118519
+ if (next) {
118520
+ this.commit(next);
118521
+ }
118522
+ }
118523
+ onFilterToggle(seriesIndex, filteredIndex) {
118524
+ const chartData = this.element().chartData;
118525
+ if (!chartData) {
118526
+ return;
118527
+ }
118528
+ const next = seriesIndex !== undefined
118529
+ ? hideChartSeries(chartData, seriesIndex)
118530
+ : restoreFilteredSeries(chartData, filteredIndex);
118531
+ if (next) {
118532
+ this.commit(next);
118533
+ }
118534
+ }
118535
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ChartQuickActionsOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
118536
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: ChartQuickActionsOverlayComponent, isStandalone: true, selector: "pptx-chart-quick-actions-overlay", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, canEdit: { classPropertyName: "canEdit", publicName: "canEdit", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-pptx-chart-quick-actions": "true", "data-export-ignore": "true" }, listeners: { "pointerdown": "$event.stopPropagation()", "document:mousedown": "onDocumentMouseDown($event)" } }, ngImport: i0, template: `
118537
+ @if (descriptor(); as d) {
118538
+ @for (button of d.buttons; track button.id) {
118539
+ <button
118540
+ type="button"
118541
+ class="pptx-ng-chart-quick-btn"
118542
+ [disabled]="!canEdit()"
118543
+ [attr.data-testid]="'chart-quick-action-' + button.id"
118544
+ [attr.aria-label]="button.labelKey | translate"
118545
+ [title]="button.labelKey | translate"
118546
+ [style.left.px]="screenPx(button.x)"
118547
+ [style.top.px]="screenPx(button.y)"
118548
+ [style.width.px]="screenPx(button.size)"
118549
+ [style.height.px]="screenPx(button.size)"
118550
+ (click)="toggle(button.id)"
118551
+ >
118552
+ @if (button.id === 'elements') {
118553
+ <svg lucidePlus class="pptx-ng-chart-quick-icon"></svg>
118554
+ } @else if (button.id === 'styles') {
118555
+ <svg lucidePaintbrush class="pptx-ng-chart-quick-icon"></svg>
118556
+ } @else {
118557
+ <svg lucideFilter class="pptx-ng-chart-quick-icon"></svg>
118558
+ }
118559
+ </button>
118560
+
118561
+ @if (open() === button.id && button.id === 'elements') {
118562
+ <div
118563
+ class="pptx-ng-chart-quick-card"
118564
+ data-testid="chart-quick-elements-popover"
118565
+ [style.left.px]="screenPx(button.x + CHART_QUICK_ACTION_BUTTON_SIZE + 4)"
118566
+ [style.top.px]="screenPx(button.y)"
118567
+ >
118568
+ <h4 class="pptx-ng-chart-quick-heading">
118569
+ {{ 'pptx.chart.quickElements' | translate }}
118570
+ </h4>
118571
+ @for (item of d.elements; track item.key) {
118572
+ <label class="pptx-ng-chart-quick-check">
118573
+ <input
118574
+ type="checkbox"
118575
+ [disabled]="!canEdit()"
118576
+ [checked]="item.checked"
118577
+ [attr.data-testid]="'chart-quick-element-' + item.key"
118578
+ (change)="onElementToggle(item.key, $event)"
118579
+ />
118580
+ <span>{{ item.labelKey | translate }}</span>
118581
+ </label>
118582
+ }
118583
+ </div>
118584
+ }
118585
+
118586
+ @if (open() === button.id && button.id === 'styles') {
118587
+ <div
118588
+ class="pptx-ng-chart-quick-card pptx-ng-chart-quick-styles"
118589
+ data-testid="chart-quick-styles-popover"
118590
+ [style.left.px]="screenPx(button.x + CHART_QUICK_ACTION_BUTTON_SIZE + 4)"
118591
+ [style.top.px]="screenPx(button.y)"
118592
+ >
118593
+ @for (preset of d.styles.presets; track preset.id) {
118594
+ <button
118595
+ type="button"
118596
+ class="pptx-ng-chart-quick-swatch"
118597
+ [class.is-applied]="preset.applied"
118598
+ [disabled]="!canEdit()"
118599
+ [attr.data-testid]="'chart-quick-style-' + preset.id"
118600
+ [attr.aria-label]="preset.labelKey | translate"
118601
+ [attr.aria-pressed]="preset.applied"
118602
+ [title]="preset.labelKey | translate"
118603
+ (click)="onStylePreset(preset.id)"
118604
+ >
118605
+ <span class="pptx-ng-chart-quick-swatch-row">
118606
+ @for (c of preset.colors.slice(0, 5); track $index) {
118607
+ <span [style.background-color]="c"></span>
118608
+ }
118609
+ </span>
118610
+ </button>
118611
+ }
118612
+ </div>
118613
+ }
118614
+
118615
+ @if (open() === button.id && button.id === 'filters') {
118616
+ <div
118617
+ class="pptx-ng-chart-quick-card"
118618
+ data-testid="chart-quick-filters-popover"
118619
+ [style.left.px]="screenPx(button.x + CHART_QUICK_ACTION_BUTTON_SIZE + 4)"
118620
+ [style.top.px]="screenPx(button.y)"
118621
+ >
118622
+ <h4 class="pptx-ng-chart-quick-heading">{{ 'pptx.chart.quickFilters' | translate }}</h4>
118623
+ @for (row of d.filters.series; track row.key) {
118624
+ <label class="pptx-ng-chart-quick-check">
118625
+ <input
118626
+ type="checkbox"
118627
+ [disabled]="!canEdit()"
118628
+ [checked]="row.visible"
118629
+ [attr.data-testid]="'chart-quick-filter-' + row.key"
118630
+ (change)="onFilterToggle(row.seriesIndex, row.filteredIndex)"
118631
+ />
118632
+ <span class="pptx-ng-chart-quick-name">{{ row.name }}</span>
118633
+ </label>
118634
+ }
118635
+ </div>
118636
+ }
118637
+ }
118638
+ }
118639
+ `, isInline: true, styles: [":host{position:absolute;left:0;top:0;z-index:59}.pptx-ng-chart-quick-btn{position:absolute;display:flex;align-items:center;justify-content:center;border-radius:4px;border:1px solid #d1d5db;background:#fff;color:#374151;cursor:pointer}.pptx-ng-chart-quick-btn:hover{background:#f3f4f6}.pptx-ng-chart-quick-btn:disabled{opacity:.6;cursor:not-allowed}.pptx-ng-chart-quick-icon{width:14px;height:14px}.pptx-ng-chart-quick-card{position:absolute;z-index:10;display:flex;flex-direction:column;gap:.35rem;min-width:11rem;padding:.5rem;border-radius:6px;border:1px solid var(--pptx-inspector-border, #444);background:var(--pptx-inspector-input-bg, #2d2d2d);box-shadow:0 4px 16px #00000059}.pptx-ng-chart-quick-heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-ng-chart-quick-check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-ng-chart-quick-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-ng-chart-quick-styles{display:grid;grid-template-columns:repeat(3,1fr);gap:.35rem;min-width:12rem}.pptx-ng-chart-quick-swatch{border-radius:4px;overflow:hidden;border:1px solid var(--pptx-inspector-border, #444);padding:0;cursor:pointer}.pptx-ng-chart-quick-swatch.is-applied{border-color:var(--pptx-inspector-active, #0078d4);box-shadow:0 0 0 1px var(--pptx-inspector-active, #0078d4)}.pptx-ng-chart-quick-swatch-row{display:flex;height:20px;width:100%}.pptx-ng-chart-quick-swatch-row>span{flex:1 1 auto}\n"], dependencies: [{ kind: "component", type: LucidePlus, selector: "svg[lucidePlus]" }, { kind: "component", type: LucidePaintbrush, selector: "svg[lucidePaintbrush]" }, { kind: "component", type: LucideFilter, selector: "svg[lucideFunnel], svg[lucideFilter]" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
118640
+ }
118641
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ChartQuickActionsOverlayComponent, decorators: [{
118642
+ type: Component,
118643
+ args: [{ selector: 'pptx-chart-quick-actions-overlay', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [TranslatePipe, LucidePlus, LucidePaintbrush, LucideFilter], host: {
118644
+ 'data-pptx-chart-quick-actions': 'true',
118645
+ 'data-export-ignore': 'true',
118646
+ '(pointerdown)': '$event.stopPropagation()',
118647
+ }, template: `
118648
+ @if (descriptor(); as d) {
118649
+ @for (button of d.buttons; track button.id) {
118650
+ <button
118651
+ type="button"
118652
+ class="pptx-ng-chart-quick-btn"
118653
+ [disabled]="!canEdit()"
118654
+ [attr.data-testid]="'chart-quick-action-' + button.id"
118655
+ [attr.aria-label]="button.labelKey | translate"
118656
+ [title]="button.labelKey | translate"
118657
+ [style.left.px]="screenPx(button.x)"
118658
+ [style.top.px]="screenPx(button.y)"
118659
+ [style.width.px]="screenPx(button.size)"
118660
+ [style.height.px]="screenPx(button.size)"
118661
+ (click)="toggle(button.id)"
118662
+ >
118663
+ @if (button.id === 'elements') {
118664
+ <svg lucidePlus class="pptx-ng-chart-quick-icon"></svg>
118665
+ } @else if (button.id === 'styles') {
118666
+ <svg lucidePaintbrush class="pptx-ng-chart-quick-icon"></svg>
118667
+ } @else {
118668
+ <svg lucideFilter class="pptx-ng-chart-quick-icon"></svg>
118669
+ }
118670
+ </button>
118671
+
118672
+ @if (open() === button.id && button.id === 'elements') {
118673
+ <div
118674
+ class="pptx-ng-chart-quick-card"
118675
+ data-testid="chart-quick-elements-popover"
118676
+ [style.left.px]="screenPx(button.x + CHART_QUICK_ACTION_BUTTON_SIZE + 4)"
118677
+ [style.top.px]="screenPx(button.y)"
118678
+ >
118679
+ <h4 class="pptx-ng-chart-quick-heading">
118680
+ {{ 'pptx.chart.quickElements' | translate }}
118681
+ </h4>
118682
+ @for (item of d.elements; track item.key) {
118683
+ <label class="pptx-ng-chart-quick-check">
118684
+ <input
118685
+ type="checkbox"
118686
+ [disabled]="!canEdit()"
118687
+ [checked]="item.checked"
118688
+ [attr.data-testid]="'chart-quick-element-' + item.key"
118689
+ (change)="onElementToggle(item.key, $event)"
118690
+ />
118691
+ <span>{{ item.labelKey | translate }}</span>
118692
+ </label>
118693
+ }
118694
+ </div>
118695
+ }
118696
+
118697
+ @if (open() === button.id && button.id === 'styles') {
118698
+ <div
118699
+ class="pptx-ng-chart-quick-card pptx-ng-chart-quick-styles"
118700
+ data-testid="chart-quick-styles-popover"
118701
+ [style.left.px]="screenPx(button.x + CHART_QUICK_ACTION_BUTTON_SIZE + 4)"
118702
+ [style.top.px]="screenPx(button.y)"
118703
+ >
118704
+ @for (preset of d.styles.presets; track preset.id) {
118705
+ <button
118706
+ type="button"
118707
+ class="pptx-ng-chart-quick-swatch"
118708
+ [class.is-applied]="preset.applied"
118709
+ [disabled]="!canEdit()"
118710
+ [attr.data-testid]="'chart-quick-style-' + preset.id"
118711
+ [attr.aria-label]="preset.labelKey | translate"
118712
+ [attr.aria-pressed]="preset.applied"
118713
+ [title]="preset.labelKey | translate"
118714
+ (click)="onStylePreset(preset.id)"
118715
+ >
118716
+ <span class="pptx-ng-chart-quick-swatch-row">
118717
+ @for (c of preset.colors.slice(0, 5); track $index) {
118718
+ <span [style.background-color]="c"></span>
118719
+ }
118720
+ </span>
118721
+ </button>
118722
+ }
118723
+ </div>
118724
+ }
118725
+
118726
+ @if (open() === button.id && button.id === 'filters') {
118727
+ <div
118728
+ class="pptx-ng-chart-quick-card"
118729
+ data-testid="chart-quick-filters-popover"
118730
+ [style.left.px]="screenPx(button.x + CHART_QUICK_ACTION_BUTTON_SIZE + 4)"
118731
+ [style.top.px]="screenPx(button.y)"
118732
+ >
118733
+ <h4 class="pptx-ng-chart-quick-heading">{{ 'pptx.chart.quickFilters' | translate }}</h4>
118734
+ @for (row of d.filters.series; track row.key) {
118735
+ <label class="pptx-ng-chart-quick-check">
118736
+ <input
118737
+ type="checkbox"
118738
+ [disabled]="!canEdit()"
118739
+ [checked]="row.visible"
118740
+ [attr.data-testid]="'chart-quick-filter-' + row.key"
118741
+ (change)="onFilterToggle(row.seriesIndex, row.filteredIndex)"
118742
+ />
118743
+ <span class="pptx-ng-chart-quick-name">{{ row.name }}</span>
118744
+ </label>
118745
+ }
118746
+ </div>
118747
+ }
118748
+ }
118749
+ }
118750
+ `, styles: [":host{position:absolute;left:0;top:0;z-index:59}.pptx-ng-chart-quick-btn{position:absolute;display:flex;align-items:center;justify-content:center;border-radius:4px;border:1px solid #d1d5db;background:#fff;color:#374151;cursor:pointer}.pptx-ng-chart-quick-btn:hover{background:#f3f4f6}.pptx-ng-chart-quick-btn:disabled{opacity:.6;cursor:not-allowed}.pptx-ng-chart-quick-icon{width:14px;height:14px}.pptx-ng-chart-quick-card{position:absolute;z-index:10;display:flex;flex-direction:column;gap:.35rem;min-width:11rem;padding:.5rem;border-radius:6px;border:1px solid var(--pptx-inspector-border, #444);background:var(--pptx-inspector-input-bg, #2d2d2d);box-shadow:0 4px 16px #00000059}.pptx-ng-chart-quick-heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-ng-chart-quick-check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-ng-chart-quick-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-ng-chart-quick-styles{display:grid;grid-template-columns:repeat(3,1fr);gap:.35rem;min-width:12rem}.pptx-ng-chart-quick-swatch{border-radius:4px;overflow:hidden;border:1px solid var(--pptx-inspector-border, #444);padding:0;cursor:pointer}.pptx-ng-chart-quick-swatch.is-applied{border-color:var(--pptx-inspector-active, #0078d4);box-shadow:0 0 0 1px var(--pptx-inspector-active, #0078d4)}.pptx-ng-chart-quick-swatch-row{display:flex;height:20px;width:100%}.pptx-ng-chart-quick-swatch-row>span{flex:1 1 auto}\n"] }]
118751
+ }], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], onDocumentMouseDown: [{
118752
+ type: HostListener,
118753
+ args: ['document:mousedown', ['$event']]
118754
+ }] } });
118755
+
117548
118756
  /**
117549
118757
  * Pure geometry for interactive element drag/resize.
117550
118758
  *
@@ -119375,227 +120583,235 @@ class ChartRendererComponent {
119375
120583
  @if (legendItems().length > 0) {
119376
120584
  @for (item of legendItems(); track $index) {
119377
120585
  <g [attr.transform]="legendTransform(item)">
119378
- <rect
119379
- x="0"
119380
- y="-7"
119381
- [attr.width]="swatchSize"
119382
- [attr.height]="swatchSize"
119383
- rx="2"
119384
- [attr.fill]="item.color"
119385
- />
119386
- <text
119387
- [attr.x]="swatchSize + 3"
119388
- y="3"
119389
- [attr.font-size]="item.fontSize"
119390
- [attr.fill]="item.fill"
119391
- [attr.font-weight]="item.fontWeight"
119392
- [attr.font-style]="item.fontStyle"
119393
- [attr.font-family]="item.fontFamily ?? null"
119394
- >
119395
- {{ item.label }}
119396
- </text>
119397
- </g>
119398
- }
119399
- }
119400
- </svg>
119401
- `, isInline: true, dependencies: [{ kind: "component", type: ChartPrimitivesComponent, selector: "g[pptx-chart-primitives]", inputs: ["primitives"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
119402
- }
119403
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ChartRendererComponent, decorators: [{
119404
- type: Component,
119405
- args: [{
119406
- selector: 'pptx-chart-renderer',
119407
- standalone: true,
119408
- changeDetection: ChangeDetectionStrategy.OnPush,
119409
- imports: [ChartPrimitivesComponent],
119410
- template: `
119411
- <svg
119412
- [attr.width]="vm().svgWidth"
119413
- [attr.height]="vm().svgHeight"
119414
- [attr.viewBox]="viewBox()"
119415
- class="pptx-ng-chart-svg"
119416
- style="overflow: visible; display: block; pointer-events: none;"
119417
- >
119418
- <!-- c:dPt/c:pictureOptions picture-fill patterns, rendered before anything
119419
- references them via fill="url(#...)" -->
119420
- @if ((vm().defs ?? []).length > 0) {
119421
- <defs>
119422
- @for (def of vm().defs ?? []; track def.id) {
119423
- <pattern
119424
- [attr.id]="def.id"
119425
- [attr.patternUnits]="def.patternUnits"
119426
- [attr.x]="def.x"
119427
- [attr.y]="def.y"
119428
- [attr.width]="def.width"
119429
- [attr.height]="def.height"
119430
- >
119431
- <image
119432
- [attr.href]="def.href"
120586
+ @if (item.lineSwatch) {
120587
+ <g pptx-chart-primitives [primitives]="item.lineSwatch.primitives"></g>
120588
+ } @else {
120589
+ <rect
120590
+ x="0"
120591
+ y="-7"
120592
+ [attr.width]="swatchSize"
120593
+ [attr.height]="swatchSize"
120594
+ rx="2"
120595
+ [attr.fill]="item.color"
120596
+ />
120597
+ }
120598
+ <text
120599
+ [attr.x]="swatchSize + 3"
120600
+ y="3"
120601
+ [attr.font-size]="item.fontSize"
120602
+ [attr.fill]="item.fill"
120603
+ [attr.font-weight]="item.fontWeight"
120604
+ [attr.font-style]="item.fontStyle"
120605
+ [attr.font-family]="item.fontFamily ?? null"
120606
+ >
120607
+ {{ item.label }}
120608
+ </text>
120609
+ </g>
120610
+ }
120611
+ }
120612
+ </svg>
120613
+ `, isInline: true, dependencies: [{ kind: "component", type: ChartPrimitivesComponent, selector: "g[pptx-chart-primitives]", inputs: ["primitives"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
120614
+ }
120615
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ChartRendererComponent, decorators: [{
120616
+ type: Component,
120617
+ args: [{
120618
+ selector: 'pptx-chart-renderer',
120619
+ standalone: true,
120620
+ changeDetection: ChangeDetectionStrategy.OnPush,
120621
+ imports: [ChartPrimitivesComponent],
120622
+ template: `
120623
+ <svg
120624
+ [attr.width]="vm().svgWidth"
120625
+ [attr.height]="vm().svgHeight"
120626
+ [attr.viewBox]="viewBox()"
120627
+ class="pptx-ng-chart-svg"
120628
+ style="overflow: visible; display: block; pointer-events: none;"
120629
+ >
120630
+ <!-- c:dPt/c:pictureOptions picture-fill patterns, rendered before anything
120631
+ references them via fill="url(#...)" -->
120632
+ @if ((vm().defs ?? []).length > 0) {
120633
+ <defs>
120634
+ @for (def of vm().defs ?? []; track def.id) {
120635
+ <pattern
120636
+ [attr.id]="def.id"
120637
+ [attr.patternUnits]="def.patternUnits"
120638
+ [attr.x]="def.x"
120639
+ [attr.y]="def.y"
120640
+ [attr.width]="def.width"
120641
+ [attr.height]="def.height"
120642
+ >
120643
+ <image
120644
+ [attr.href]="def.href"
120645
+ x="0"
120646
+ y="0"
120647
+ [attr.width]="def.width"
120648
+ [attr.height]="def.height"
120649
+ [attr.preserveAspectRatio]="def.preserveAspectRatio"
120650
+ />
120651
+ </pattern>
120652
+ }
120653
+ </defs>
120654
+ }
120655
+
120656
+ <!-- Chart-area fill; absent when the deck declares c:chartSpace/a:noFill -->
120657
+ @if (vm().areaFill) {
120658
+ <rect
120659
+ x="0"
120660
+ y="0"
120661
+ [attr.width]="vm().svgWidth"
120662
+ [attr.height]="vm().svgHeight"
120663
+ [attr.rx]="vm().areaRadius"
120664
+ [attr.fill]="vm().areaFill"
120665
+ />
120666
+ }
120667
+
120668
+ <!-- Chart title (data-chart-part enables in-place editing in edit mode) -->
120669
+ @if (vm().title) {
120670
+ <text
120671
+ [attr.x]="vm().titleX"
120672
+ [attr.y]="vm().titleY"
120673
+ text-anchor="middle"
120674
+ [attr.font-size]="vm().titleStyle?.fontSize ?? 12"
120675
+ [attr.font-weight]="vm().titleStyle?.fontWeight ?? 600"
120676
+ [attr.font-family]="vm().titleStyle?.fontFamily"
120677
+ [attr.fill]="vm().titleStyle?.fill ?? '#1e293b'"
120678
+ data-chart-part="title"
120679
+ >
120680
+ @if (vm().titleRunSpans; as titleRunSpans) {
120681
+ @for (run of titleRunSpans; track $index) {
120682
+ <tspan
120683
+ [attr.font-size]="run.fontSize"
120684
+ [attr.font-weight]="run.fontWeight"
120685
+ [attr.font-style]="run.fontStyle"
120686
+ [attr.font-family]="run.fontFamily"
120687
+ [attr.fill]="run.fill"
120688
+ >
120689
+ {{ run.text }}
120690
+ </tspan>
120691
+ }
120692
+ } @else {
120693
+ {{ vm().title }}
120694
+ }
120695
+ </text>
120696
+ }
120697
+
120698
+ <!-- Gridlines -->
120699
+ @for (gl of vm().gridlines; track $index) {
120700
+ <line
120701
+ [attr.x1]="gl.x1"
120702
+ [attr.y1]="gl.y1"
120703
+ [attr.x2]="gl.x2"
120704
+ [attr.y2]="gl.y2"
120705
+ [attr.stroke]="gl.stroke"
120706
+ [attr.stroke-width]="gl.strokeWidth"
120707
+ />
120708
+ }
120709
+
120710
+ <!-- Value-axis labels -->
120711
+ @for (lbl of vm().axisLabels; track $index) {
120712
+ <text
120713
+ [attr.x]="lbl.x"
120714
+ [attr.y]="lbl.y"
120715
+ [attr.text-anchor]="lbl.textAnchor"
120716
+ [attr.font-size]="lbl.fontSize"
120717
+ [attr.fill]="lbl.fill"
120718
+ [attr.dominant-baseline]="lbl.dominantBaseline ?? 'auto'"
120719
+ >
120720
+ {{ lbl.text }}
120721
+ </text>
120722
+ }
120723
+
120724
+ <!-- Secondary value-axis gridlines -->
120725
+ @for (gl of vm().secondaryGridlines ?? []; track $index) {
120726
+ <line
120727
+ [attr.x1]="gl.x1"
120728
+ [attr.y1]="gl.y1"
120729
+ [attr.x2]="gl.x2"
120730
+ [attr.y2]="gl.y2"
120731
+ [attr.stroke]="gl.stroke"
120732
+ [attr.stroke-width]="gl.strokeWidth"
120733
+ [attr.stroke-dasharray]="gl.dashArray"
120734
+ [attr.opacity]="gl.opacity ?? 1"
120735
+ />
120736
+ }
120737
+
120738
+ <!-- Secondary value-axis labels -->
120739
+ @for (lbl of vm().secondaryAxisLabels ?? []; track $index) {
120740
+ <text
120741
+ [attr.x]="lbl.x"
120742
+ [attr.y]="lbl.y"
120743
+ [attr.text-anchor]="lbl.textAnchor"
120744
+ [attr.font-size]="lbl.fontSize"
120745
+ [attr.fill]="lbl.fill"
120746
+ [attr.dominant-baseline]="lbl.dominantBaseline ?? 'auto'"
120747
+ [attr.opacity]="lbl.opacity ?? 1"
120748
+ [attr.transform]="lbl.transform"
120749
+ >
120750
+ {{ lbl.text }}
120751
+ </text>
120752
+ }
120753
+
120754
+ <!-- Zero line -->
120755
+ @if (vm().zeroLine) {
120756
+ <line
120757
+ [attr.x1]="vm().zeroLine!.x1"
120758
+ [attr.y1]="vm().zeroLine!.y1"
120759
+ [attr.x2]="vm().zeroLine!.x2"
120760
+ [attr.y2]="vm().zeroLine!.y2"
120761
+ [attr.stroke]="vm().zeroLine!.stroke"
120762
+ [attr.stroke-width]="vm().zeroLine!.strokeWidth"
120763
+ />
120764
+ }
120765
+
120766
+ <!-- Category-axis labels -->
120767
+ @for (lbl of vm().categoryLabels; track $index) {
120768
+ <text
120769
+ [attr.x]="lbl.x"
120770
+ [attr.y]="lbl.y"
120771
+ [attr.text-anchor]="lbl.textAnchor"
120772
+ [attr.font-size]="lbl.fontSize"
120773
+ [attr.fill]="lbl.fill"
120774
+ [attr.dominant-baseline]="lbl.dominantBaseline ?? 'auto'"
120775
+ >
120776
+ {{ lbl.text }}
120777
+ </text>
120778
+ }
120779
+
120780
+ <!-- Data primitives (bars, lines, arcs, dots ...); data marks carry
120781
+ data-chart-* hit-testing attributes (inert outside edit mode). -->
120782
+ <g pptx-chart-primitives [primitives]="vm().primitives"></g>
120783
+
120784
+ <!-- Data labels -->
120785
+ @for (dl of vm().dataLabels; track $index) {
120786
+ <text
120787
+ [attr.x]="dl.x"
120788
+ [attr.y]="dl.y"
120789
+ [attr.text-anchor]="dl.textAnchor"
120790
+ [attr.font-size]="dl.fontSize"
120791
+ [attr.fill]="dl.fill"
120792
+ [attr.font-weight]="dl.fontWeight ?? 'normal'"
120793
+ [attr.dominant-baseline]="dl.dominantBaseline ?? 'auto'"
120794
+ >
120795
+ {{ dl.text }}
120796
+ </text>
120797
+ }
120798
+
120799
+ <!-- Legend -->
120800
+ @if (legendItems().length > 0) {
120801
+ @for (item of legendItems(); track $index) {
120802
+ <g [attr.transform]="legendTransform(item)">
120803
+ @if (item.lineSwatch) {
120804
+ <g pptx-chart-primitives [primitives]="item.lineSwatch.primitives"></g>
120805
+ } @else {
120806
+ <rect
119433
120807
  x="0"
119434
- y="0"
119435
- [attr.width]="def.width"
119436
- [attr.height]="def.height"
119437
- [attr.preserveAspectRatio]="def.preserveAspectRatio"
120808
+ y="-7"
120809
+ [attr.width]="swatchSize"
120810
+ [attr.height]="swatchSize"
120811
+ rx="2"
120812
+ [attr.fill]="item.color"
119438
120813
  />
119439
- </pattern>
119440
- }
119441
- </defs>
119442
- }
119443
-
119444
- <!-- Chart-area fill; absent when the deck declares c:chartSpace/a:noFill -->
119445
- @if (vm().areaFill) {
119446
- <rect
119447
- x="0"
119448
- y="0"
119449
- [attr.width]="vm().svgWidth"
119450
- [attr.height]="vm().svgHeight"
119451
- [attr.rx]="vm().areaRadius"
119452
- [attr.fill]="vm().areaFill"
119453
- />
119454
- }
119455
-
119456
- <!-- Chart title (data-chart-part enables in-place editing in edit mode) -->
119457
- @if (vm().title) {
119458
- <text
119459
- [attr.x]="vm().titleX"
119460
- [attr.y]="vm().titleY"
119461
- text-anchor="middle"
119462
- [attr.font-size]="vm().titleStyle?.fontSize ?? 12"
119463
- [attr.font-weight]="vm().titleStyle?.fontWeight ?? 600"
119464
- [attr.font-family]="vm().titleStyle?.fontFamily"
119465
- [attr.fill]="vm().titleStyle?.fill ?? '#1e293b'"
119466
- data-chart-part="title"
119467
- >
119468
- @if (vm().titleRunSpans; as titleRunSpans) {
119469
- @for (run of titleRunSpans; track $index) {
119470
- <tspan
119471
- [attr.font-size]="run.fontSize"
119472
- [attr.font-weight]="run.fontWeight"
119473
- [attr.font-style]="run.fontStyle"
119474
- [attr.font-family]="run.fontFamily"
119475
- [attr.fill]="run.fill"
119476
- >
119477
- {{ run.text }}
119478
- </tspan>
119479
120814
  }
119480
- } @else {
119481
- {{ vm().title }}
119482
- }
119483
- </text>
119484
- }
119485
-
119486
- <!-- Gridlines -->
119487
- @for (gl of vm().gridlines; track $index) {
119488
- <line
119489
- [attr.x1]="gl.x1"
119490
- [attr.y1]="gl.y1"
119491
- [attr.x2]="gl.x2"
119492
- [attr.y2]="gl.y2"
119493
- [attr.stroke]="gl.stroke"
119494
- [attr.stroke-width]="gl.strokeWidth"
119495
- />
119496
- }
119497
-
119498
- <!-- Value-axis labels -->
119499
- @for (lbl of vm().axisLabels; track $index) {
119500
- <text
119501
- [attr.x]="lbl.x"
119502
- [attr.y]="lbl.y"
119503
- [attr.text-anchor]="lbl.textAnchor"
119504
- [attr.font-size]="lbl.fontSize"
119505
- [attr.fill]="lbl.fill"
119506
- [attr.dominant-baseline]="lbl.dominantBaseline ?? 'auto'"
119507
- >
119508
- {{ lbl.text }}
119509
- </text>
119510
- }
119511
-
119512
- <!-- Secondary value-axis gridlines -->
119513
- @for (gl of vm().secondaryGridlines ?? []; track $index) {
119514
- <line
119515
- [attr.x1]="gl.x1"
119516
- [attr.y1]="gl.y1"
119517
- [attr.x2]="gl.x2"
119518
- [attr.y2]="gl.y2"
119519
- [attr.stroke]="gl.stroke"
119520
- [attr.stroke-width]="gl.strokeWidth"
119521
- [attr.stroke-dasharray]="gl.dashArray"
119522
- [attr.opacity]="gl.opacity ?? 1"
119523
- />
119524
- }
119525
-
119526
- <!-- Secondary value-axis labels -->
119527
- @for (lbl of vm().secondaryAxisLabels ?? []; track $index) {
119528
- <text
119529
- [attr.x]="lbl.x"
119530
- [attr.y]="lbl.y"
119531
- [attr.text-anchor]="lbl.textAnchor"
119532
- [attr.font-size]="lbl.fontSize"
119533
- [attr.fill]="lbl.fill"
119534
- [attr.dominant-baseline]="lbl.dominantBaseline ?? 'auto'"
119535
- [attr.opacity]="lbl.opacity ?? 1"
119536
- [attr.transform]="lbl.transform"
119537
- >
119538
- {{ lbl.text }}
119539
- </text>
119540
- }
119541
-
119542
- <!-- Zero line -->
119543
- @if (vm().zeroLine) {
119544
- <line
119545
- [attr.x1]="vm().zeroLine!.x1"
119546
- [attr.y1]="vm().zeroLine!.y1"
119547
- [attr.x2]="vm().zeroLine!.x2"
119548
- [attr.y2]="vm().zeroLine!.y2"
119549
- [attr.stroke]="vm().zeroLine!.stroke"
119550
- [attr.stroke-width]="vm().zeroLine!.strokeWidth"
119551
- />
119552
- }
119553
-
119554
- <!-- Category-axis labels -->
119555
- @for (lbl of vm().categoryLabels; track $index) {
119556
- <text
119557
- [attr.x]="lbl.x"
119558
- [attr.y]="lbl.y"
119559
- [attr.text-anchor]="lbl.textAnchor"
119560
- [attr.font-size]="lbl.fontSize"
119561
- [attr.fill]="lbl.fill"
119562
- [attr.dominant-baseline]="lbl.dominantBaseline ?? 'auto'"
119563
- >
119564
- {{ lbl.text }}
119565
- </text>
119566
- }
119567
-
119568
- <!-- Data primitives (bars, lines, arcs, dots ...); data marks carry
119569
- data-chart-* hit-testing attributes (inert outside edit mode). -->
119570
- <g pptx-chart-primitives [primitives]="vm().primitives"></g>
119571
-
119572
- <!-- Data labels -->
119573
- @for (dl of vm().dataLabels; track $index) {
119574
- <text
119575
- [attr.x]="dl.x"
119576
- [attr.y]="dl.y"
119577
- [attr.text-anchor]="dl.textAnchor"
119578
- [attr.font-size]="dl.fontSize"
119579
- [attr.fill]="dl.fill"
119580
- [attr.font-weight]="dl.fontWeight ?? 'normal'"
119581
- [attr.dominant-baseline]="dl.dominantBaseline ?? 'auto'"
119582
- >
119583
- {{ dl.text }}
119584
- </text>
119585
- }
119586
-
119587
- <!-- Legend -->
119588
- @if (legendItems().length > 0) {
119589
- @for (item of legendItems(); track $index) {
119590
- <g [attr.transform]="legendTransform(item)">
119591
- <rect
119592
- x="0"
119593
- y="-7"
119594
- [attr.width]="swatchSize"
119595
- [attr.height]="swatchSize"
119596
- rx="2"
119597
- [attr.fill]="item.color"
119598
- />
119599
120815
  <text
119600
120816
  [attr.x]="swatchSize + 3"
119601
120817
  y="3"
@@ -119910,121 +121126,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
119910
121126
  `, styles: [".pptx-ng-bar-chart-3d-scene{width:100%;height:100%;display:block}\n"] }]
119911
121127
  }], ctorParameters: () => [], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], selectedPart: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedPart", required: false }] }], textStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "textStyle", required: false }] }], partSelect: [{ type: i0.Output, args: ["partSelect"] }], valueDragPreview: [{ type: i0.Output, args: ["valueDragPreview"] }], valueDragCommit: [{ type: i0.Output, args: ["valueDragCommit"] }], sceneRef: [{ type: i0.ViewChild, args: ['scene', { isSignal: true }] }] } });
119912
121128
 
119913
- // ─────────────────────────────────────────────────────────────────────────────
119914
- // Direct part-editing gate
119915
- // ─────────────────────────────────────────────────────────────────────────────
119916
- /**
119917
- * May this chart's individual parts (title, series, data points) be entered
119918
- * for direct on-canvas editing?
119919
- *
119920
- * Pure so it is unit-testable without a full Angular injection context:
119921
- * `ChartElementViewComponent`'s constructor runs an `effect()` that needs a
119922
- * `ChangeDetectionScheduler` this package's TestBed-free suite doesn't
119923
- * provide. G8: `a:graphicFrameLocks/@noDrilldown` forbids the drill-down,
119924
- * even when the chart is otherwise selected + editable.
119925
- */
119926
- function chartCanEditParts(editable, isSelected, hasEditor, element) {
119927
- return editable && isSelected && hasEditor && canDrillDown(element);
119928
- }
119929
- // ─────────────────────────────────────────────────────────────────────────────
119930
- // Value-drag commit gate
119931
- // ─────────────────────────────────────────────────────────────────────────────
119932
- /**
119933
- * The chart data a finished drag should commit, or null when nothing should
119934
- * be committed (cancelled, or the press never became a drag).
119935
- */
119936
- function chartDragCommitData(session, commit) {
119937
- if (!commit || !session?.moved) {
119938
- return null;
119939
- }
119940
- return session.lastData;
119941
- }
119942
- /**
119943
- * Same gate as {@link chartDragCommitData}, for a pie/radar/stacked mark drag
119944
- * ({@link ChartMarkDragState} carries the same `moved`/`lastData` shape as the
119945
- * cartesian value-drag session, just resolved through a different geometry).
119946
- */
119947
- function chartMarkDragCommitData(session, commit) {
119948
- if (!commit || !session?.moved) {
119949
- return null;
119950
- }
119951
- return session.lastData;
119952
- }
119953
- // ─────────────────────────────────────────────────────────────────────────────
119954
- // 3D value-drag commit data (bar3D/line3D/area3D/pie3D/surface3D: no drag
119955
- // state machine of their own, the shared three.js scene reports the
119956
- // live/final value directly)
119957
- // ─────────────────────────────────────────────────────────────────────────────
119958
- /**
119959
- * The chart data a 3D scene's `onValueDragPreview`/`onValueDragCommit` should
119960
- * apply, or `null` when the part carries no point index (a series-level part,
119961
- * or a part shape the 3D scenes never actually emit for a value drag) or there
119962
- * is no chart data to update against.
119963
- *
119964
- * Pure so it is unit-testable without a WebGL context: `BarChart3DRendererComponent`
119965
- * / `LineChart3DRendererComponent` / `AreaChart3DRendererComponent` /
119966
- * `PieChart3DRendererComponent` / `SurfaceChart3DRendererComponent` forward
119967
- * their `valueDragPreview`/`valueDragCommit` outputs here via
119968
- * `ChartElementViewComponent`.
119969
- */
119970
- function chart3DPointValueUpdate(chartData, part, value) {
119971
- if (!chartData || part.pointIndex === undefined) {
119972
- return null;
119973
- }
119974
- return withChartPointValue(chartData, part.seriesIndex, part.pointIndex, value);
119975
- }
119976
- /**
119977
- * Commit an on-canvas chart edit through the editor's normal element-update
119978
- * path (the exact channel the inspector uses: one history snapshot per call).
119979
- * No-op without an editor or when the element is not on any slide.
119980
- *
119981
- * `templateSlideId` is the id of the slide the hosting canvas renders (from
119982
- * `SLIDE_CONTEXT`); it resolves template (master/layout) chart elements, which
119983
- * live in the per-slide template store rather than in `slides[].elements`.
119984
- */
119985
- function commitChartElementData(editor, elementId, chartData, templateSlideId) {
119986
- if (!editor) {
119987
- return;
119988
- }
119989
- const slideIndex = findOwningSlideIndex(editor.slides(), elementId, templateSlideId);
119990
- if (slideIndex < 0) {
119991
- return;
119992
- }
119993
- editor.updateElement(slideIndex, elementId, { chartData });
119994
- }
119995
- // ─────────────────────────────────────────────────────────────────────────────
119996
- // Interaction stylesheet (shared base rules + Angular's own badge/editor CSS)
119997
- // ─────────────────────────────────────────────────────────────────────────────
119998
- const STYLE_ELEMENT_ID = 'pptx-ng-chart-interaction-styles';
119999
- /**
120000
- * Angular-only interaction CSS, injected once into `document.head` alongside
120001
- * the shared `[data-chart-part]` / selected-mark rules from
120002
- * `ensureChartInteractionStyles` (`pptx-viewer-shared`). Component styles are
120003
- * view-encapsulated in Angular, so they cannot reach into the chart
120004
- * renderer's SVG or style the badge/title-input this component projects
120005
- * next to it.
120006
- */
120007
- const INTERACTION_CSS = `
120008
- .pptx-ng-chart-view { position: relative; width: 100%; height: 100%; }
120009
- .pptx-ng-chart-drag-badge { position: absolute; top: 4px; right: 4px; z-index: 10; border-radius: 4px; background: rgba(37, 99, 235, 0.9); padding: 2px 6px; font-size: 10px; font-weight: 500; color: #fff; pointer-events: none; }
120010
- .pptx-ng-chart-title-input { position: absolute; left: 50%; top: 2px; z-index: 10; width: 60%; transform: translateX(-50%); border: 1px solid #94a3b8; border-radius: 4px; background: #fff; padding: 2px 4px; text-align: center; font-size: 11px; color: #0f172a; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); }
120011
- `;
120012
- /**
120013
- * Inject the interaction stylesheets for chart part hit targets: the shared
120014
- * base rules (singleton, shared across all five bindings) plus Angular's own
120015
- * badge/title-input CSS (singleton, this binding only).
120016
- */
120017
- function ensureChartInteractionStyles() {
120018
- ensureChartInteractionStyles$1();
120019
- if (typeof document === 'undefined' || document.getElementById(STYLE_ELEMENT_ID)) {
120020
- return;
120021
- }
120022
- const style = document.createElement('style');
120023
- style.id = STYLE_ELEMENT_ID;
120024
- style.textContent = INTERACTION_CSS;
120025
- document.head.appendChild(style);
120026
- }
120027
-
120028
121129
  /**
120029
121130
  * chart-part-selection.service.ts: the bridge between on-canvas chart part
120030
121131
  * clicks (`ChartElementViewComponent`) and the chart inspector
@@ -126160,6 +127261,15 @@ function buildAngularImageRenderView(element) {
126160
127261
  if (computed.opacity !== undefined) {
126161
127262
  imageStyle.opacity = computed.opacity;
126162
127263
  }
127264
+ // `a:blipFill/@rotWithShape="0"`: keep the picture's pixels upright/fixed to
127265
+ // the page frame while `frameGeometryMask`'s container still carries the
127266
+ // shape's own rotate/flip. `undefined` (no-op) in the overwhelming majority
127267
+ // of cases; see `getImageFillCounterTransform`.
127268
+ const fillCounterTransform = getImageFillCounterTransform(element, Boolean(imageStyle['transform']));
127269
+ if (fillCounterTransform) {
127270
+ imageStyle['transform'] = fillCounterTransform;
127271
+ imageStyle['transform-origin'] = 'center';
127272
+ }
126163
127273
  const frameGeometryMask = buildAngularImageContainerMask(element);
126164
127274
  const colorWash = getImageColorWashStyle(isImageLikeElement$1(element) ? element.imageEffects?.colorWash : undefined);
126165
127275
  const colorWashStyle = colorWash
@@ -127952,6 +129062,16 @@ class SlideCanvasComponent {
127952
129062
  return box ? (this.elementById(box.id) ?? null) : null;
127953
129063
  }, /* @ts-ignore */
127954
129064
  ...(ngDevMode ? [{ debugName: "singleSelectedElement" }] : /* istanbul ignore next */ []));
129065
+ /**
129066
+ * The single selected element when (and only when) it is a chart: gates
129067
+ * the "Chart Elements"/"Chart Styles"/"Chart Filters" quick-action overlay,
129068
+ * which only makes sense for exactly one selected chart.
129069
+ */
129070
+ this.quickActionsChartElement = computed(() => {
129071
+ const el = this.singleSelectedElement();
129072
+ return el && el.type === 'chart' ? el : null;
129073
+ }, /* @ts-ignore */
129074
+ ...(ngDevMode ? [{ debugName: "quickActionsChartElement" }] : /* istanbul ignore next */ []));
127955
129075
  /**
127956
129076
  * Resize-handle render boxes (stage coords) for the single selection. Empty
127957
129077
  * for an element whose authored `a:spLocks/@noResize` pins its size.
@@ -128909,7 +130029,7 @@ class SlideCanvasComponent {
128909
130029
  // SmartArt), so template (master/layout) element commits can resolve
128910
130030
  // their owning slide; template elements are absent from slides[].elements.
128911
130031
  { provide: SLIDE_CONTEXT, useExisting: forwardRef(() => SlideCanvasComponent) },
128912
- ], viewQueries: [{ propertyName: "textEditor", first: true, predicate: ["textEditor"], descendants: true, isSignal: true }, { propertyName: "listEditor", first: true, predicate: InlineListEditorComponent, descendants: true, isSignal: true }, { propertyName: "stageRef", first: true, predicate: ["stage"], descendants: true, isSignal: true }, { propertyName: "viewportRef", first: true, predicate: ["viewport"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n\t#viewport\n\tclass=\"pptx-ng-canvas-viewport\"\n\t[attr.data-pptx-viewport]=\"interactive() ? '' : null\"\n\t(pointerdown)=\"onViewportPointerDown($event)\"\n>\n\t<div class=\"pptx-ng-canvas-wrapper\" [ngStyle]=\"wrapperStyle()\">\n\t\t<div\n\t\t\t#stage\n\t\t\tclass=\"pptx-ng-canvas-stage\"\n\t\t\t[class.is-editable]=\"editable()\"\n\t\t\trole=\"region\"\n\t\t\t[attr.aria-roledescription]=\"interactive() || presenting() ? 'slide' : null\"\n\t\t\t[attr.data-pptx-ai-active]=\"aiActive() ? 'true' : null\"\n\t\t\t[ngStyle]=\"stageStyle()\"\n\t\t\t(pointerdown)=\"onStagePointerDown($event)\"\n\t\t\t(contextmenu)=\"onContextMenu($event)\"\n\t\t\t(dblclick)=\"onDblClick($event)\"\n\t\t>\n\t\t\t<!--\n\t\t\t\tTemplate layer: inherited master/layout elements, rendered BEHIND\n\t\t\t\tthe slide's own elements (lower z-index). Interactive + given the\n\t\t\t\tamber editable affordance only while editTemplateMode is on; when\n\t\t\t\toff they render inertly with no affordance, exactly as core\n\t\t\t\tdelivered them. `marked` keeps the data-pptx-element contract on\n\t\t\t\tthem even while locked: the marker means \"rendered slide element\",\n\t\t\t\tnot \"editable right now\".\n\t\t\t-->\n\t\t\t@for (element of visibleTemplateElements(); track element.id; let i = $index) {\n\t\t\t\t<pptx-element-renderer\n\t\t\t\t\t[element]=\"element\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zIndex]=\"i\"\n\t\t\t\t\t[obstacles]=\"connectorObstacles()\"\n\t\t\t\t\t[canvasWidth]=\"canvasSize().width\"\n\t\t\t\t\t[canvasHeight]=\"canvasSize().height\"\n\t\t\t\t\t[interactive]=\"interactive() && editTemplateMode()\"\n\t\t\t\t\t[marked]=\"interactive()\"\n\t\t\t\t\t[exposeElementId]=\"exposeElementIds()\"\n\t\t\t\t\t[editable]=\"editable() && editTemplateMode()\"\n\t\t\t\t\t[fieldContext]=\"fieldContext()\"\n\t\t\t\t\t[slideElements]=\"allElements()\"\n\t\t\t\t\t[editTemplateMode]=\"editTemplateMode()\"\n\t\t\t\t\t[editingElementId]=\"editingId()\"\n\t\t\t\t\t(cellCommit)=\"cellCommit.emit($event)\"\n\t\t\t\t\t(tableChange)=\"tableChange.emit($event)\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@for (element of elements(); track element.id; let i = $index) {\n\t\t\t\t<pptx-element-renderer\n\t\t\t\t\t[element]=\"element\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zIndex]=\"visibleTemplateElements().length + i\"\n\t\t\t\t\t[obstacles]=\"connectorObstacles()\"\n\t\t\t\t\t[canvasWidth]=\"canvasSize().width\"\n\t\t\t\t\t[canvasHeight]=\"canvasSize().height\"\n\t\t\t\t\t[interactive]=\"interactive()\"\n\t\t\t\t\t[exposeElementId]=\"exposeElementIds()\"\n\t\t\t\t\t[presenting]=\"presenting()\"\n\t\t\t\t\t[editable]=\"editable()\"\n\t\t\t\t\t[fieldContext]=\"fieldContext()\"\n\t\t\t\t\t[slideElements]=\"allElements()\"\n\t\t\t\t\t[editTemplateMode]=\"false\"\n\t\t\t\t\t[editingElementId]=\"editingId()\"\n\t\t\t\t\t(cellCommit)=\"cellCommit.emit($event)\"\n\t\t\t\t\t(tableChange)=\"tableChange.emit($event)\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t<pptx-activex-controls-overlay\n\t\t\t\t[controls]=\"slide()?.activeXControls ?? []\"\n\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t/>\n\t\t\t@if (aiHighlights().length > 0 || aiActive()) {\n\t\t\t\t<pptx-ai-focus-highlight-overlay\n\t\t\t\t\t[highlights]=\"aiHighlights()\"\n\t\t\t\t\t[elements]=\"elements()\"\n\t\t\t\t\t[activeSlideIndex]=\"aiActiveSlideIndex()\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@if (aiChangeBatch(); as batch) {\n\t\t\t\t<pptx-ai-change-overlay [batch]=\"batch\" [activeSlideIndex]=\"aiActiveSlideIndex()\" />\n\t\t\t}\n\t\t\t@for (box of selectionBoxes(); track box.id) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-selection\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"box.x\"\n\t\t\t\t\t[style.top.px]=\"box.y\"\n\t\t\t\t\t[style.width.px]=\"box.width\"\n\t\t\t\t\t[style.height.px]=\"box.height\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (h of handleBoxes(); track h.handle) {\n\t\t\t\t@let controlArtwork = selectionControlArtwork(h, h.handle);\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"resize\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.resize' | translate: { handle: h.handle }\"\n\t\t\t\t\t[ngStyle]=\"controlArtwork.frame\"\n\t\t\t\t\t[style.cursor]=\"h.cursor\"\n\t\t\t\t\t[style.--pptx-selection-width.px]=\"singleSelected()?.width\"\n\t\t\t\t\t[style.--pptx-selection-height.px]=\"singleSelected()?.height\"\n\t\t\t\t\t(pointerdown)=\"onHandlePointerDown($event, h.handle)\"\n\t\t\t\t\t(keydown)=\"onResizeHandleKeydown($event, h.handle)\"\n\t\t\t\t>\n\t\t\t\t\t<span data-pptx-handle-hit [ngStyle]=\"resizeHitAreaStyle(h.handle)\"></span>\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"pptx-ng-control-artwork\"\n\t\t\t\t\t\tdata-pptx-handle-artwork\n\t\t\t\t\t\t[ngStyle]=\"controlArtwork.artwork\"\n\t\t\t\t\t></span>\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t@if (marqueeRect(); as mr) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-marquee\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"mr.x\"\n\t\t\t\t\t[style.top.px]=\"mr.y\"\n\t\t\t\t\t[style.width.px]=\"mr.width\"\n\t\t\t\t\t[style.height.px]=\"mr.height\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (g of snapGuides(); track $index) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-snap-guide\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos : g.start\"\n\t\t\t\t\t[style.top.px]=\"g.axis === 'x' ? g.start : g.pos\"\n\t\t\t\t\t[style.width.px]=\"g.axis === 'x' ? 0 : g.end - g.start\"\n\t\t\t\t\t[style.height.px]=\"g.axis === 'x' ? g.end - g.start : 0\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@if (rotateHandle(); as rh) {\n\t\t\t\t@let rotateArtwork = selectionControlArtwork(rh);\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-rotate-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.z-index]=\"editingBox() ? 10003 : null\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"rotate\"\n\t\t\t\t\t[pptxRotateHandleFor]=\"singleSelected()?.id\"\n\t\t\t\t\t[pptxRotateHandleStage]=\"stage\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.rotate' | translate\"\n\t\t\t\t\t[ngStyle]=\"rotateArtwork.frame\"\n\t\t\t\t\t(pointerdown)=\"onRotatePointerDown($event)\"\n\t\t\t\t\t(keydown)=\"onRotateHandleKeydown($event)\"\n\t\t\t\t>\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"pptx-ng-control-artwork\"\n\t\t\t\t\t\tdata-pptx-handle-artwork\n\t\t\t\t\t\t[ngStyle]=\"rotateArtwork.artwork\"\n\t\t\t\t\t></span>\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tShape-adjustment affordances (amber diamonds), ONE per `a:avLst`\n\t\t\t\tguide. Existence, position and cursor all come from the SHARED\n\t\t\t\t`getShapeAdjustmentHandleDescriptors`, so they appear only for a\n\t\t\t\tgeometry with an adjustable parameter, sit where the other four\n\t\t\t\tbindings put them, and are absent for a shape locked with\n\t\t\t\t`noAdjustHandles`. Dragging one writes `shapeAdjustments`; it does\n\t\t\t\tNOT resize the element.\n\t\t\t-->\n\t\t\t@for (ah of adjustHandles(); track ah.key) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-adjust-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"adjust\"\n\t\t\t\t\t[attr.data-pptx-adjust-key]=\"ah.key\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.adjust' | translate\"\n\t\t\t\t\t[style.left.px]=\"ah.left\"\n\t\t\t\t\t[style.top.px]=\"ah.top\"\n\t\t\t\t\t[style.width.px]=\"ah.size\"\n\t\t\t\t\t[style.height.px]=\"ah.size\"\n\t\t\t\t\t[style.cursor]=\"ah.cursor\"\n\t\t\t\t\t(pointerdown)=\"onAdjustPointerDown($event, ah)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tConnector endpoint authoring: drag an end onto a shape's connection\n\t\t\t\tpoint to bind it (`a:stCxn` / `a:endCxn`), or off one to detach.\n\t\t\t\tCandidate sites are revealed only while an end is in flight so they\n\t\t\t\tnever obscure the deck at rest.\n\t\t\t-->\n\t\t\t@for (site of connectorSiteCandidates(); track site.key) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-connection-site\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[class.is-snapped]=\"site.snapped\"\n\t\t\t\t\tdata-pptx-connection-site\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[style.left.px]=\"site.left\"\n\t\t\t\t\t[style.top.px]=\"site.top\"\n\t\t\t\t\t[style.width.px]=\"site.size\"\n\t\t\t\t\t[style.height.px]=\"site.size\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (ep of connectorEndpoints(); track ep.kind) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-connector-endpoint\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[class.is-attached]=\"ep.attached\"\n\t\t\t\t\t[attr.data-pptx-connector-endpoint]=\"ep.kind\"\n\t\t\t\t\t[attr.data-pptx-connector-attached]=\"ep.attached\"\n\t\t\t\t\t[attr.aria-label]=\"\n\t\t\t\t\t\t(ep.kind === 'start'\n\t\t\t\t\t\t\t? 'pptx.canvas.connectorEndpointStart'\n\t\t\t\t\t\t\t: 'pptx.canvas.connectorEndpointEnd'\n\t\t\t\t\t\t) | translate\n\t\t\t\t\t\"\n\t\t\t\t\t[style.left.px]=\"ep.left\"\n\t\t\t\t\t[style.top.px]=\"ep.top\"\n\t\t\t\t\t[style.width.px]=\"ep.size\"\n\t\t\t\t\t[style.height.px]=\"ep.size\"\n\t\t\t\t\t(pointerdown)=\"onConnectorEndpointPointerDown($event, ep.kind)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t\t@if (editingBox(); as eb) {\n\t\t\t\t@if (inlineListSeed(); as seed) {\n\t\t\t\t\t@for (session of [seed]; track session) {\n\t\t\t\t\t\t<pptx-inline-list-editor\n\t\t\t\t\t\t\t[element]=\"eb.element\"\n\t\t\t\t\t\t\t[seed]=\"session\"\n\t\t\t\t\t\t\t[activationSelection]=\"listActivationSelection()\"\n\t\t\t\t\t\t\t[spellCheck]=\"spellCheck()\"\n\t\t\t\t\t\t\t(textInput)=\"textInput.emit($event)\"\n\t\t\t\t\t\t\t(textCommit)=\"textCommit.emit($event)\"\n\t\t\t\t\t\t\t(textCancel)=\"textCancel.emit()\"\n\t\t\t\t\t\t\t(textFormat)=\"textFormat.emit($event)\"\n\t\t\t\t\t\t\t(listSession)=\"listSession.emit($event)\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t} @else {\n\t\t\t\t\t<textarea\n\t\t\t\t\t\t#textEditor\n\t\t\t\t\t\tdata-inline-editor\n\t\t\t\t\t\tclass=\"pptx-ng-text-editor\"\n\t\t\t\t\t\taria-label=\"Edit slide text\"\n\t\t\t\t\t\t[spellcheck]=\"spellCheck()\"\n\t\t\t\t\t\t[style.left.px]=\"eb.x\"\n\t\t\t\t\t\t[style.top.px]=\"eb.y\"\n\t\t\t\t\t\t[style.width.px]=\"eb.width\"\n\t\t\t\t\t\t[style.height.px]=\"eb.height\"\n\t\t\t\t\t\t(pointerdown)=\"$event.stopPropagation()\"\n\t\t\t\t\t\t(input)=\"onTextInput($event, eb.id)\"\n\t\t\t\t\t\t(blur)=\"commitText($event, eb.id)\"\n\t\t\t\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t\t\t\t></textarea>\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tView overlays: editor aids only, never on thumbnails/preview/presentation.\n\t\t\t\tAll are pointer-events:none so they never break selection/drag.\n\t\t\t\tNone carry data-pptx-element / aria-roledescription / data-pptx-viewport.\n\t\t\t-->\n\t\t\t@if (interactive() && showGrid()) {\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-overlay-grid\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t>\n\t\t\t\t\t<defs>\n\t\t\t\t\t\t<pattern\n\t\t\t\t\t\t\t[attr.id]=\"gridPatternId\"\n\t\t\t\t\t\t\t[attr.width]=\"gridSpacingPx()\"\n\t\t\t\t\t\t\t[attr.height]=\"gridSpacingPx()\"\n\t\t\t\t\t\t\tpatternUnits=\"userSpaceOnUse\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t[attr.cx]=\"gridSpacingPx() / 2\"\n\t\t\t\t\t\t\t\t[attr.cy]=\"gridSpacingPx() / 2\"\n\t\t\t\t\t\t\t\tr=\"0.6\"\n\t\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.55)\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</pattern>\n\t\t\t\t\t</defs>\n\t\t\t\t\t<rect\n\t\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t\t\t[attr.fill]=\"'url(#' + gridPatternId + ')'\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t@if (interactive() && showGuides()) {\n\t\t\t\t<!--\n\t\t\t\t\tCenter crosshair: one horizontal line and one vertical line\n\t\t\t\t\tthrough the midpoint of the slide. Static; draggable guides\n\t\t\t\t\tare a follow-up.\n\t\t\t\t-->\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-overlay-guides\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t>\n\t\t\t\t\t<!-- Horizontal center guide -->\n\t\t\t\t\t<line\n\t\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t\t[attr.y1]=\"canvasSize().height / 2\"\n\t\t\t\t\t\t[attr.x2]=\"canvasSize().width\"\n\t\t\t\t\t\t[attr.y2]=\"canvasSize().height / 2\"\n\t\t\t\t\t\tstroke=\"rgba(99,102,241,0.7)\"\n\t\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t\t\tstroke-dasharray=\"6 3\"\n\t\t\t\t\t/>\n\t\t\t\t\t<!-- Vertical center guide -->\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"canvasSize().width / 2\"\n\t\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t\t[attr.x2]=\"canvasSize().width / 2\"\n\t\t\t\t\t\t[attr.y2]=\"canvasSize().height\"\n\t\t\t\t\t\tstroke=\"rgba(99,102,241,0.7)\"\n\t\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t\t\tstroke-dasharray=\"6 3\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tUser-created ruler guides.\n\t\t\t\tEach guide has a non-interactive line body and an interactive\n\t\t\t\tdrag handle. Double-click the handle to delete the guide.\n\n\t\t\t\tDeliberately OUTSIDE the showGuides() gate above (which owns the\n\t\t\t\tstatic centre crosshair): a guide the user just dragged off a ruler\n\t\t\t\tstrip must be visible immediately, as it is in Vue and Vanilla.\n\t\t\t\tWhile it lived under that gate, the drag-out gesture silently\n\t\t\t\tproduced an invisible guide unless View > Guides happened to be on.\n\t\t\t-->\n\t\t\t@if (interactive()) {\n\t\t\t\t@for (g of rulerGuidesSvc.rulerGuides(); track g.id) {\n\t\t\t\t\t<!-- Guide line body: pointer-events:none -->\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"pptx-ng-ruler-guide-line\"\n\t\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos : 0\"\n\t\t\t\t\t\t[style.top.px]=\"g.axis === 'y' ? g.pos : 0\"\n\t\t\t\t\t\t[style.width]=\"g.axis === 'x' ? '1px' : '100%'\"\n\t\t\t\t\t\t[style.height]=\"g.axis === 'y' ? '1px' : '100%'\"\n\t\t\t\t\t></div>\n\t\t\t\t\t<!-- Drag handle: pointer-events:auto -->\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"pptx-ng-ruler-guide-handle\"\n\t\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos - 4 : 0\"\n\t\t\t\t\t\t[style.top.px]=\"g.axis === 'y' ? g.pos - 4 : 0\"\n\t\t\t\t\t\t[style.width]=\"g.axis === 'x' ? '9px' : '100%'\"\n\t\t\t\t\t\t[style.height]=\"g.axis === 'y' ? '9px' : '100%'\"\n\t\t\t\t\t\t[style.cursor]=\"g.axis === 'x' ? 'col-resize' : 'row-resize'\"\n\t\t\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onGuidePointerDown($event, g.id, g.axis)\"\n\t\t\t\t\t\t(dblclick)=\"rulerGuidesSvc.onGuideDoubleClick($event, g.id)\"\n\t\t\t\t\t\t[title]=\"'pptx.canvas.guideTooltip' | translate\"\n\t\t\t\t\t></div>\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tLive ink stroke preview: shown while the user is drawing.\n\t\t\t\tpointer-events:none so it never intercepts element gestures.\n\t\t\t\tNo data-pptx-element / aria-roledescription / data-pptx-viewport.\n\n\t\t\t\tRenders `inkDrawing.liveStrokeView()`: the same plain-path /\n\t\t\t\tpressure-circle / tilt-nib decision `InkRendererComponent` makes for\n\t\t\t\ta committed stroke, so a calligraphic lean or pressure-variable\n\t\t\t\twidth shows up while the pointer is still down, not only after\n\t\t\t\trelease.\n\t\t\t-->\n\t\t\t@if (inkDrawing.active() && inkDrawing.liveStrokeView() && drawTool() !== 'select') {\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-ink-preview\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t\tstyle=\"position: absolute; inset: 0; pointer-events: none; z-index: 70\"\n\t\t\t\t>\n\t\t\t\t\t@if (inkDrawing.liveStrokeView()?.nibMarks; as nibMarks) {\n\t\t\t\t\t\t<g [attr.opacity]=\"inkDrawing.liveStrokeView()?.opacity\">\n\t\t\t\t\t\t\t@for (m of nibMarks; track $index) {\n\t\t\t\t\t\t\t\t<ellipse\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"m.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"m.cy\"\n\t\t\t\t\t\t\t\t\t[attr.rx]=\"m.rPerp\"\n\t\t\t\t\t\t\t\t\t[attr.ry]=\"m.rTilt\"\n\t\t\t\t\t\t\t\t\t[attr.transform]=\"'rotate(' + m.rotationDeg + ' ' + m.cx + ' ' + m.cy + ')'\"\n\t\t\t\t\t\t\t\t\t[attr.fill]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t} @else if (inkDrawing.liveStrokeView()?.circles; as circles) {\n\t\t\t\t\t\t<g [attr.opacity]=\"inkDrawing.liveStrokeView()?.opacity\">\n\t\t\t\t\t\t\t@for (c of circles; track $index) {\n\t\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"c.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"c.cy\"\n\t\t\t\t\t\t\t\t\t[attr.r]=\"c.r\"\n\t\t\t\t\t\t\t\t\t[attr.fill]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t[attr.d]=\"inkDrawing.liveStrokeView()?.d\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t[attr.stroke]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"inkDrawing.liveStrokeView()?.width\"\n\t\t\t\t\t\t\t[attr.stroke-opacity]=\"inkDrawing.liveStrokeView()?.opacity\"\n\t\t\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tProjected overlays (collaboration cursors + remote selections).\n\t\t\t\tThey live INSIDE the scaled stage so the stage's CSS\n\t\t\t\ttransform:scale() applies the on-screen scale exactly once and\n\t\t\t\tthey can be authored in raw, unscaled slide coordinates, exactly\n\t\t\t\tlike the React/Vue overlays.\n\t\t\t-->\n\t\t\t<ng-content />\n\t\t</div>\n\n\t\t<!--\n\t\t\tRuler strips: siblings to the scaled stage inside the wrapper div,\n\t\t\tabsolutely positioned at top:0/left:0 within the wrapper's padding area.\n\t\t\tThe wrapper's padding (RULER_THICKNESS px top+left) reserves space for\n\t\t\tthese strips so the stage content starts below/right of them.\n\t\t\tpointer-events:none so they never intercept element gestures.\n\t\t-->\n\t\t@if (interactive() && showRulers()) {\n\t\t\t<!-- Corner square at the intersection of the two ruler strips -->\n\t\t\t<div class=\"pptx-ng-ruler-corner\" aria-hidden=\"true\"></div>\n\n\t\t\t<!--\n\t\t\t\tHorizontal ruler: spans the top padding row of the wrapper.\n\t\t\t\tA guide is dropped on pointer-UP (not down), resolved by the shared\n\t\t\t\trulerDragToGuidePosition, so a stray click on the strip cannot\n\t\t\t\tdrop a guide the user never dragged out - the rule React, Vue,\n\t\t\t\tSvelte and Vanilla all follow.\n\t\t\t-->\n\t\t\t<svg\n\t\t\t\tclass=\"pptx-ng-ruler-h\"\n\t\t\t\tdata-pptx-ruler=\"h\"\n\t\t\t\trole=\"presentation\"\n\t\t\t\t[attr.width]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t[style.cursor]=\"editable() ? 'row-resize' : null\"\n\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onRulerPointerDown('h', $event)\"\n\t\t\t\t(pointerup)=\"rulerGuidesSvc.onRulerPointerUp('h', $event)\"\n\t\t\t>\n\t\t\t\t<rect\n\t\t\t\t\t[attr.width]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t\tfill=\"#1e293b\"\n\t\t\t\t/>\n\t\t\t\t@if (hRulerHighlight(); as highlight) {\n\t\t\t\t\t<rect\n\t\t\t\t\t\t[attr.x]=\"highlight.start\"\n\t\t\t\t\t\ty=\"0\"\n\t\t\t\t\t\t[attr.width]=\"highlight.span\"\n\t\t\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t\t\tfill=\"rgba(96,165,250,0.25)\"\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\t<line\n\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t[attr.y1]=\"rulerThickness - 0.5\"\n\t\t\t\t\t[attr.x2]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t\t[attr.y2]=\"rulerThickness - 0.5\"\n\t\t\t\t\tstroke=\"rgba(255,255,255,0.15)\"\n\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t/>\n\t\t\t\t@for (tick of hRulerTicks(); track $index) {\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"tick.position\"\n\t\t\t\t\t\t[attr.y1]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.x2]=\"tick.position\"\n\t\t\t\t\t\t[attr.y2]=\"rulerThickness - rulerThickness * (tick.isMajor ? 0.6 : 0.3)\"\n\t\t\t\t\t\tstroke=\"rgba(156,163,175,0.7)\"\n\t\t\t\t\t\t[attr.stroke-width]=\"tick.isMajor ? 1 : 0.5\"\n\t\t\t\t\t/>\n\t\t\t\t\t@if (tick.label) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t[attr.x]=\"tick.position + 2\"\n\t\t\t\t\t\t\t[attr.y]=\"rulerFontSize + 1\"\n\t\t\t\t\t\t\t[attr.font-size]=\"rulerFontSize\"\n\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.9)\"\n\t\t\t\t\t\t\tstyle=\"font-family: system-ui, sans-serif\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ tick.label }}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</svg>\n\n\t\t\t<!-- Vertical ruler: spans the left padding column of the wrapper -->\n\t\t\t<svg\n\t\t\t\tclass=\"pptx-ng-ruler-v\"\n\t\t\t\tdata-pptx-ruler=\"v\"\n\t\t\t\trole=\"presentation\"\n\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t[attr.height]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t[style.cursor]=\"editable() ? 'col-resize' : null\"\n\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onRulerPointerDown('v', $event)\"\n\t\t\t\t(pointerup)=\"rulerGuidesSvc.onRulerPointerUp('v', $event)\"\n\t\t\t>\n\t\t\t\t<rect\n\t\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t\tfill=\"#1e293b\"\n\t\t\t\t/>\n\t\t\t\t@if (vRulerHighlight(); as highlight) {\n\t\t\t\t\t<rect\n\t\t\t\t\t\tx=\"0\"\n\t\t\t\t\t\t[attr.y]=\"highlight.start\"\n\t\t\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.height]=\"highlight.span\"\n\t\t\t\t\t\tfill=\"rgba(96,165,250,0.25)\"\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\t<line\n\t\t\t\t\t[attr.x1]=\"rulerThickness - 0.5\"\n\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t[attr.x2]=\"rulerThickness - 0.5\"\n\t\t\t\t\t[attr.y2]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t\tstroke=\"rgba(255,255,255,0.15)\"\n\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t/>\n\t\t\t\t@for (tick of vRulerTicks(); track $index) {\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.y1]=\"tick.position\"\n\t\t\t\t\t\t[attr.x2]=\"rulerThickness - rulerThickness * (tick.isMajor ? 0.6 : 0.3)\"\n\t\t\t\t\t\t[attr.y2]=\"tick.position\"\n\t\t\t\t\t\tstroke=\"rgba(156,163,175,0.7)\"\n\t\t\t\t\t\t[attr.stroke-width]=\"tick.isMajor ? 1 : 0.5\"\n\t\t\t\t\t/>\n\t\t\t\t\t@if (tick.label) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\tx=\"2\"\n\t\t\t\t\t\t\t[attr.y]=\"tick.position + rulerFontSize + 2\"\n\t\t\t\t\t\t\t[attr.font-size]=\"rulerFontSize\"\n\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.9)\"\n\t\t\t\t\t\t\tstyle=\"font-family: system-ui, sans-serif\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ tick.label }}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</svg>\n\t\t}\n\t</div>\n</div>\n", styles: [".pptx-ng-canvas-stage.is-editable{touch-action:none}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ElementRendererComponent, selector: "pptx-element-renderer", inputs: ["element", "mediaDataUrls", "zIndex", "obstacles", "canvasWidth", "canvasHeight", "interactive", "marked", "exposeElementId", "presenting", "editable", "fieldContext", "slideElements", "editTemplateMode", "parentGroupFill", "editingElementId"], outputs: ["cellCommit", "tableChange"] }, { kind: "component", type: AiFocusHighlightOverlayComponent, selector: "pptx-ai-focus-highlight-overlay", inputs: ["highlights", "elements", "activeSlideIndex"] }, { kind: "component", type: AiChangeOverlayComponent, selector: "pptx-ai-change-overlay", inputs: ["batch", "activeSlideIndex"] }, { kind: "component", type: ActiveXControlsOverlayComponent, selector: "pptx-activex-controls-overlay", inputs: ["controls", "canvasSize"] }, { kind: "component", type: InlineListEditorComponent, selector: "pptx-inline-list-editor", inputs: ["element", "seed", "spellCheck", "activationSelection"], outputs: ["textInput", "textCommit", "textCancel", "textFormat", "listSession"] }, { kind: "directive", type: RotateHandlePlacementDirective, selector: "[pptxRotateHandleFor]", inputs: ["pptxRotateHandleFor", "pptxRotateHandleStage"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
130032
+ ], viewQueries: [{ propertyName: "textEditor", first: true, predicate: ["textEditor"], descendants: true, isSignal: true }, { propertyName: "listEditor", first: true, predicate: InlineListEditorComponent, descendants: true, isSignal: true }, { propertyName: "stageRef", first: true, predicate: ["stage"], descendants: true, isSignal: true }, { propertyName: "viewportRef", first: true, predicate: ["viewport"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n\t#viewport\n\tclass=\"pptx-ng-canvas-viewport\"\n\t[attr.data-pptx-viewport]=\"interactive() ? '' : null\"\n\t(pointerdown)=\"onViewportPointerDown($event)\"\n>\n\t<div class=\"pptx-ng-canvas-wrapper\" [ngStyle]=\"wrapperStyle()\">\n\t\t<div\n\t\t\t#stage\n\t\t\tclass=\"pptx-ng-canvas-stage\"\n\t\t\t[class.is-editable]=\"editable()\"\n\t\t\trole=\"region\"\n\t\t\t[attr.aria-roledescription]=\"interactive() || presenting() ? 'slide' : null\"\n\t\t\t[attr.data-pptx-ai-active]=\"aiActive() ? 'true' : null\"\n\t\t\t[ngStyle]=\"stageStyle()\"\n\t\t\t(pointerdown)=\"onStagePointerDown($event)\"\n\t\t\t(contextmenu)=\"onContextMenu($event)\"\n\t\t\t(dblclick)=\"onDblClick($event)\"\n\t\t>\n\t\t\t<!--\n\t\t\t\tTemplate layer: inherited master/layout elements, rendered BEHIND\n\t\t\t\tthe slide's own elements (lower z-index). Interactive + given the\n\t\t\t\tamber editable affordance only while editTemplateMode is on; when\n\t\t\t\toff they render inertly with no affordance, exactly as core\n\t\t\t\tdelivered them. `marked` keeps the data-pptx-element contract on\n\t\t\t\tthem even while locked: the marker means \"rendered slide element\",\n\t\t\t\tnot \"editable right now\".\n\t\t\t-->\n\t\t\t@for (element of visibleTemplateElements(); track element.id; let i = $index) {\n\t\t\t\t<pptx-element-renderer\n\t\t\t\t\t[element]=\"element\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zIndex]=\"i\"\n\t\t\t\t\t[obstacles]=\"connectorObstacles()\"\n\t\t\t\t\t[canvasWidth]=\"canvasSize().width\"\n\t\t\t\t\t[canvasHeight]=\"canvasSize().height\"\n\t\t\t\t\t[interactive]=\"interactive() && editTemplateMode()\"\n\t\t\t\t\t[marked]=\"interactive()\"\n\t\t\t\t\t[exposeElementId]=\"exposeElementIds()\"\n\t\t\t\t\t[editable]=\"editable() && editTemplateMode()\"\n\t\t\t\t\t[fieldContext]=\"fieldContext()\"\n\t\t\t\t\t[slideElements]=\"allElements()\"\n\t\t\t\t\t[editTemplateMode]=\"editTemplateMode()\"\n\t\t\t\t\t[editingElementId]=\"editingId()\"\n\t\t\t\t\t(cellCommit)=\"cellCommit.emit($event)\"\n\t\t\t\t\t(tableChange)=\"tableChange.emit($event)\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@for (element of elements(); track element.id; let i = $index) {\n\t\t\t\t<pptx-element-renderer\n\t\t\t\t\t[element]=\"element\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zIndex]=\"visibleTemplateElements().length + i\"\n\t\t\t\t\t[obstacles]=\"connectorObstacles()\"\n\t\t\t\t\t[canvasWidth]=\"canvasSize().width\"\n\t\t\t\t\t[canvasHeight]=\"canvasSize().height\"\n\t\t\t\t\t[interactive]=\"interactive()\"\n\t\t\t\t\t[exposeElementId]=\"exposeElementIds()\"\n\t\t\t\t\t[presenting]=\"presenting()\"\n\t\t\t\t\t[editable]=\"editable()\"\n\t\t\t\t\t[fieldContext]=\"fieldContext()\"\n\t\t\t\t\t[slideElements]=\"allElements()\"\n\t\t\t\t\t[editTemplateMode]=\"false\"\n\t\t\t\t\t[editingElementId]=\"editingId()\"\n\t\t\t\t\t(cellCommit)=\"cellCommit.emit($event)\"\n\t\t\t\t\t(tableChange)=\"tableChange.emit($event)\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t<pptx-activex-controls-overlay\n\t\t\t\t[controls]=\"slide()?.activeXControls ?? []\"\n\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t/>\n\t\t\t@if (aiHighlights().length > 0 || aiActive()) {\n\t\t\t\t<pptx-ai-focus-highlight-overlay\n\t\t\t\t\t[highlights]=\"aiHighlights()\"\n\t\t\t\t\t[elements]=\"elements()\"\n\t\t\t\t\t[activeSlideIndex]=\"aiActiveSlideIndex()\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@if (aiChangeBatch(); as batch) {\n\t\t\t\t<pptx-ai-change-overlay [batch]=\"batch\" [activeSlideIndex]=\"aiActiveSlideIndex()\" />\n\t\t\t}\n\t\t\t@for (box of selectionBoxes(); track box.id) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-selection\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"box.x\"\n\t\t\t\t\t[style.top.px]=\"box.y\"\n\t\t\t\t\t[style.width.px]=\"box.width\"\n\t\t\t\t\t[style.height.px]=\"box.height\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (h of handleBoxes(); track h.handle) {\n\t\t\t\t@let controlArtwork = selectionControlArtwork(h, h.handle);\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"resize\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.resize' | translate: { handle: h.handle }\"\n\t\t\t\t\t[ngStyle]=\"controlArtwork.frame\"\n\t\t\t\t\t[style.cursor]=\"h.cursor\"\n\t\t\t\t\t[style.--pptx-selection-width.px]=\"singleSelected()?.width\"\n\t\t\t\t\t[style.--pptx-selection-height.px]=\"singleSelected()?.height\"\n\t\t\t\t\t(pointerdown)=\"onHandlePointerDown($event, h.handle)\"\n\t\t\t\t\t(keydown)=\"onResizeHandleKeydown($event, h.handle)\"\n\t\t\t\t>\n\t\t\t\t\t<span data-pptx-handle-hit [ngStyle]=\"resizeHitAreaStyle(h.handle)\"></span>\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"pptx-ng-control-artwork\"\n\t\t\t\t\t\tdata-pptx-handle-artwork\n\t\t\t\t\t\t[ngStyle]=\"controlArtwork.artwork\"\n\t\t\t\t\t></span>\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t@if (marqueeRect(); as mr) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-marquee\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"mr.x\"\n\t\t\t\t\t[style.top.px]=\"mr.y\"\n\t\t\t\t\t[style.width.px]=\"mr.width\"\n\t\t\t\t\t[style.height.px]=\"mr.height\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (g of snapGuides(); track $index) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-snap-guide\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos : g.start\"\n\t\t\t\t\t[style.top.px]=\"g.axis === 'x' ? g.start : g.pos\"\n\t\t\t\t\t[style.width.px]=\"g.axis === 'x' ? 0 : g.end - g.start\"\n\t\t\t\t\t[style.height.px]=\"g.axis === 'x' ? g.end - g.start : 0\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@if (rotateHandle(); as rh) {\n\t\t\t\t@let rotateArtwork = selectionControlArtwork(rh);\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-rotate-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.z-index]=\"editingBox() ? 10003 : null\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"rotate\"\n\t\t\t\t\t[pptxRotateHandleFor]=\"singleSelected()?.id\"\n\t\t\t\t\t[pptxRotateHandleStage]=\"stage\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.rotate' | translate\"\n\t\t\t\t\t[ngStyle]=\"rotateArtwork.frame\"\n\t\t\t\t\t(pointerdown)=\"onRotatePointerDown($event)\"\n\t\t\t\t\t(keydown)=\"onRotateHandleKeydown($event)\"\n\t\t\t\t>\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"pptx-ng-control-artwork\"\n\t\t\t\t\t\tdata-pptx-handle-artwork\n\t\t\t\t\t\t[ngStyle]=\"rotateArtwork.artwork\"\n\t\t\t\t\t></span>\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tShape-adjustment affordances (amber diamonds), ONE per `a:avLst`\n\t\t\t\tguide. Existence, position and cursor all come from the SHARED\n\t\t\t\t`getShapeAdjustmentHandleDescriptors`, so they appear only for a\n\t\t\t\tgeometry with an adjustable parameter, sit where the other four\n\t\t\t\tbindings put them, and are absent for a shape locked with\n\t\t\t\t`noAdjustHandles`. Dragging one writes `shapeAdjustments`; it does\n\t\t\t\tNOT resize the element.\n\t\t\t-->\n\t\t\t@for (ah of adjustHandles(); track ah.key) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-adjust-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"adjust\"\n\t\t\t\t\t[attr.data-pptx-adjust-key]=\"ah.key\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.adjust' | translate\"\n\t\t\t\t\t[style.left.px]=\"ah.left\"\n\t\t\t\t\t[style.top.px]=\"ah.top\"\n\t\t\t\t\t[style.width.px]=\"ah.size\"\n\t\t\t\t\t[style.height.px]=\"ah.size\"\n\t\t\t\t\t[style.cursor]=\"ah.cursor\"\n\t\t\t\t\t(pointerdown)=\"onAdjustPointerDown($event, ah)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tPowerPoint's floating \"Chart Elements\"/\"Chart Styles\"/\"Chart\n\t\t\t\tFilters\" quick-action icons, shown just outside the chart's\n\t\t\t\ttop-right corner when it is the single selected element.\n\t\t\t-->\n\t\t\t@if (editable() && quickActionsChartElement(); as chartEl) {\n\t\t\t\t<pptx-chart-quick-actions-overlay\n\t\t\t\t\t[element]=\"chartEl\"\n\t\t\t\t\t[canEdit]=\"editable()\"\n\t\t\t\t\t[zoom]=\"effectiveScalePublic()\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tConnector endpoint authoring: drag an end onto a shape's connection\n\t\t\t\tpoint to bind it (`a:stCxn` / `a:endCxn`), or off one to detach.\n\t\t\t\tCandidate sites are revealed only while an end is in flight so they\n\t\t\t\tnever obscure the deck at rest.\n\t\t\t-->\n\t\t\t@for (site of connectorSiteCandidates(); track site.key) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-connection-site\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[class.is-snapped]=\"site.snapped\"\n\t\t\t\t\tdata-pptx-connection-site\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[style.left.px]=\"site.left\"\n\t\t\t\t\t[style.top.px]=\"site.top\"\n\t\t\t\t\t[style.width.px]=\"site.size\"\n\t\t\t\t\t[style.height.px]=\"site.size\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (ep of connectorEndpoints(); track ep.kind) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-connector-endpoint\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[class.is-attached]=\"ep.attached\"\n\t\t\t\t\t[attr.data-pptx-connector-endpoint]=\"ep.kind\"\n\t\t\t\t\t[attr.data-pptx-connector-attached]=\"ep.attached\"\n\t\t\t\t\t[attr.aria-label]=\"\n\t\t\t\t\t\t(ep.kind === 'start'\n\t\t\t\t\t\t\t? 'pptx.canvas.connectorEndpointStart'\n\t\t\t\t\t\t\t: 'pptx.canvas.connectorEndpointEnd'\n\t\t\t\t\t\t) | translate\n\t\t\t\t\t\"\n\t\t\t\t\t[style.left.px]=\"ep.left\"\n\t\t\t\t\t[style.top.px]=\"ep.top\"\n\t\t\t\t\t[style.width.px]=\"ep.size\"\n\t\t\t\t\t[style.height.px]=\"ep.size\"\n\t\t\t\t\t(pointerdown)=\"onConnectorEndpointPointerDown($event, ep.kind)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t\t@if (editingBox(); as eb) {\n\t\t\t\t@if (inlineListSeed(); as seed) {\n\t\t\t\t\t@for (session of [seed]; track session) {\n\t\t\t\t\t\t<pptx-inline-list-editor\n\t\t\t\t\t\t\t[element]=\"eb.element\"\n\t\t\t\t\t\t\t[seed]=\"session\"\n\t\t\t\t\t\t\t[activationSelection]=\"listActivationSelection()\"\n\t\t\t\t\t\t\t[spellCheck]=\"spellCheck()\"\n\t\t\t\t\t\t\t(textInput)=\"textInput.emit($event)\"\n\t\t\t\t\t\t\t(textCommit)=\"textCommit.emit($event)\"\n\t\t\t\t\t\t\t(textCancel)=\"textCancel.emit()\"\n\t\t\t\t\t\t\t(textFormat)=\"textFormat.emit($event)\"\n\t\t\t\t\t\t\t(listSession)=\"listSession.emit($event)\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t} @else {\n\t\t\t\t\t<textarea\n\t\t\t\t\t\t#textEditor\n\t\t\t\t\t\tdata-inline-editor\n\t\t\t\t\t\tclass=\"pptx-ng-text-editor\"\n\t\t\t\t\t\taria-label=\"Edit slide text\"\n\t\t\t\t\t\t[spellcheck]=\"spellCheck()\"\n\t\t\t\t\t\t[style.left.px]=\"eb.x\"\n\t\t\t\t\t\t[style.top.px]=\"eb.y\"\n\t\t\t\t\t\t[style.width.px]=\"eb.width\"\n\t\t\t\t\t\t[style.height.px]=\"eb.height\"\n\t\t\t\t\t\t(pointerdown)=\"$event.stopPropagation()\"\n\t\t\t\t\t\t(input)=\"onTextInput($event, eb.id)\"\n\t\t\t\t\t\t(blur)=\"commitText($event, eb.id)\"\n\t\t\t\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t\t\t\t></textarea>\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tView overlays: editor aids only, never on thumbnails/preview/presentation.\n\t\t\t\tAll are pointer-events:none so they never break selection/drag.\n\t\t\t\tNone carry data-pptx-element / aria-roledescription / data-pptx-viewport.\n\t\t\t-->\n\t\t\t@if (interactive() && showGrid()) {\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-overlay-grid\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t>\n\t\t\t\t\t<defs>\n\t\t\t\t\t\t<pattern\n\t\t\t\t\t\t\t[attr.id]=\"gridPatternId\"\n\t\t\t\t\t\t\t[attr.width]=\"gridSpacingPx()\"\n\t\t\t\t\t\t\t[attr.height]=\"gridSpacingPx()\"\n\t\t\t\t\t\t\tpatternUnits=\"userSpaceOnUse\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t[attr.cx]=\"gridSpacingPx() / 2\"\n\t\t\t\t\t\t\t\t[attr.cy]=\"gridSpacingPx() / 2\"\n\t\t\t\t\t\t\t\tr=\"0.6\"\n\t\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.55)\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</pattern>\n\t\t\t\t\t</defs>\n\t\t\t\t\t<rect\n\t\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t\t\t[attr.fill]=\"'url(#' + gridPatternId + ')'\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t@if (interactive() && showGuides()) {\n\t\t\t\t<!--\n\t\t\t\t\tCenter crosshair: one horizontal line and one vertical line\n\t\t\t\t\tthrough the midpoint of the slide. Static; draggable guides\n\t\t\t\t\tare a follow-up.\n\t\t\t\t-->\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-overlay-guides\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t>\n\t\t\t\t\t<!-- Horizontal center guide -->\n\t\t\t\t\t<line\n\t\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t\t[attr.y1]=\"canvasSize().height / 2\"\n\t\t\t\t\t\t[attr.x2]=\"canvasSize().width\"\n\t\t\t\t\t\t[attr.y2]=\"canvasSize().height / 2\"\n\t\t\t\t\t\tstroke=\"rgba(99,102,241,0.7)\"\n\t\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t\t\tstroke-dasharray=\"6 3\"\n\t\t\t\t\t/>\n\t\t\t\t\t<!-- Vertical center guide -->\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"canvasSize().width / 2\"\n\t\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t\t[attr.x2]=\"canvasSize().width / 2\"\n\t\t\t\t\t\t[attr.y2]=\"canvasSize().height\"\n\t\t\t\t\t\tstroke=\"rgba(99,102,241,0.7)\"\n\t\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t\t\tstroke-dasharray=\"6 3\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tUser-created ruler guides.\n\t\t\t\tEach guide has a non-interactive line body and an interactive\n\t\t\t\tdrag handle. Double-click the handle to delete the guide.\n\n\t\t\t\tDeliberately OUTSIDE the showGuides() gate above (which owns the\n\t\t\t\tstatic centre crosshair): a guide the user just dragged off a ruler\n\t\t\t\tstrip must be visible immediately, as it is in Vue and Vanilla.\n\t\t\t\tWhile it lived under that gate, the drag-out gesture silently\n\t\t\t\tproduced an invisible guide unless View > Guides happened to be on.\n\t\t\t-->\n\t\t\t@if (interactive()) {\n\t\t\t\t@for (g of rulerGuidesSvc.rulerGuides(); track g.id) {\n\t\t\t\t\t<!-- Guide line body: pointer-events:none -->\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"pptx-ng-ruler-guide-line\"\n\t\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos : 0\"\n\t\t\t\t\t\t[style.top.px]=\"g.axis === 'y' ? g.pos : 0\"\n\t\t\t\t\t\t[style.width]=\"g.axis === 'x' ? '1px' : '100%'\"\n\t\t\t\t\t\t[style.height]=\"g.axis === 'y' ? '1px' : '100%'\"\n\t\t\t\t\t></div>\n\t\t\t\t\t<!-- Drag handle: pointer-events:auto -->\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"pptx-ng-ruler-guide-handle\"\n\t\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos - 4 : 0\"\n\t\t\t\t\t\t[style.top.px]=\"g.axis === 'y' ? g.pos - 4 : 0\"\n\t\t\t\t\t\t[style.width]=\"g.axis === 'x' ? '9px' : '100%'\"\n\t\t\t\t\t\t[style.height]=\"g.axis === 'y' ? '9px' : '100%'\"\n\t\t\t\t\t\t[style.cursor]=\"g.axis === 'x' ? 'col-resize' : 'row-resize'\"\n\t\t\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onGuidePointerDown($event, g.id, g.axis)\"\n\t\t\t\t\t\t(dblclick)=\"rulerGuidesSvc.onGuideDoubleClick($event, g.id)\"\n\t\t\t\t\t\t[title]=\"'pptx.canvas.guideTooltip' | translate\"\n\t\t\t\t\t></div>\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tLive ink stroke preview: shown while the user is drawing.\n\t\t\t\tpointer-events:none so it never intercepts element gestures.\n\t\t\t\tNo data-pptx-element / aria-roledescription / data-pptx-viewport.\n\n\t\t\t\tRenders `inkDrawing.liveStrokeView()`: the same plain-path /\n\t\t\t\tpressure-circle / tilt-nib decision `InkRendererComponent` makes for\n\t\t\t\ta committed stroke, so a calligraphic lean or pressure-variable\n\t\t\t\twidth shows up while the pointer is still down, not only after\n\t\t\t\trelease.\n\t\t\t-->\n\t\t\t@if (inkDrawing.active() && inkDrawing.liveStrokeView() && drawTool() !== 'select') {\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-ink-preview\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t\tstyle=\"position: absolute; inset: 0; pointer-events: none; z-index: 70\"\n\t\t\t\t>\n\t\t\t\t\t@if (inkDrawing.liveStrokeView()?.nibMarks; as nibMarks) {\n\t\t\t\t\t\t<g [attr.opacity]=\"inkDrawing.liveStrokeView()?.opacity\">\n\t\t\t\t\t\t\t@for (m of nibMarks; track $index) {\n\t\t\t\t\t\t\t\t<ellipse\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"m.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"m.cy\"\n\t\t\t\t\t\t\t\t\t[attr.rx]=\"m.rPerp\"\n\t\t\t\t\t\t\t\t\t[attr.ry]=\"m.rTilt\"\n\t\t\t\t\t\t\t\t\t[attr.transform]=\"'rotate(' + m.rotationDeg + ' ' + m.cx + ' ' + m.cy + ')'\"\n\t\t\t\t\t\t\t\t\t[attr.fill]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t} @else if (inkDrawing.liveStrokeView()?.circles; as circles) {\n\t\t\t\t\t\t<g [attr.opacity]=\"inkDrawing.liveStrokeView()?.opacity\">\n\t\t\t\t\t\t\t@for (c of circles; track $index) {\n\t\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"c.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"c.cy\"\n\t\t\t\t\t\t\t\t\t[attr.r]=\"c.r\"\n\t\t\t\t\t\t\t\t\t[attr.fill]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t[attr.d]=\"inkDrawing.liveStrokeView()?.d\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t[attr.stroke]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"inkDrawing.liveStrokeView()?.width\"\n\t\t\t\t\t\t\t[attr.stroke-opacity]=\"inkDrawing.liveStrokeView()?.opacity\"\n\t\t\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tProjected overlays (collaboration cursors + remote selections).\n\t\t\t\tThey live INSIDE the scaled stage so the stage's CSS\n\t\t\t\ttransform:scale() applies the on-screen scale exactly once and\n\t\t\t\tthey can be authored in raw, unscaled slide coordinates, exactly\n\t\t\t\tlike the React/Vue overlays.\n\t\t\t-->\n\t\t\t<ng-content />\n\t\t</div>\n\n\t\t<!--\n\t\t\tRuler strips: siblings to the scaled stage inside the wrapper div,\n\t\t\tabsolutely positioned at top:0/left:0 within the wrapper's padding area.\n\t\t\tThe wrapper's padding (RULER_THICKNESS px top+left) reserves space for\n\t\t\tthese strips so the stage content starts below/right of them.\n\t\t\tpointer-events:none so they never intercept element gestures.\n\t\t-->\n\t\t@if (interactive() && showRulers()) {\n\t\t\t<!-- Corner square at the intersection of the two ruler strips -->\n\t\t\t<div class=\"pptx-ng-ruler-corner\" aria-hidden=\"true\"></div>\n\n\t\t\t<!--\n\t\t\t\tHorizontal ruler: spans the top padding row of the wrapper.\n\t\t\t\tA guide is dropped on pointer-UP (not down), resolved by the shared\n\t\t\t\trulerDragToGuidePosition, so a stray click on the strip cannot\n\t\t\t\tdrop a guide the user never dragged out - the rule React, Vue,\n\t\t\t\tSvelte and Vanilla all follow.\n\t\t\t-->\n\t\t\t<svg\n\t\t\t\tclass=\"pptx-ng-ruler-h\"\n\t\t\t\tdata-pptx-ruler=\"h\"\n\t\t\t\trole=\"presentation\"\n\t\t\t\t[attr.width]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t[style.cursor]=\"editable() ? 'row-resize' : null\"\n\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onRulerPointerDown('h', $event)\"\n\t\t\t\t(pointerup)=\"rulerGuidesSvc.onRulerPointerUp('h', $event)\"\n\t\t\t>\n\t\t\t\t<rect\n\t\t\t\t\t[attr.width]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t\tfill=\"#1e293b\"\n\t\t\t\t/>\n\t\t\t\t@if (hRulerHighlight(); as highlight) {\n\t\t\t\t\t<rect\n\t\t\t\t\t\t[attr.x]=\"highlight.start\"\n\t\t\t\t\t\ty=\"0\"\n\t\t\t\t\t\t[attr.width]=\"highlight.span\"\n\t\t\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t\t\tfill=\"rgba(96,165,250,0.25)\"\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\t<line\n\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t[attr.y1]=\"rulerThickness - 0.5\"\n\t\t\t\t\t[attr.x2]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t\t[attr.y2]=\"rulerThickness - 0.5\"\n\t\t\t\t\tstroke=\"rgba(255,255,255,0.15)\"\n\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t/>\n\t\t\t\t@for (tick of hRulerTicks(); track $index) {\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"tick.position\"\n\t\t\t\t\t\t[attr.y1]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.x2]=\"tick.position\"\n\t\t\t\t\t\t[attr.y2]=\"rulerThickness - rulerThickness * (tick.isMajor ? 0.6 : 0.3)\"\n\t\t\t\t\t\tstroke=\"rgba(156,163,175,0.7)\"\n\t\t\t\t\t\t[attr.stroke-width]=\"tick.isMajor ? 1 : 0.5\"\n\t\t\t\t\t/>\n\t\t\t\t\t@if (tick.label) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t[attr.x]=\"tick.position + 2\"\n\t\t\t\t\t\t\t[attr.y]=\"rulerFontSize + 1\"\n\t\t\t\t\t\t\t[attr.font-size]=\"rulerFontSize\"\n\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.9)\"\n\t\t\t\t\t\t\tstyle=\"font-family: system-ui, sans-serif\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ tick.label }}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</svg>\n\n\t\t\t<!-- Vertical ruler: spans the left padding column of the wrapper -->\n\t\t\t<svg\n\t\t\t\tclass=\"pptx-ng-ruler-v\"\n\t\t\t\tdata-pptx-ruler=\"v\"\n\t\t\t\trole=\"presentation\"\n\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t[attr.height]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t[style.cursor]=\"editable() ? 'col-resize' : null\"\n\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onRulerPointerDown('v', $event)\"\n\t\t\t\t(pointerup)=\"rulerGuidesSvc.onRulerPointerUp('v', $event)\"\n\t\t\t>\n\t\t\t\t<rect\n\t\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t\tfill=\"#1e293b\"\n\t\t\t\t/>\n\t\t\t\t@if (vRulerHighlight(); as highlight) {\n\t\t\t\t\t<rect\n\t\t\t\t\t\tx=\"0\"\n\t\t\t\t\t\t[attr.y]=\"highlight.start\"\n\t\t\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.height]=\"highlight.span\"\n\t\t\t\t\t\tfill=\"rgba(96,165,250,0.25)\"\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\t<line\n\t\t\t\t\t[attr.x1]=\"rulerThickness - 0.5\"\n\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t[attr.x2]=\"rulerThickness - 0.5\"\n\t\t\t\t\t[attr.y2]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t\tstroke=\"rgba(255,255,255,0.15)\"\n\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t/>\n\t\t\t\t@for (tick of vRulerTicks(); track $index) {\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.y1]=\"tick.position\"\n\t\t\t\t\t\t[attr.x2]=\"rulerThickness - rulerThickness * (tick.isMajor ? 0.6 : 0.3)\"\n\t\t\t\t\t\t[attr.y2]=\"tick.position\"\n\t\t\t\t\t\tstroke=\"rgba(156,163,175,0.7)\"\n\t\t\t\t\t\t[attr.stroke-width]=\"tick.isMajor ? 1 : 0.5\"\n\t\t\t\t\t/>\n\t\t\t\t\t@if (tick.label) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\tx=\"2\"\n\t\t\t\t\t\t\t[attr.y]=\"tick.position + rulerFontSize + 2\"\n\t\t\t\t\t\t\t[attr.font-size]=\"rulerFontSize\"\n\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.9)\"\n\t\t\t\t\t\t\tstyle=\"font-family: system-ui, sans-serif\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ tick.label }}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</svg>\n\t\t}\n\t</div>\n</div>\n", styles: [".pptx-ng-canvas-stage.is-editable{touch-action:none}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ElementRendererComponent, selector: "pptx-element-renderer", inputs: ["element", "mediaDataUrls", "zIndex", "obstacles", "canvasWidth", "canvasHeight", "interactive", "marked", "exposeElementId", "presenting", "editable", "fieldContext", "slideElements", "editTemplateMode", "parentGroupFill", "editingElementId"], outputs: ["cellCommit", "tableChange"] }, { kind: "component", type: AiFocusHighlightOverlayComponent, selector: "pptx-ai-focus-highlight-overlay", inputs: ["highlights", "elements", "activeSlideIndex"] }, { kind: "component", type: AiChangeOverlayComponent, selector: "pptx-ai-change-overlay", inputs: ["batch", "activeSlideIndex"] }, { kind: "component", type: ActiveXControlsOverlayComponent, selector: "pptx-activex-controls-overlay", inputs: ["controls", "canvasSize"] }, { kind: "component", type: InlineListEditorComponent, selector: "pptx-inline-list-editor", inputs: ["element", "seed", "spellCheck", "activationSelection"], outputs: ["textInput", "textCommit", "textCancel", "textFormat", "listSession"] }, { kind: "directive", type: RotateHandlePlacementDirective, selector: "[pptxRotateHandleFor]", inputs: ["pptxRotateHandleFor", "pptxRotateHandleStage"] }, { kind: "component", type: ChartQuickActionsOverlayComponent, selector: "pptx-chart-quick-actions-overlay", inputs: ["element", "canEdit", "zoom"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
128913
130033
  }
128914
130034
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: SlideCanvasComponent, decorators: [{
128915
130035
  type: Component,
@@ -128930,7 +130050,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
128930
130050
  ActiveXControlsOverlayComponent,
128931
130051
  InlineListEditorComponent,
128932
130052
  RotateHandlePlacementDirective,
128933
- ], template: "<div\n\t#viewport\n\tclass=\"pptx-ng-canvas-viewport\"\n\t[attr.data-pptx-viewport]=\"interactive() ? '' : null\"\n\t(pointerdown)=\"onViewportPointerDown($event)\"\n>\n\t<div class=\"pptx-ng-canvas-wrapper\" [ngStyle]=\"wrapperStyle()\">\n\t\t<div\n\t\t\t#stage\n\t\t\tclass=\"pptx-ng-canvas-stage\"\n\t\t\t[class.is-editable]=\"editable()\"\n\t\t\trole=\"region\"\n\t\t\t[attr.aria-roledescription]=\"interactive() || presenting() ? 'slide' : null\"\n\t\t\t[attr.data-pptx-ai-active]=\"aiActive() ? 'true' : null\"\n\t\t\t[ngStyle]=\"stageStyle()\"\n\t\t\t(pointerdown)=\"onStagePointerDown($event)\"\n\t\t\t(contextmenu)=\"onContextMenu($event)\"\n\t\t\t(dblclick)=\"onDblClick($event)\"\n\t\t>\n\t\t\t<!--\n\t\t\t\tTemplate layer: inherited master/layout elements, rendered BEHIND\n\t\t\t\tthe slide's own elements (lower z-index). Interactive + given the\n\t\t\t\tamber editable affordance only while editTemplateMode is on; when\n\t\t\t\toff they render inertly with no affordance, exactly as core\n\t\t\t\tdelivered them. `marked` keeps the data-pptx-element contract on\n\t\t\t\tthem even while locked: the marker means \"rendered slide element\",\n\t\t\t\tnot \"editable right now\".\n\t\t\t-->\n\t\t\t@for (element of visibleTemplateElements(); track element.id; let i = $index) {\n\t\t\t\t<pptx-element-renderer\n\t\t\t\t\t[element]=\"element\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zIndex]=\"i\"\n\t\t\t\t\t[obstacles]=\"connectorObstacles()\"\n\t\t\t\t\t[canvasWidth]=\"canvasSize().width\"\n\t\t\t\t\t[canvasHeight]=\"canvasSize().height\"\n\t\t\t\t\t[interactive]=\"interactive() && editTemplateMode()\"\n\t\t\t\t\t[marked]=\"interactive()\"\n\t\t\t\t\t[exposeElementId]=\"exposeElementIds()\"\n\t\t\t\t\t[editable]=\"editable() && editTemplateMode()\"\n\t\t\t\t\t[fieldContext]=\"fieldContext()\"\n\t\t\t\t\t[slideElements]=\"allElements()\"\n\t\t\t\t\t[editTemplateMode]=\"editTemplateMode()\"\n\t\t\t\t\t[editingElementId]=\"editingId()\"\n\t\t\t\t\t(cellCommit)=\"cellCommit.emit($event)\"\n\t\t\t\t\t(tableChange)=\"tableChange.emit($event)\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@for (element of elements(); track element.id; let i = $index) {\n\t\t\t\t<pptx-element-renderer\n\t\t\t\t\t[element]=\"element\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zIndex]=\"visibleTemplateElements().length + i\"\n\t\t\t\t\t[obstacles]=\"connectorObstacles()\"\n\t\t\t\t\t[canvasWidth]=\"canvasSize().width\"\n\t\t\t\t\t[canvasHeight]=\"canvasSize().height\"\n\t\t\t\t\t[interactive]=\"interactive()\"\n\t\t\t\t\t[exposeElementId]=\"exposeElementIds()\"\n\t\t\t\t\t[presenting]=\"presenting()\"\n\t\t\t\t\t[editable]=\"editable()\"\n\t\t\t\t\t[fieldContext]=\"fieldContext()\"\n\t\t\t\t\t[slideElements]=\"allElements()\"\n\t\t\t\t\t[editTemplateMode]=\"false\"\n\t\t\t\t\t[editingElementId]=\"editingId()\"\n\t\t\t\t\t(cellCommit)=\"cellCommit.emit($event)\"\n\t\t\t\t\t(tableChange)=\"tableChange.emit($event)\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t<pptx-activex-controls-overlay\n\t\t\t\t[controls]=\"slide()?.activeXControls ?? []\"\n\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t/>\n\t\t\t@if (aiHighlights().length > 0 || aiActive()) {\n\t\t\t\t<pptx-ai-focus-highlight-overlay\n\t\t\t\t\t[highlights]=\"aiHighlights()\"\n\t\t\t\t\t[elements]=\"elements()\"\n\t\t\t\t\t[activeSlideIndex]=\"aiActiveSlideIndex()\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@if (aiChangeBatch(); as batch) {\n\t\t\t\t<pptx-ai-change-overlay [batch]=\"batch\" [activeSlideIndex]=\"aiActiveSlideIndex()\" />\n\t\t\t}\n\t\t\t@for (box of selectionBoxes(); track box.id) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-selection\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"box.x\"\n\t\t\t\t\t[style.top.px]=\"box.y\"\n\t\t\t\t\t[style.width.px]=\"box.width\"\n\t\t\t\t\t[style.height.px]=\"box.height\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (h of handleBoxes(); track h.handle) {\n\t\t\t\t@let controlArtwork = selectionControlArtwork(h, h.handle);\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"resize\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.resize' | translate: { handle: h.handle }\"\n\t\t\t\t\t[ngStyle]=\"controlArtwork.frame\"\n\t\t\t\t\t[style.cursor]=\"h.cursor\"\n\t\t\t\t\t[style.--pptx-selection-width.px]=\"singleSelected()?.width\"\n\t\t\t\t\t[style.--pptx-selection-height.px]=\"singleSelected()?.height\"\n\t\t\t\t\t(pointerdown)=\"onHandlePointerDown($event, h.handle)\"\n\t\t\t\t\t(keydown)=\"onResizeHandleKeydown($event, h.handle)\"\n\t\t\t\t>\n\t\t\t\t\t<span data-pptx-handle-hit [ngStyle]=\"resizeHitAreaStyle(h.handle)\"></span>\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"pptx-ng-control-artwork\"\n\t\t\t\t\t\tdata-pptx-handle-artwork\n\t\t\t\t\t\t[ngStyle]=\"controlArtwork.artwork\"\n\t\t\t\t\t></span>\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t@if (marqueeRect(); as mr) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-marquee\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"mr.x\"\n\t\t\t\t\t[style.top.px]=\"mr.y\"\n\t\t\t\t\t[style.width.px]=\"mr.width\"\n\t\t\t\t\t[style.height.px]=\"mr.height\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (g of snapGuides(); track $index) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-snap-guide\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos : g.start\"\n\t\t\t\t\t[style.top.px]=\"g.axis === 'x' ? g.start : g.pos\"\n\t\t\t\t\t[style.width.px]=\"g.axis === 'x' ? 0 : g.end - g.start\"\n\t\t\t\t\t[style.height.px]=\"g.axis === 'x' ? g.end - g.start : 0\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@if (rotateHandle(); as rh) {\n\t\t\t\t@let rotateArtwork = selectionControlArtwork(rh);\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-rotate-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.z-index]=\"editingBox() ? 10003 : null\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"rotate\"\n\t\t\t\t\t[pptxRotateHandleFor]=\"singleSelected()?.id\"\n\t\t\t\t\t[pptxRotateHandleStage]=\"stage\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.rotate' | translate\"\n\t\t\t\t\t[ngStyle]=\"rotateArtwork.frame\"\n\t\t\t\t\t(pointerdown)=\"onRotatePointerDown($event)\"\n\t\t\t\t\t(keydown)=\"onRotateHandleKeydown($event)\"\n\t\t\t\t>\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"pptx-ng-control-artwork\"\n\t\t\t\t\t\tdata-pptx-handle-artwork\n\t\t\t\t\t\t[ngStyle]=\"rotateArtwork.artwork\"\n\t\t\t\t\t></span>\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tShape-adjustment affordances (amber diamonds), ONE per `a:avLst`\n\t\t\t\tguide. Existence, position and cursor all come from the SHARED\n\t\t\t\t`getShapeAdjustmentHandleDescriptors`, so they appear only for a\n\t\t\t\tgeometry with an adjustable parameter, sit where the other four\n\t\t\t\tbindings put them, and are absent for a shape locked with\n\t\t\t\t`noAdjustHandles`. Dragging one writes `shapeAdjustments`; it does\n\t\t\t\tNOT resize the element.\n\t\t\t-->\n\t\t\t@for (ah of adjustHandles(); track ah.key) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-adjust-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"adjust\"\n\t\t\t\t\t[attr.data-pptx-adjust-key]=\"ah.key\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.adjust' | translate\"\n\t\t\t\t\t[style.left.px]=\"ah.left\"\n\t\t\t\t\t[style.top.px]=\"ah.top\"\n\t\t\t\t\t[style.width.px]=\"ah.size\"\n\t\t\t\t\t[style.height.px]=\"ah.size\"\n\t\t\t\t\t[style.cursor]=\"ah.cursor\"\n\t\t\t\t\t(pointerdown)=\"onAdjustPointerDown($event, ah)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tConnector endpoint authoring: drag an end onto a shape's connection\n\t\t\t\tpoint to bind it (`a:stCxn` / `a:endCxn`), or off one to detach.\n\t\t\t\tCandidate sites are revealed only while an end is in flight so they\n\t\t\t\tnever obscure the deck at rest.\n\t\t\t-->\n\t\t\t@for (site of connectorSiteCandidates(); track site.key) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-connection-site\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[class.is-snapped]=\"site.snapped\"\n\t\t\t\t\tdata-pptx-connection-site\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[style.left.px]=\"site.left\"\n\t\t\t\t\t[style.top.px]=\"site.top\"\n\t\t\t\t\t[style.width.px]=\"site.size\"\n\t\t\t\t\t[style.height.px]=\"site.size\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (ep of connectorEndpoints(); track ep.kind) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-connector-endpoint\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[class.is-attached]=\"ep.attached\"\n\t\t\t\t\t[attr.data-pptx-connector-endpoint]=\"ep.kind\"\n\t\t\t\t\t[attr.data-pptx-connector-attached]=\"ep.attached\"\n\t\t\t\t\t[attr.aria-label]=\"\n\t\t\t\t\t\t(ep.kind === 'start'\n\t\t\t\t\t\t\t? 'pptx.canvas.connectorEndpointStart'\n\t\t\t\t\t\t\t: 'pptx.canvas.connectorEndpointEnd'\n\t\t\t\t\t\t) | translate\n\t\t\t\t\t\"\n\t\t\t\t\t[style.left.px]=\"ep.left\"\n\t\t\t\t\t[style.top.px]=\"ep.top\"\n\t\t\t\t\t[style.width.px]=\"ep.size\"\n\t\t\t\t\t[style.height.px]=\"ep.size\"\n\t\t\t\t\t(pointerdown)=\"onConnectorEndpointPointerDown($event, ep.kind)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t\t@if (editingBox(); as eb) {\n\t\t\t\t@if (inlineListSeed(); as seed) {\n\t\t\t\t\t@for (session of [seed]; track session) {\n\t\t\t\t\t\t<pptx-inline-list-editor\n\t\t\t\t\t\t\t[element]=\"eb.element\"\n\t\t\t\t\t\t\t[seed]=\"session\"\n\t\t\t\t\t\t\t[activationSelection]=\"listActivationSelection()\"\n\t\t\t\t\t\t\t[spellCheck]=\"spellCheck()\"\n\t\t\t\t\t\t\t(textInput)=\"textInput.emit($event)\"\n\t\t\t\t\t\t\t(textCommit)=\"textCommit.emit($event)\"\n\t\t\t\t\t\t\t(textCancel)=\"textCancel.emit()\"\n\t\t\t\t\t\t\t(textFormat)=\"textFormat.emit($event)\"\n\t\t\t\t\t\t\t(listSession)=\"listSession.emit($event)\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t} @else {\n\t\t\t\t\t<textarea\n\t\t\t\t\t\t#textEditor\n\t\t\t\t\t\tdata-inline-editor\n\t\t\t\t\t\tclass=\"pptx-ng-text-editor\"\n\t\t\t\t\t\taria-label=\"Edit slide text\"\n\t\t\t\t\t\t[spellcheck]=\"spellCheck()\"\n\t\t\t\t\t\t[style.left.px]=\"eb.x\"\n\t\t\t\t\t\t[style.top.px]=\"eb.y\"\n\t\t\t\t\t\t[style.width.px]=\"eb.width\"\n\t\t\t\t\t\t[style.height.px]=\"eb.height\"\n\t\t\t\t\t\t(pointerdown)=\"$event.stopPropagation()\"\n\t\t\t\t\t\t(input)=\"onTextInput($event, eb.id)\"\n\t\t\t\t\t\t(blur)=\"commitText($event, eb.id)\"\n\t\t\t\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t\t\t\t></textarea>\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tView overlays: editor aids only, never on thumbnails/preview/presentation.\n\t\t\t\tAll are pointer-events:none so they never break selection/drag.\n\t\t\t\tNone carry data-pptx-element / aria-roledescription / data-pptx-viewport.\n\t\t\t-->\n\t\t\t@if (interactive() && showGrid()) {\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-overlay-grid\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t>\n\t\t\t\t\t<defs>\n\t\t\t\t\t\t<pattern\n\t\t\t\t\t\t\t[attr.id]=\"gridPatternId\"\n\t\t\t\t\t\t\t[attr.width]=\"gridSpacingPx()\"\n\t\t\t\t\t\t\t[attr.height]=\"gridSpacingPx()\"\n\t\t\t\t\t\t\tpatternUnits=\"userSpaceOnUse\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t[attr.cx]=\"gridSpacingPx() / 2\"\n\t\t\t\t\t\t\t\t[attr.cy]=\"gridSpacingPx() / 2\"\n\t\t\t\t\t\t\t\tr=\"0.6\"\n\t\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.55)\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</pattern>\n\t\t\t\t\t</defs>\n\t\t\t\t\t<rect\n\t\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t\t\t[attr.fill]=\"'url(#' + gridPatternId + ')'\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t@if (interactive() && showGuides()) {\n\t\t\t\t<!--\n\t\t\t\t\tCenter crosshair: one horizontal line and one vertical line\n\t\t\t\t\tthrough the midpoint of the slide. Static; draggable guides\n\t\t\t\t\tare a follow-up.\n\t\t\t\t-->\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-overlay-guides\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t>\n\t\t\t\t\t<!-- Horizontal center guide -->\n\t\t\t\t\t<line\n\t\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t\t[attr.y1]=\"canvasSize().height / 2\"\n\t\t\t\t\t\t[attr.x2]=\"canvasSize().width\"\n\t\t\t\t\t\t[attr.y2]=\"canvasSize().height / 2\"\n\t\t\t\t\t\tstroke=\"rgba(99,102,241,0.7)\"\n\t\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t\t\tstroke-dasharray=\"6 3\"\n\t\t\t\t\t/>\n\t\t\t\t\t<!-- Vertical center guide -->\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"canvasSize().width / 2\"\n\t\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t\t[attr.x2]=\"canvasSize().width / 2\"\n\t\t\t\t\t\t[attr.y2]=\"canvasSize().height\"\n\t\t\t\t\t\tstroke=\"rgba(99,102,241,0.7)\"\n\t\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t\t\tstroke-dasharray=\"6 3\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tUser-created ruler guides.\n\t\t\t\tEach guide has a non-interactive line body and an interactive\n\t\t\t\tdrag handle. Double-click the handle to delete the guide.\n\n\t\t\t\tDeliberately OUTSIDE the showGuides() gate above (which owns the\n\t\t\t\tstatic centre crosshair): a guide the user just dragged off a ruler\n\t\t\t\tstrip must be visible immediately, as it is in Vue and Vanilla.\n\t\t\t\tWhile it lived under that gate, the drag-out gesture silently\n\t\t\t\tproduced an invisible guide unless View > Guides happened to be on.\n\t\t\t-->\n\t\t\t@if (interactive()) {\n\t\t\t\t@for (g of rulerGuidesSvc.rulerGuides(); track g.id) {\n\t\t\t\t\t<!-- Guide line body: pointer-events:none -->\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"pptx-ng-ruler-guide-line\"\n\t\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos : 0\"\n\t\t\t\t\t\t[style.top.px]=\"g.axis === 'y' ? g.pos : 0\"\n\t\t\t\t\t\t[style.width]=\"g.axis === 'x' ? '1px' : '100%'\"\n\t\t\t\t\t\t[style.height]=\"g.axis === 'y' ? '1px' : '100%'\"\n\t\t\t\t\t></div>\n\t\t\t\t\t<!-- Drag handle: pointer-events:auto -->\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"pptx-ng-ruler-guide-handle\"\n\t\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos - 4 : 0\"\n\t\t\t\t\t\t[style.top.px]=\"g.axis === 'y' ? g.pos - 4 : 0\"\n\t\t\t\t\t\t[style.width]=\"g.axis === 'x' ? '9px' : '100%'\"\n\t\t\t\t\t\t[style.height]=\"g.axis === 'y' ? '9px' : '100%'\"\n\t\t\t\t\t\t[style.cursor]=\"g.axis === 'x' ? 'col-resize' : 'row-resize'\"\n\t\t\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onGuidePointerDown($event, g.id, g.axis)\"\n\t\t\t\t\t\t(dblclick)=\"rulerGuidesSvc.onGuideDoubleClick($event, g.id)\"\n\t\t\t\t\t\t[title]=\"'pptx.canvas.guideTooltip' | translate\"\n\t\t\t\t\t></div>\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tLive ink stroke preview: shown while the user is drawing.\n\t\t\t\tpointer-events:none so it never intercepts element gestures.\n\t\t\t\tNo data-pptx-element / aria-roledescription / data-pptx-viewport.\n\n\t\t\t\tRenders `inkDrawing.liveStrokeView()`: the same plain-path /\n\t\t\t\tpressure-circle / tilt-nib decision `InkRendererComponent` makes for\n\t\t\t\ta committed stroke, so a calligraphic lean or pressure-variable\n\t\t\t\twidth shows up while the pointer is still down, not only after\n\t\t\t\trelease.\n\t\t\t-->\n\t\t\t@if (inkDrawing.active() && inkDrawing.liveStrokeView() && drawTool() !== 'select') {\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-ink-preview\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t\tstyle=\"position: absolute; inset: 0; pointer-events: none; z-index: 70\"\n\t\t\t\t>\n\t\t\t\t\t@if (inkDrawing.liveStrokeView()?.nibMarks; as nibMarks) {\n\t\t\t\t\t\t<g [attr.opacity]=\"inkDrawing.liveStrokeView()?.opacity\">\n\t\t\t\t\t\t\t@for (m of nibMarks; track $index) {\n\t\t\t\t\t\t\t\t<ellipse\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"m.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"m.cy\"\n\t\t\t\t\t\t\t\t\t[attr.rx]=\"m.rPerp\"\n\t\t\t\t\t\t\t\t\t[attr.ry]=\"m.rTilt\"\n\t\t\t\t\t\t\t\t\t[attr.transform]=\"'rotate(' + m.rotationDeg + ' ' + m.cx + ' ' + m.cy + ')'\"\n\t\t\t\t\t\t\t\t\t[attr.fill]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t} @else if (inkDrawing.liveStrokeView()?.circles; as circles) {\n\t\t\t\t\t\t<g [attr.opacity]=\"inkDrawing.liveStrokeView()?.opacity\">\n\t\t\t\t\t\t\t@for (c of circles; track $index) {\n\t\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"c.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"c.cy\"\n\t\t\t\t\t\t\t\t\t[attr.r]=\"c.r\"\n\t\t\t\t\t\t\t\t\t[attr.fill]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t[attr.d]=\"inkDrawing.liveStrokeView()?.d\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t[attr.stroke]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"inkDrawing.liveStrokeView()?.width\"\n\t\t\t\t\t\t\t[attr.stroke-opacity]=\"inkDrawing.liveStrokeView()?.opacity\"\n\t\t\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tProjected overlays (collaboration cursors + remote selections).\n\t\t\t\tThey live INSIDE the scaled stage so the stage's CSS\n\t\t\t\ttransform:scale() applies the on-screen scale exactly once and\n\t\t\t\tthey can be authored in raw, unscaled slide coordinates, exactly\n\t\t\t\tlike the React/Vue overlays.\n\t\t\t-->\n\t\t\t<ng-content />\n\t\t</div>\n\n\t\t<!--\n\t\t\tRuler strips: siblings to the scaled stage inside the wrapper div,\n\t\t\tabsolutely positioned at top:0/left:0 within the wrapper's padding area.\n\t\t\tThe wrapper's padding (RULER_THICKNESS px top+left) reserves space for\n\t\t\tthese strips so the stage content starts below/right of them.\n\t\t\tpointer-events:none so they never intercept element gestures.\n\t\t-->\n\t\t@if (interactive() && showRulers()) {\n\t\t\t<!-- Corner square at the intersection of the two ruler strips -->\n\t\t\t<div class=\"pptx-ng-ruler-corner\" aria-hidden=\"true\"></div>\n\n\t\t\t<!--\n\t\t\t\tHorizontal ruler: spans the top padding row of the wrapper.\n\t\t\t\tA guide is dropped on pointer-UP (not down), resolved by the shared\n\t\t\t\trulerDragToGuidePosition, so a stray click on the strip cannot\n\t\t\t\tdrop a guide the user never dragged out - the rule React, Vue,\n\t\t\t\tSvelte and Vanilla all follow.\n\t\t\t-->\n\t\t\t<svg\n\t\t\t\tclass=\"pptx-ng-ruler-h\"\n\t\t\t\tdata-pptx-ruler=\"h\"\n\t\t\t\trole=\"presentation\"\n\t\t\t\t[attr.width]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t[style.cursor]=\"editable() ? 'row-resize' : null\"\n\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onRulerPointerDown('h', $event)\"\n\t\t\t\t(pointerup)=\"rulerGuidesSvc.onRulerPointerUp('h', $event)\"\n\t\t\t>\n\t\t\t\t<rect\n\t\t\t\t\t[attr.width]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t\tfill=\"#1e293b\"\n\t\t\t\t/>\n\t\t\t\t@if (hRulerHighlight(); as highlight) {\n\t\t\t\t\t<rect\n\t\t\t\t\t\t[attr.x]=\"highlight.start\"\n\t\t\t\t\t\ty=\"0\"\n\t\t\t\t\t\t[attr.width]=\"highlight.span\"\n\t\t\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t\t\tfill=\"rgba(96,165,250,0.25)\"\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\t<line\n\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t[attr.y1]=\"rulerThickness - 0.5\"\n\t\t\t\t\t[attr.x2]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t\t[attr.y2]=\"rulerThickness - 0.5\"\n\t\t\t\t\tstroke=\"rgba(255,255,255,0.15)\"\n\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t/>\n\t\t\t\t@for (tick of hRulerTicks(); track $index) {\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"tick.position\"\n\t\t\t\t\t\t[attr.y1]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.x2]=\"tick.position\"\n\t\t\t\t\t\t[attr.y2]=\"rulerThickness - rulerThickness * (tick.isMajor ? 0.6 : 0.3)\"\n\t\t\t\t\t\tstroke=\"rgba(156,163,175,0.7)\"\n\t\t\t\t\t\t[attr.stroke-width]=\"tick.isMajor ? 1 : 0.5\"\n\t\t\t\t\t/>\n\t\t\t\t\t@if (tick.label) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t[attr.x]=\"tick.position + 2\"\n\t\t\t\t\t\t\t[attr.y]=\"rulerFontSize + 1\"\n\t\t\t\t\t\t\t[attr.font-size]=\"rulerFontSize\"\n\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.9)\"\n\t\t\t\t\t\t\tstyle=\"font-family: system-ui, sans-serif\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ tick.label }}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</svg>\n\n\t\t\t<!-- Vertical ruler: spans the left padding column of the wrapper -->\n\t\t\t<svg\n\t\t\t\tclass=\"pptx-ng-ruler-v\"\n\t\t\t\tdata-pptx-ruler=\"v\"\n\t\t\t\trole=\"presentation\"\n\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t[attr.height]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t[style.cursor]=\"editable() ? 'col-resize' : null\"\n\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onRulerPointerDown('v', $event)\"\n\t\t\t\t(pointerup)=\"rulerGuidesSvc.onRulerPointerUp('v', $event)\"\n\t\t\t>\n\t\t\t\t<rect\n\t\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t\tfill=\"#1e293b\"\n\t\t\t\t/>\n\t\t\t\t@if (vRulerHighlight(); as highlight) {\n\t\t\t\t\t<rect\n\t\t\t\t\t\tx=\"0\"\n\t\t\t\t\t\t[attr.y]=\"highlight.start\"\n\t\t\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.height]=\"highlight.span\"\n\t\t\t\t\t\tfill=\"rgba(96,165,250,0.25)\"\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\t<line\n\t\t\t\t\t[attr.x1]=\"rulerThickness - 0.5\"\n\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t[attr.x2]=\"rulerThickness - 0.5\"\n\t\t\t\t\t[attr.y2]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t\tstroke=\"rgba(255,255,255,0.15)\"\n\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t/>\n\t\t\t\t@for (tick of vRulerTicks(); track $index) {\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.y1]=\"tick.position\"\n\t\t\t\t\t\t[attr.x2]=\"rulerThickness - rulerThickness * (tick.isMajor ? 0.6 : 0.3)\"\n\t\t\t\t\t\t[attr.y2]=\"tick.position\"\n\t\t\t\t\t\tstroke=\"rgba(156,163,175,0.7)\"\n\t\t\t\t\t\t[attr.stroke-width]=\"tick.isMajor ? 1 : 0.5\"\n\t\t\t\t\t/>\n\t\t\t\t\t@if (tick.label) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\tx=\"2\"\n\t\t\t\t\t\t\t[attr.y]=\"tick.position + rulerFontSize + 2\"\n\t\t\t\t\t\t\t[attr.font-size]=\"rulerFontSize\"\n\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.9)\"\n\t\t\t\t\t\t\tstyle=\"font-family: system-ui, sans-serif\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ tick.label }}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</svg>\n\t\t}\n\t</div>\n</div>\n", styles: [".pptx-ng-canvas-stage.is-editable{touch-action:none}\n"] }]
130053
+ ChartQuickActionsOverlayComponent,
130054
+ ], template: "<div\n\t#viewport\n\tclass=\"pptx-ng-canvas-viewport\"\n\t[attr.data-pptx-viewport]=\"interactive() ? '' : null\"\n\t(pointerdown)=\"onViewportPointerDown($event)\"\n>\n\t<div class=\"pptx-ng-canvas-wrapper\" [ngStyle]=\"wrapperStyle()\">\n\t\t<div\n\t\t\t#stage\n\t\t\tclass=\"pptx-ng-canvas-stage\"\n\t\t\t[class.is-editable]=\"editable()\"\n\t\t\trole=\"region\"\n\t\t\t[attr.aria-roledescription]=\"interactive() || presenting() ? 'slide' : null\"\n\t\t\t[attr.data-pptx-ai-active]=\"aiActive() ? 'true' : null\"\n\t\t\t[ngStyle]=\"stageStyle()\"\n\t\t\t(pointerdown)=\"onStagePointerDown($event)\"\n\t\t\t(contextmenu)=\"onContextMenu($event)\"\n\t\t\t(dblclick)=\"onDblClick($event)\"\n\t\t>\n\t\t\t<!--\n\t\t\t\tTemplate layer: inherited master/layout elements, rendered BEHIND\n\t\t\t\tthe slide's own elements (lower z-index). Interactive + given the\n\t\t\t\tamber editable affordance only while editTemplateMode is on; when\n\t\t\t\toff they render inertly with no affordance, exactly as core\n\t\t\t\tdelivered them. `marked` keeps the data-pptx-element contract on\n\t\t\t\tthem even while locked: the marker means \"rendered slide element\",\n\t\t\t\tnot \"editable right now\".\n\t\t\t-->\n\t\t\t@for (element of visibleTemplateElements(); track element.id; let i = $index) {\n\t\t\t\t<pptx-element-renderer\n\t\t\t\t\t[element]=\"element\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zIndex]=\"i\"\n\t\t\t\t\t[obstacles]=\"connectorObstacles()\"\n\t\t\t\t\t[canvasWidth]=\"canvasSize().width\"\n\t\t\t\t\t[canvasHeight]=\"canvasSize().height\"\n\t\t\t\t\t[interactive]=\"interactive() && editTemplateMode()\"\n\t\t\t\t\t[marked]=\"interactive()\"\n\t\t\t\t\t[exposeElementId]=\"exposeElementIds()\"\n\t\t\t\t\t[editable]=\"editable() && editTemplateMode()\"\n\t\t\t\t\t[fieldContext]=\"fieldContext()\"\n\t\t\t\t\t[slideElements]=\"allElements()\"\n\t\t\t\t\t[editTemplateMode]=\"editTemplateMode()\"\n\t\t\t\t\t[editingElementId]=\"editingId()\"\n\t\t\t\t\t(cellCommit)=\"cellCommit.emit($event)\"\n\t\t\t\t\t(tableChange)=\"tableChange.emit($event)\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@for (element of elements(); track element.id; let i = $index) {\n\t\t\t\t<pptx-element-renderer\n\t\t\t\t\t[element]=\"element\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zIndex]=\"visibleTemplateElements().length + i\"\n\t\t\t\t\t[obstacles]=\"connectorObstacles()\"\n\t\t\t\t\t[canvasWidth]=\"canvasSize().width\"\n\t\t\t\t\t[canvasHeight]=\"canvasSize().height\"\n\t\t\t\t\t[interactive]=\"interactive()\"\n\t\t\t\t\t[exposeElementId]=\"exposeElementIds()\"\n\t\t\t\t\t[presenting]=\"presenting()\"\n\t\t\t\t\t[editable]=\"editable()\"\n\t\t\t\t\t[fieldContext]=\"fieldContext()\"\n\t\t\t\t\t[slideElements]=\"allElements()\"\n\t\t\t\t\t[editTemplateMode]=\"false\"\n\t\t\t\t\t[editingElementId]=\"editingId()\"\n\t\t\t\t\t(cellCommit)=\"cellCommit.emit($event)\"\n\t\t\t\t\t(tableChange)=\"tableChange.emit($event)\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t<pptx-activex-controls-overlay\n\t\t\t\t[controls]=\"slide()?.activeXControls ?? []\"\n\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t/>\n\t\t\t@if (aiHighlights().length > 0 || aiActive()) {\n\t\t\t\t<pptx-ai-focus-highlight-overlay\n\t\t\t\t\t[highlights]=\"aiHighlights()\"\n\t\t\t\t\t[elements]=\"elements()\"\n\t\t\t\t\t[activeSlideIndex]=\"aiActiveSlideIndex()\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@if (aiChangeBatch(); as batch) {\n\t\t\t\t<pptx-ai-change-overlay [batch]=\"batch\" [activeSlideIndex]=\"aiActiveSlideIndex()\" />\n\t\t\t}\n\t\t\t@for (box of selectionBoxes(); track box.id) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-selection\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"box.x\"\n\t\t\t\t\t[style.top.px]=\"box.y\"\n\t\t\t\t\t[style.width.px]=\"box.width\"\n\t\t\t\t\t[style.height.px]=\"box.height\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (h of handleBoxes(); track h.handle) {\n\t\t\t\t@let controlArtwork = selectionControlArtwork(h, h.handle);\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"resize\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.resize' | translate: { handle: h.handle }\"\n\t\t\t\t\t[ngStyle]=\"controlArtwork.frame\"\n\t\t\t\t\t[style.cursor]=\"h.cursor\"\n\t\t\t\t\t[style.--pptx-selection-width.px]=\"singleSelected()?.width\"\n\t\t\t\t\t[style.--pptx-selection-height.px]=\"singleSelected()?.height\"\n\t\t\t\t\t(pointerdown)=\"onHandlePointerDown($event, h.handle)\"\n\t\t\t\t\t(keydown)=\"onResizeHandleKeydown($event, h.handle)\"\n\t\t\t\t>\n\t\t\t\t\t<span data-pptx-handle-hit [ngStyle]=\"resizeHitAreaStyle(h.handle)\"></span>\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"pptx-ng-control-artwork\"\n\t\t\t\t\t\tdata-pptx-handle-artwork\n\t\t\t\t\t\t[ngStyle]=\"controlArtwork.artwork\"\n\t\t\t\t\t></span>\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t@if (marqueeRect(); as mr) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-marquee\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"mr.x\"\n\t\t\t\t\t[style.top.px]=\"mr.y\"\n\t\t\t\t\t[style.width.px]=\"mr.width\"\n\t\t\t\t\t[style.height.px]=\"mr.height\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (g of snapGuides(); track $index) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-snap-guide\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos : g.start\"\n\t\t\t\t\t[style.top.px]=\"g.axis === 'x' ? g.start : g.pos\"\n\t\t\t\t\t[style.width.px]=\"g.axis === 'x' ? 0 : g.end - g.start\"\n\t\t\t\t\t[style.height.px]=\"g.axis === 'x' ? g.end - g.start : 0\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@if (rotateHandle(); as rh) {\n\t\t\t\t@let rotateArtwork = selectionControlArtwork(rh);\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-rotate-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.z-index]=\"editingBox() ? 10003 : null\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"rotate\"\n\t\t\t\t\t[pptxRotateHandleFor]=\"singleSelected()?.id\"\n\t\t\t\t\t[pptxRotateHandleStage]=\"stage\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.rotate' | translate\"\n\t\t\t\t\t[ngStyle]=\"rotateArtwork.frame\"\n\t\t\t\t\t(pointerdown)=\"onRotatePointerDown($event)\"\n\t\t\t\t\t(keydown)=\"onRotateHandleKeydown($event)\"\n\t\t\t\t>\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"pptx-ng-control-artwork\"\n\t\t\t\t\t\tdata-pptx-handle-artwork\n\t\t\t\t\t\t[ngStyle]=\"rotateArtwork.artwork\"\n\t\t\t\t\t></span>\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tShape-adjustment affordances (amber diamonds), ONE per `a:avLst`\n\t\t\t\tguide. Existence, position and cursor all come from the SHARED\n\t\t\t\t`getShapeAdjustmentHandleDescriptors`, so they appear only for a\n\t\t\t\tgeometry with an adjustable parameter, sit where the other four\n\t\t\t\tbindings put them, and are absent for a shape locked with\n\t\t\t\t`noAdjustHandles`. Dragging one writes `shapeAdjustments`; it does\n\t\t\t\tNOT resize the element.\n\t\t\t-->\n\t\t\t@for (ah of adjustHandles(); track ah.key) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-adjust-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"adjust\"\n\t\t\t\t\t[attr.data-pptx-adjust-key]=\"ah.key\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.adjust' | translate\"\n\t\t\t\t\t[style.left.px]=\"ah.left\"\n\t\t\t\t\t[style.top.px]=\"ah.top\"\n\t\t\t\t\t[style.width.px]=\"ah.size\"\n\t\t\t\t\t[style.height.px]=\"ah.size\"\n\t\t\t\t\t[style.cursor]=\"ah.cursor\"\n\t\t\t\t\t(pointerdown)=\"onAdjustPointerDown($event, ah)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tPowerPoint's floating \"Chart Elements\"/\"Chart Styles\"/\"Chart\n\t\t\t\tFilters\" quick-action icons, shown just outside the chart's\n\t\t\t\ttop-right corner when it is the single selected element.\n\t\t\t-->\n\t\t\t@if (editable() && quickActionsChartElement(); as chartEl) {\n\t\t\t\t<pptx-chart-quick-actions-overlay\n\t\t\t\t\t[element]=\"chartEl\"\n\t\t\t\t\t[canEdit]=\"editable()\"\n\t\t\t\t\t[zoom]=\"effectiveScalePublic()\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tConnector endpoint authoring: drag an end onto a shape's connection\n\t\t\t\tpoint to bind it (`a:stCxn` / `a:endCxn`), or off one to detach.\n\t\t\t\tCandidate sites are revealed only while an end is in flight so they\n\t\t\t\tnever obscure the deck at rest.\n\t\t\t-->\n\t\t\t@for (site of connectorSiteCandidates(); track site.key) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-connection-site\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[class.is-snapped]=\"site.snapped\"\n\t\t\t\t\tdata-pptx-connection-site\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[style.left.px]=\"site.left\"\n\t\t\t\t\t[style.top.px]=\"site.top\"\n\t\t\t\t\t[style.width.px]=\"site.size\"\n\t\t\t\t\t[style.height.px]=\"site.size\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (ep of connectorEndpoints(); track ep.kind) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-connector-endpoint\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[class.is-attached]=\"ep.attached\"\n\t\t\t\t\t[attr.data-pptx-connector-endpoint]=\"ep.kind\"\n\t\t\t\t\t[attr.data-pptx-connector-attached]=\"ep.attached\"\n\t\t\t\t\t[attr.aria-label]=\"\n\t\t\t\t\t\t(ep.kind === 'start'\n\t\t\t\t\t\t\t? 'pptx.canvas.connectorEndpointStart'\n\t\t\t\t\t\t\t: 'pptx.canvas.connectorEndpointEnd'\n\t\t\t\t\t\t) | translate\n\t\t\t\t\t\"\n\t\t\t\t\t[style.left.px]=\"ep.left\"\n\t\t\t\t\t[style.top.px]=\"ep.top\"\n\t\t\t\t\t[style.width.px]=\"ep.size\"\n\t\t\t\t\t[style.height.px]=\"ep.size\"\n\t\t\t\t\t(pointerdown)=\"onConnectorEndpointPointerDown($event, ep.kind)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t\t@if (editingBox(); as eb) {\n\t\t\t\t@if (inlineListSeed(); as seed) {\n\t\t\t\t\t@for (session of [seed]; track session) {\n\t\t\t\t\t\t<pptx-inline-list-editor\n\t\t\t\t\t\t\t[element]=\"eb.element\"\n\t\t\t\t\t\t\t[seed]=\"session\"\n\t\t\t\t\t\t\t[activationSelection]=\"listActivationSelection()\"\n\t\t\t\t\t\t\t[spellCheck]=\"spellCheck()\"\n\t\t\t\t\t\t\t(textInput)=\"textInput.emit($event)\"\n\t\t\t\t\t\t\t(textCommit)=\"textCommit.emit($event)\"\n\t\t\t\t\t\t\t(textCancel)=\"textCancel.emit()\"\n\t\t\t\t\t\t\t(textFormat)=\"textFormat.emit($event)\"\n\t\t\t\t\t\t\t(listSession)=\"listSession.emit($event)\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t} @else {\n\t\t\t\t\t<textarea\n\t\t\t\t\t\t#textEditor\n\t\t\t\t\t\tdata-inline-editor\n\t\t\t\t\t\tclass=\"pptx-ng-text-editor\"\n\t\t\t\t\t\taria-label=\"Edit slide text\"\n\t\t\t\t\t\t[spellcheck]=\"spellCheck()\"\n\t\t\t\t\t\t[style.left.px]=\"eb.x\"\n\t\t\t\t\t\t[style.top.px]=\"eb.y\"\n\t\t\t\t\t\t[style.width.px]=\"eb.width\"\n\t\t\t\t\t\t[style.height.px]=\"eb.height\"\n\t\t\t\t\t\t(pointerdown)=\"$event.stopPropagation()\"\n\t\t\t\t\t\t(input)=\"onTextInput($event, eb.id)\"\n\t\t\t\t\t\t(blur)=\"commitText($event, eb.id)\"\n\t\t\t\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t\t\t\t></textarea>\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tView overlays: editor aids only, never on thumbnails/preview/presentation.\n\t\t\t\tAll are pointer-events:none so they never break selection/drag.\n\t\t\t\tNone carry data-pptx-element / aria-roledescription / data-pptx-viewport.\n\t\t\t-->\n\t\t\t@if (interactive() && showGrid()) {\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-overlay-grid\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t>\n\t\t\t\t\t<defs>\n\t\t\t\t\t\t<pattern\n\t\t\t\t\t\t\t[attr.id]=\"gridPatternId\"\n\t\t\t\t\t\t\t[attr.width]=\"gridSpacingPx()\"\n\t\t\t\t\t\t\t[attr.height]=\"gridSpacingPx()\"\n\t\t\t\t\t\t\tpatternUnits=\"userSpaceOnUse\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t[attr.cx]=\"gridSpacingPx() / 2\"\n\t\t\t\t\t\t\t\t[attr.cy]=\"gridSpacingPx() / 2\"\n\t\t\t\t\t\t\t\tr=\"0.6\"\n\t\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.55)\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</pattern>\n\t\t\t\t\t</defs>\n\t\t\t\t\t<rect\n\t\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t\t\t[attr.fill]=\"'url(#' + gridPatternId + ')'\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t@if (interactive() && showGuides()) {\n\t\t\t\t<!--\n\t\t\t\t\tCenter crosshair: one horizontal line and one vertical line\n\t\t\t\t\tthrough the midpoint of the slide. Static; draggable guides\n\t\t\t\t\tare a follow-up.\n\t\t\t\t-->\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-overlay-guides\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t>\n\t\t\t\t\t<!-- Horizontal center guide -->\n\t\t\t\t\t<line\n\t\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t\t[attr.y1]=\"canvasSize().height / 2\"\n\t\t\t\t\t\t[attr.x2]=\"canvasSize().width\"\n\t\t\t\t\t\t[attr.y2]=\"canvasSize().height / 2\"\n\t\t\t\t\t\tstroke=\"rgba(99,102,241,0.7)\"\n\t\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t\t\tstroke-dasharray=\"6 3\"\n\t\t\t\t\t/>\n\t\t\t\t\t<!-- Vertical center guide -->\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"canvasSize().width / 2\"\n\t\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t\t[attr.x2]=\"canvasSize().width / 2\"\n\t\t\t\t\t\t[attr.y2]=\"canvasSize().height\"\n\t\t\t\t\t\tstroke=\"rgba(99,102,241,0.7)\"\n\t\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t\t\tstroke-dasharray=\"6 3\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tUser-created ruler guides.\n\t\t\t\tEach guide has a non-interactive line body and an interactive\n\t\t\t\tdrag handle. Double-click the handle to delete the guide.\n\n\t\t\t\tDeliberately OUTSIDE the showGuides() gate above (which owns the\n\t\t\t\tstatic centre crosshair): a guide the user just dragged off a ruler\n\t\t\t\tstrip must be visible immediately, as it is in Vue and Vanilla.\n\t\t\t\tWhile it lived under that gate, the drag-out gesture silently\n\t\t\t\tproduced an invisible guide unless View > Guides happened to be on.\n\t\t\t-->\n\t\t\t@if (interactive()) {\n\t\t\t\t@for (g of rulerGuidesSvc.rulerGuides(); track g.id) {\n\t\t\t\t\t<!-- Guide line body: pointer-events:none -->\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"pptx-ng-ruler-guide-line\"\n\t\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos : 0\"\n\t\t\t\t\t\t[style.top.px]=\"g.axis === 'y' ? g.pos : 0\"\n\t\t\t\t\t\t[style.width]=\"g.axis === 'x' ? '1px' : '100%'\"\n\t\t\t\t\t\t[style.height]=\"g.axis === 'y' ? '1px' : '100%'\"\n\t\t\t\t\t></div>\n\t\t\t\t\t<!-- Drag handle: pointer-events:auto -->\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"pptx-ng-ruler-guide-handle\"\n\t\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos - 4 : 0\"\n\t\t\t\t\t\t[style.top.px]=\"g.axis === 'y' ? g.pos - 4 : 0\"\n\t\t\t\t\t\t[style.width]=\"g.axis === 'x' ? '9px' : '100%'\"\n\t\t\t\t\t\t[style.height]=\"g.axis === 'y' ? '9px' : '100%'\"\n\t\t\t\t\t\t[style.cursor]=\"g.axis === 'x' ? 'col-resize' : 'row-resize'\"\n\t\t\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onGuidePointerDown($event, g.id, g.axis)\"\n\t\t\t\t\t\t(dblclick)=\"rulerGuidesSvc.onGuideDoubleClick($event, g.id)\"\n\t\t\t\t\t\t[title]=\"'pptx.canvas.guideTooltip' | translate\"\n\t\t\t\t\t></div>\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tLive ink stroke preview: shown while the user is drawing.\n\t\t\t\tpointer-events:none so it never intercepts element gestures.\n\t\t\t\tNo data-pptx-element / aria-roledescription / data-pptx-viewport.\n\n\t\t\t\tRenders `inkDrawing.liveStrokeView()`: the same plain-path /\n\t\t\t\tpressure-circle / tilt-nib decision `InkRendererComponent` makes for\n\t\t\t\ta committed stroke, so a calligraphic lean or pressure-variable\n\t\t\t\twidth shows up while the pointer is still down, not only after\n\t\t\t\trelease.\n\t\t\t-->\n\t\t\t@if (inkDrawing.active() && inkDrawing.liveStrokeView() && drawTool() !== 'select') {\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-ink-preview\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t\tstyle=\"position: absolute; inset: 0; pointer-events: none; z-index: 70\"\n\t\t\t\t>\n\t\t\t\t\t@if (inkDrawing.liveStrokeView()?.nibMarks; as nibMarks) {\n\t\t\t\t\t\t<g [attr.opacity]=\"inkDrawing.liveStrokeView()?.opacity\">\n\t\t\t\t\t\t\t@for (m of nibMarks; track $index) {\n\t\t\t\t\t\t\t\t<ellipse\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"m.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"m.cy\"\n\t\t\t\t\t\t\t\t\t[attr.rx]=\"m.rPerp\"\n\t\t\t\t\t\t\t\t\t[attr.ry]=\"m.rTilt\"\n\t\t\t\t\t\t\t\t\t[attr.transform]=\"'rotate(' + m.rotationDeg + ' ' + m.cx + ' ' + m.cy + ')'\"\n\t\t\t\t\t\t\t\t\t[attr.fill]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t} @else if (inkDrawing.liveStrokeView()?.circles; as circles) {\n\t\t\t\t\t\t<g [attr.opacity]=\"inkDrawing.liveStrokeView()?.opacity\">\n\t\t\t\t\t\t\t@for (c of circles; track $index) {\n\t\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"c.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"c.cy\"\n\t\t\t\t\t\t\t\t\t[attr.r]=\"c.r\"\n\t\t\t\t\t\t\t\t\t[attr.fill]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t[attr.d]=\"inkDrawing.liveStrokeView()?.d\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t[attr.stroke]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"inkDrawing.liveStrokeView()?.width\"\n\t\t\t\t\t\t\t[attr.stroke-opacity]=\"inkDrawing.liveStrokeView()?.opacity\"\n\t\t\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tProjected overlays (collaboration cursors + remote selections).\n\t\t\t\tThey live INSIDE the scaled stage so the stage's CSS\n\t\t\t\ttransform:scale() applies the on-screen scale exactly once and\n\t\t\t\tthey can be authored in raw, unscaled slide coordinates, exactly\n\t\t\t\tlike the React/Vue overlays.\n\t\t\t-->\n\t\t\t<ng-content />\n\t\t</div>\n\n\t\t<!--\n\t\t\tRuler strips: siblings to the scaled stage inside the wrapper div,\n\t\t\tabsolutely positioned at top:0/left:0 within the wrapper's padding area.\n\t\t\tThe wrapper's padding (RULER_THICKNESS px top+left) reserves space for\n\t\t\tthese strips so the stage content starts below/right of them.\n\t\t\tpointer-events:none so they never intercept element gestures.\n\t\t-->\n\t\t@if (interactive() && showRulers()) {\n\t\t\t<!-- Corner square at the intersection of the two ruler strips -->\n\t\t\t<div class=\"pptx-ng-ruler-corner\" aria-hidden=\"true\"></div>\n\n\t\t\t<!--\n\t\t\t\tHorizontal ruler: spans the top padding row of the wrapper.\n\t\t\t\tA guide is dropped on pointer-UP (not down), resolved by the shared\n\t\t\t\trulerDragToGuidePosition, so a stray click on the strip cannot\n\t\t\t\tdrop a guide the user never dragged out - the rule React, Vue,\n\t\t\t\tSvelte and Vanilla all follow.\n\t\t\t-->\n\t\t\t<svg\n\t\t\t\tclass=\"pptx-ng-ruler-h\"\n\t\t\t\tdata-pptx-ruler=\"h\"\n\t\t\t\trole=\"presentation\"\n\t\t\t\t[attr.width]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t[style.cursor]=\"editable() ? 'row-resize' : null\"\n\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onRulerPointerDown('h', $event)\"\n\t\t\t\t(pointerup)=\"rulerGuidesSvc.onRulerPointerUp('h', $event)\"\n\t\t\t>\n\t\t\t\t<rect\n\t\t\t\t\t[attr.width]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t\tfill=\"#1e293b\"\n\t\t\t\t/>\n\t\t\t\t@if (hRulerHighlight(); as highlight) {\n\t\t\t\t\t<rect\n\t\t\t\t\t\t[attr.x]=\"highlight.start\"\n\t\t\t\t\t\ty=\"0\"\n\t\t\t\t\t\t[attr.width]=\"highlight.span\"\n\t\t\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t\t\tfill=\"rgba(96,165,250,0.25)\"\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\t<line\n\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t[attr.y1]=\"rulerThickness - 0.5\"\n\t\t\t\t\t[attr.x2]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t\t[attr.y2]=\"rulerThickness - 0.5\"\n\t\t\t\t\tstroke=\"rgba(255,255,255,0.15)\"\n\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t/>\n\t\t\t\t@for (tick of hRulerTicks(); track $index) {\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"tick.position\"\n\t\t\t\t\t\t[attr.y1]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.x2]=\"tick.position\"\n\t\t\t\t\t\t[attr.y2]=\"rulerThickness - rulerThickness * (tick.isMajor ? 0.6 : 0.3)\"\n\t\t\t\t\t\tstroke=\"rgba(156,163,175,0.7)\"\n\t\t\t\t\t\t[attr.stroke-width]=\"tick.isMajor ? 1 : 0.5\"\n\t\t\t\t\t/>\n\t\t\t\t\t@if (tick.label) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t[attr.x]=\"tick.position + 2\"\n\t\t\t\t\t\t\t[attr.y]=\"rulerFontSize + 1\"\n\t\t\t\t\t\t\t[attr.font-size]=\"rulerFontSize\"\n\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.9)\"\n\t\t\t\t\t\t\tstyle=\"font-family: system-ui, sans-serif\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ tick.label }}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</svg>\n\n\t\t\t<!-- Vertical ruler: spans the left padding column of the wrapper -->\n\t\t\t<svg\n\t\t\t\tclass=\"pptx-ng-ruler-v\"\n\t\t\t\tdata-pptx-ruler=\"v\"\n\t\t\t\trole=\"presentation\"\n\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t[attr.height]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t[style.cursor]=\"editable() ? 'col-resize' : null\"\n\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onRulerPointerDown('v', $event)\"\n\t\t\t\t(pointerup)=\"rulerGuidesSvc.onRulerPointerUp('v', $event)\"\n\t\t\t>\n\t\t\t\t<rect\n\t\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t\tfill=\"#1e293b\"\n\t\t\t\t/>\n\t\t\t\t@if (vRulerHighlight(); as highlight) {\n\t\t\t\t\t<rect\n\t\t\t\t\t\tx=\"0\"\n\t\t\t\t\t\t[attr.y]=\"highlight.start\"\n\t\t\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.height]=\"highlight.span\"\n\t\t\t\t\t\tfill=\"rgba(96,165,250,0.25)\"\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\t<line\n\t\t\t\t\t[attr.x1]=\"rulerThickness - 0.5\"\n\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t[attr.x2]=\"rulerThickness - 0.5\"\n\t\t\t\t\t[attr.y2]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t\tstroke=\"rgba(255,255,255,0.15)\"\n\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t/>\n\t\t\t\t@for (tick of vRulerTicks(); track $index) {\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.y1]=\"tick.position\"\n\t\t\t\t\t\t[attr.x2]=\"rulerThickness - rulerThickness * (tick.isMajor ? 0.6 : 0.3)\"\n\t\t\t\t\t\t[attr.y2]=\"tick.position\"\n\t\t\t\t\t\tstroke=\"rgba(156,163,175,0.7)\"\n\t\t\t\t\t\t[attr.stroke-width]=\"tick.isMajor ? 1 : 0.5\"\n\t\t\t\t\t/>\n\t\t\t\t\t@if (tick.label) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\tx=\"2\"\n\t\t\t\t\t\t\t[attr.y]=\"tick.position + rulerFontSize + 2\"\n\t\t\t\t\t\t\t[attr.font-size]=\"rulerFontSize\"\n\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.9)\"\n\t\t\t\t\t\t\tstyle=\"font-family: system-ui, sans-serif\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ tick.label }}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</svg>\n\t\t}\n\t</div>\n</div>\n", styles: [".pptx-ng-canvas-stage.is-editable{touch-action:none}\n"] }]
128934
130055
  }], ctorParameters: () => [], propDecorators: { slide: [{ type: i0.Input, args: [{ isSignal: true, alias: "slide", required: false }] }], canvasSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "canvasSize", required: true }] }], mediaDataUrls: [{ type: i0.Input, args: [{ isSignal: true, alias: "mediaDataUrls", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], fitPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "fitPadding", required: false }] }], maxFitScale: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFitScale", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], showGrid: [{ type: i0.Input, args: [{ isSignal: true, alias: "showGrid", required: false }] }], showRulers: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRulers", required: false }] }], rulerUnit: [{ type: i0.Input, args: [{ isSignal: true, alias: "rulerUnit", required: false }] }], showGuides: [{ type: i0.Input, args: [{ isSignal: true, alias: "showGuides", required: false }] }], snapToGrid: [{ type: i0.Input, args: [{ isSignal: true, alias: "snapToGrid", required: false }] }], gridSpacing: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridSpacing", required: false }] }], snapToShape: [{ type: i0.Input, args: [{ isSignal: true, alias: "snapToShape", required: false }] }], guideCommand: [{ type: i0.Input, args: [{ isSignal: true, alias: "guideCommand", required: false }] }], spellCheck: [{ type: i0.Input, args: [{ isSignal: true, alias: "spellCheck", required: false }] }], snapToGuides: [{ type: i0.Input, args: [{ isSignal: true, alias: "snapToGuides", required: false }] }], autoFit: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoFit", required: false }] }], transparentBackground: [{ type: i0.Input, args: [{ isSignal: true, alias: "transparentBackground", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], exposeElementIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "exposeElementIds", required: false }] }], presenting: [{ type: i0.Input, args: [{ isSignal: true, alias: "presenting", required: false }] }], selectedIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedIds", required: false }] }], editingId: [{ type: i0.Input, args: [{ isSignal: true, alias: "editingId", required: false }] }], editTemplateMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "editTemplateMode", required: false }] }], templateElements: [{ type: i0.Input, args: [{ isSignal: true, alias: "templateElements", required: false }] }], aiHighlights: [{ type: i0.Input, args: [{ isSignal: true, alias: "aiHighlights", required: false }] }], aiActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "aiActive", required: false }] }], aiActiveSlideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "aiActiveSlideIndex", required: false }] }], aiChangeBatch: [{ type: i0.Input, args: [{ isSignal: true, alias: "aiChangeBatch", required: false }] }], aiPickMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "aiPickMode", required: false }] }], drawTool: [{ type: i0.Input, args: [{ isSignal: true, alias: "drawTool", required: false }] }], drawColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "drawColor", required: false }] }], drawWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "drawWidth", required: false }] }], elementSelect: [{ type: i0.Output, args: ["elementSelect"] }], backgroundClick: [{ type: i0.Output, args: ["backgroundClick"] }], transformStart: [{ type: i0.Output, args: ["transformStart"] }], transformUpdate: [{ type: i0.Output, args: ["transformUpdate"] }], transformEnd: [{ type: i0.Output, args: ["transformEnd"] }], adjustUpdate: [{ type: i0.Output, args: ["adjustUpdate"] }], connectorEndpointUpdate: [{ type: i0.Output, args: ["connectorEndpointUpdate"] }], contextMenu: [{ type: i0.Output, args: ["contextMenu"] }], textEditStart: [{ type: i0.Output, args: ["textEditStart"] }], textCommit: [{ type: i0.Output, args: ["textCommit"] }], textInput: [{ type: i0.Output, args: ["textInput"] }], textCancel: [{ type: i0.Output, args: ["textCancel"] }], textFormat: [{ type: i0.Output, args: ["textFormat"] }], listSession: [{ type: i0.Output, args: ["listSession"] }], rotateUpdate: [{ type: i0.Output, args: ["rotateUpdate"] }], marqueeSelect: [{ type: i0.Output, args: ["marqueeSelect"] }], inkStrokeComplete: [{ type: i0.Output, args: ["inkStrokeComplete"] }], eraserHit: [{ type: i0.Output, args: ["eraserHit"] }], cellCommit: [{ type: i0.Output, args: ["cellCommit"] }], tableChange: [{ type: i0.Output, args: ["tableChange"] }], textEditor: [{ type: i0.ViewChild, args: ['textEditor', { isSignal: true }] }], listEditor: [{ type: i0.ViewChild, args: [i0.forwardRef(() => InlineListEditorComponent), { isSignal: true }] }], stageRef: [{ type: i0.ViewChild, args: ['stage', { isSignal: true }] }], viewportRef: [{ type: i0.ViewChild, args: ['viewport', { isSignal: true }] }], onPointerMove: [{
128935
130056
  type: HostListener,
128936
130057
  args: ['document:pointermove', ['$event']]
@@ -132165,31 +133286,39 @@ async function recordWebm(canvases, opts = {}) {
132165
133286
  recorder.onerror = (e) => reject(e);
132166
133287
  });
132167
133288
  recorder.start();
132168
- const frameIntervalMs = fpsToFrameIntervalMs(fps);
132169
- for (let i = 0; i < canvases.length; i++) {
132170
- if (signal?.aborted) {
132171
- recorder.stop();
132172
- throw new DOMException('Export cancelled', 'AbortError');
132173
- }
132174
- onProgress?.(i, canvases.length);
132175
- const duration = slideTimingsMs?.[i] ?? slideDurationMs;
132176
- const framesNeeded = segmentFrameCount(duration, fps);
132177
- ctx.clearRect(0, 0, recordingCanvas.width, recordingCanvas.height);
132178
- ctx.drawImage(canvases[i], 0, 0);
132179
- for (let f = 0; f < framesNeeded; f++) {
133289
+ try {
133290
+ const frameIntervalMs = fpsToFrameIntervalMs(fps);
133291
+ for (let i = 0; i < canvases.length; i++) {
132180
133292
  if (signal?.aborted) {
132181
133293
  recorder.stop();
132182
133294
  throw new DOMException('Export cancelled', 'AbortError');
132183
133295
  }
133296
+ onProgress?.(i, canvases.length);
133297
+ const duration = slideTimingsMs?.[i] ?? slideDurationMs;
133298
+ const framesNeeded = segmentFrameCount(duration, fps);
133299
+ ctx.clearRect(0, 0, recordingCanvas.width, recordingCanvas.height);
132184
133300
  ctx.drawImage(canvases[i], 0, 0);
132185
- await new Promise((resolve) => {
132186
- setTimeout(resolve, frameIntervalMs);
132187
- });
133301
+ for (let f = 0; f < framesNeeded; f++) {
133302
+ if (signal?.aborted) {
133303
+ recorder.stop();
133304
+ throw new DOMException('Export cancelled', 'AbortError');
133305
+ }
133306
+ ctx.drawImage(canvases[i], 0, 0);
133307
+ await new Promise((resolve) => {
133308
+ setTimeout(resolve, frameIntervalMs);
133309
+ });
133310
+ }
132188
133311
  }
133312
+ recorder.stop();
133313
+ await recorderDone;
133314
+ return new Blob(chunks, { type: 'video/webm' });
133315
+ }
133316
+ finally {
133317
+ // `recorder.stop()` only stops the recorder; the capture stream itself
133318
+ // keeps compositing frames from `recordingCanvas` indefinitely otherwise
133319
+ // - a real leak on every export, not just an abort path.
133320
+ stopCaptureStream(stream);
132189
133321
  }
132190
- recorder.stop();
132191
- await recorderDone;
132192
- return new Blob(chunks, { type: 'video/webm' });
132193
133322
  }
132194
133323
 
132195
133324
  /**
@@ -147614,7 +148743,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
147614
148743
  }], propDecorators: { canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], slideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideIndex", required: false }] }], selectedElement: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedElement", required: false }] }] } });
147615
148744
 
147616
148745
  // Generated by scripts/inline-shared.mjs from package.json. Do not edit.
147617
- const PPTX_ANGULAR_VIEWER_VERSION = "3.19.0";
148746
+ const PPTX_ANGULAR_VIEWER_VERSION = "3.19.2";
147618
148747
 
147619
148748
  /**
147620
148749
  * account-page.component.ts: File > Account content.
@@ -185879,4 +187008,4 @@ function cn(...values) {
185879
187008
  */
185880
187009
 
185881
187010
  export { CollaborationCursorsComponent as $, AFTER_ANIMATION_VALUES as A, AnimationAuthorPanelComponent as B, AnimationPanelComponent as C, AnimationPlaybackService as D, AutosaveRecoveryDialogComponent as E, AutosaveService as F, BroadcastDialogComponent as G, CHART_EDITOR_STYLES as H, CINEMATIC_TRANSITION_KEYFRAMES as I, CURSOR_PALETTE as J, CanvasFitService as K, ChartAxisOptionsComponent as L, ChartAxisStyleOptionsComponent as M, ChartComboTypeOptionsComponent as N, ChartDataEditorComponent as O, ChartDataLabelOptionsComponent as P, ChartDatapointMarkerOptionsComponent as Q, ChartDatapointOptionsComponent as R, ChartDisplayOptionsComponent as S, ChartElementViewComponent as T, ChartErrorBarOptionsComponent as U, ChartMarkerOptionsComponent as V, ChartPartSelectionService as W, ChartPrimitivesComponent as X, ChartRendererComponent as Y, ChartTrendlineOptionsComponent as Z, ChartTypeSelectorComponent as _, ALIGN_OPTIONS as a, INSTANT as a$, CollaborationService as a0, ColorChangedImageComponent as a1, CommentMarkersOverlayComponent as a2, CommentsPanelComponent as a3, CommentsService as a4, ComparePanelComponent as a5, ConnectorRendererComponent as a6, ConnectorTextOverlayComponent as a7, CustomShowsComponent as a8, DEFAULT_BOUNDS as a9, EditorContextMenuComponent as aA, EditorHistory as aB, EditorStateService as aC, EditorToolbarComponent as aD, EffectsPanelComponent as aE, ElementRendererComponent as aF, EmbeddedFontsService as aG, EncryptedFileDialogComponent as aH, EquationEditorDialogComponent as aI, EquationRendererComponent as aJ, EquationTemplateGalleryComponent as aK, ExportProgressModalComponent as aL, ExportService as aM, FieldContextService as aN, FindBarComponent as aO, FindReplaceBarComponent as aP, FollowModeBarComponent as aQ, FontEmbeddingListComponent as aR, FontEmbeddingPanelComponent as aS, GALLERY_THEME_PRESETS as aT, GOOGLE_WEBFONTS_LINK_ID as aU, GRIDLINE_COLOR$1 as aV, GoogleWebfontsService as aW, GradientPickerComponent as aX, HANDOUT_OPTIONS as aY, HeaderFooterDialogComponent as aZ, HyperlinkDialogComponent as a_, DEFAULT_BROADCAST_SERVER_URL as aa, DEFAULT_CANVAS_HEIGHT as ab, DEFAULT_CANVAS_WIDTH as ac, DEFAULT_COLOR_SCHEME as ad, DEFAULT_FILL_COLOR$1 as ae, DEFAULT_LAYOUT as af, DEFAULT_MORPH_DURATION_MS as ag, DEFAULT_PALETTE$1 as ah, DEFAULT_PATTERN_FILL_PRESET as ai, DEFAULT_PRINT_SETTINGS as aj, DEFAULT_SLIDE_BACKGROUND as ak, DEFAULT_STROKE_COLOR as al, DEFAULT_STYLE as am, DEFAULT_TABLE_ROW_HEIGHT as an, DEFAULT_TEXT_COLOR$2 as ao, DEFAULT_VIEWER_PROFILE as ap, DIRECTIONAL_PRESETS as aq, DIRECTION_OPTIONS as ar, DocumentPropertiesCardComponent as as, EASE as at, EFFECT_SOUND_CATALOGUE as au, EMBEDDED_FONTS_STYLE_ID as av, EMPHASIS_PRESETS as aw, ENTRANCE_PRESETS as ax, TEMPLATES as ay, EXIT_PRESETS as az, ANIMATION_PRESET_CATEGORIES as b, REPEAT_MODE_OPTIONS as b$, ImagePropertiesPanelComponent as b0, InkDrawingService as b1, InkRendererComponent as b2, InsertSmartArtDialogComponent as b3, InspectorPaneHeaderComponent as b4, InspectorPanelComponent as b5, IsMobileService as b6, KeepAnnotationsDialogComponent as b7, LOCALE_CATALOG as b8, LONG_PRESS_DURATION_MS as b9, POWER_POINT_VIEWER_PROVIDERS as bA, PPTX_OPEN_ACCEPT as bB, PRESENTATION_OPEN_EXTENSIONS as bC, PRESENTER_CHANNEL_NAME as bD, PRESENTER_MSG_ORIGIN as bE, PRESENTER_TIMER_SEGMENT_MS as bF, PX_PER_CM as bG, PX_PER_INCH as bH, PasswordProtectionDialogComponent as bI, PasswordStrengthMeterComponent as bJ, PowerPointViewerComponent as bK, PresentToolbarAutoHide as bL, PresentationAnnotationOverlayComponent as bM, PresentationAnnotationsService as bN, PresentationOverlayComponent as bO, PresentationPropertiesPanelComponent as bP, PresentationSettingsCardComponent as bQ, PresentationSubtitleBarComponent as bR, PresentationToolbarComponent as bS, PresentationTransitionOverlayComponent as bT, PresenterViewComponent as bU, PresenterWindowService as bV, PrintDialogComponent as bW, PrintService as bX, PrintSettingsPanelComponent as bY, PropertiesDialogComponent as bZ, RANDOM_ELIGIBLE_TYPES as b_, LONG_PRESS_MOVE_TOLERANCE_PX as ba, LoadContentService as bb, LocalPresencePublisher as bc, MAX_ZOOM_SCALE as bd, MIN_ZOOM_SCALE as be, MOTION_PATH_COLUMNS as bf, MediaPreviewComponent as bg, MediaPropertiesPanelComponent as bh, MediaRendererComponent as bi, MediaTrimTimelineComponent as bj, MobileBottomBarComponent as bk, MobileMenuSheetComponent as bl, MobilePresenterViewComponent as bm, MobileSheetComponent as bn, MobileSlidesSheetComponent as bo, MobileToolbarComponent as bp, ModalDialogComponent as bq, Model3DRendererComponent as br, NotesHandoutCardComponent as bs, NotesPanelComponent as bt, NotesToolbarComponent as bu, OleRendererComponent as bv, OutlineViewOverlayComponent as bw, P14_TRANSITION_KEYFRAMES as bx, P14_TRANSITION_KEYFRAMES_2 as by, P14_TRANSITION_KEYFRAMES_ALL as bz, AUDIENCE_HASH as c, SlideSorterOverlayComponent as c$, RESIZE_HANDLES as c0, RULER_FONT_SIZE as c1, RULER_THICKNESS as c2, ReadingViewOverlayComponent as c3, RemoteSelectionOverlayComponent as c4, RibbonAnimationGalleryComponent as c5, RibbonAnimationsSectionComponent as c6, RibbonArrangeSectionComponent as c7, RibbonColorPopoverComponent as c8, RibbonComponent as c9, DEFAULT_PALETTE as cA, PALETTES$1 as cB, SMART_ART_COLOR_SCHEMES as cC, SMART_ART_STYLE_OPTIONS as cD, SUB_ITEM_LABEL as cE, SVG_WARP_PRESETS as cF, SWIPE_MAX_VERTICAL_PX as cG, SWIPE_THRESHOLD_PX as cH, SelectionPaneComponent as cI, SetUpSlideShowDialogComponent as cJ, SettingsAppearanceTabComponent as cK, SettingsDialogComponent as cL, SettingsLanguageTabComponent as cM, ShareDialogComponent as cN, ShortcutPanelComponent as cO, ShowOptionsFieldsetComponent as cP, ShowSlidesFieldsetComponent as cQ, SignatureStrippedDialogComponent as cR, SignaturesPanelComponent as cS, SignaturesService as cT, SlideBackgroundCardComponent as cU, SlideCanvasComponent as cV, SlideDefaultInspectorComponent as cW, SlideDiffChangesComponent as cX, SlideDiffRowComponent as cY, SlideDiffThumbnailsComponent as cZ, SlideSizeCardComponent as c_, RibbonDesignSectionComponent as ca, RibbonDrawSectionComponent as cb, RibbonDrawingGroupComponent as cc, RibbonEditingSectionComponent as cd, RibbonFileSectionComponent as ce, RibbonFontControlsComponent as cf, RibbonHomeSectionComponent as cg, RibbonHyperlinkButtonComponent as ch, RibbonInsertFieldsComponent as ci, RibbonInsertSectionComponent as cj, RibbonMotionPathGalleryComponent as ck, RibbonParagraphControlsComponent as cl, RibbonPrimaryRowComponent as cm, RibbonReviewSectionComponent as cn, RibbonShapeExtrasComponent as co, RibbonSlideshowSectionComponent as cp, RibbonTransitionsSectionComponent as cq, RibbonViewSectionComponent as cr, RulerGuidesService as cs, SEQUENCE_OPTIONS as ct, SEVERITY_GROUPS as cu, SEVERITY_LABELS as cv, DEFAULT_TRANSITION_DURATION_MS$1 as cw, SHORTCUT_REFERENCE_ITEMS as cx, SLIDE_TRANSITION_KEYFRAMES as cy, SLIDE_TRANSITION_KEYFRAMES_CSS as cz, AUDIENCE_NONCE_KEY as d, ZoomNavigationService as d$, SlideThemeOverridePanelComponent as d0, SlideTransitionCardComponent as d1, SlidesPanelComponent as d2, SmartArt3DRendererComponent as d3, SmartArt3DService as d4, SmartArtPreviewComponent as d5, SmartArtPropertiesComponent as d6, SmartArtRendererComponent as d7, StatusBarComponent as d8, TABLE_STRUCTURE_TOGGLES as d9, TransitionPreviewComponent as dA, VALIGN_OPTIONS as dB, VIEWER_THEME as dC, VersionHistoryPanelComponent as dD, ViewerCanvasEditingService as dE, ViewerCollabCursorService as dF, ViewerCollaborationSessionService as dG, ViewerCompareService as dH, ViewerCustomShowsService as dI, ViewerDialogsService as dJ, ViewerDocumentPropertiesService as dK, ViewerExportService as dL, ViewerExtraDialogsComponent as dM, ViewerFileIOService as dN, ViewerFindReplaceService as dO, ViewerFormatPainterService as dP, ViewerInspectorPanelService as dQ, ViewerKeyboardService as dR, ViewerMobileSheetService as dS, ViewerPresentationModeService as dT, ViewerThemeGalleryService as dU, ViewerTouchGesturesService as dV, ViewerZoomService as dW, WEBM_MIME_CANDIDATES as dX, WHEEL_SPOKE_COUNTS as dY, WriteBackScheduler as dZ, ZERO_LINE_COLOR as d_, TEXT_3D_BOTTOM_BEVEL_KEYS as da, TEXT_3D_TOP_BEVEL_KEYS as db, TEXT_DIRECTION_OPTIONS$1 as dc, THEME_CATALOG as dd, TIMING_CURVE_OPTIONS as de, TRANSITION_SPEED_DURATION_MS as df, TRIGGER_OPTIONS as dg, TYPE_LABELS as dh, TableCellAdvancedFillComponent as di, TableCellFormattingComponent as dj, TableDataEditorComponent as dk, TablePropertiesComponent as dl, TableRendererComponent as dm, TableResizeOverlayComponent as dn, TableSelectionService as dp, TagsCardComponent as dq, Text3DBevelSectionComponent as dr, Text3DPanelComponent as ds, TextAdvancedPanelComponent as dt, ThemeEditorFieldsComponent as du, ThemeGalleryComponent as dv, ThemeSelectorCardComponent as dw, TitleBarComponent as dx, TitleBarSearchComponent as dy, TransitionDirectionPickerComponent as dz, AVATAR_COLOR_SWATCHES as e, buildModel3DViewModel as e$, ZoomRendererComponent as e0, ZoomTargetService as e1, addCategory as e2, addCommentToList as e3, addGradientStopPatch as e4, addItem as e5, addSeries as e6, addSubItem as e7, advanceStep as e8, affordanceElements as e9, buildCellParagraphs as eA, buildChartViewModel as eB, buildChatLogExport as eC, buildChatLogMarkdown as eD, buildChromeStyle as eE, buildClearHyperlinkPatch as eF, buildClickGroups as eG, buildColStyles as eH, buildCollaborationConfig as eI, buildComboViewModel as eJ, buildCssGradientFromShapeStyle as eK, buildDuotoneFilter as eL, buildDuotoneFilterId as eM, buildEmbeddedFontStyles as eN, buildEquationElement as eO, buildEquationSegment as eP, buildFallbackViewModel as eQ, buildFontFaceRule as eR, buildGradientFillCss as eS, buildGridlinesAndLabels as eT, buildHyperlinkPatch as eU, buildInkContainerStyle as eV, buildInkStrokes as eW, buildLegend as eX, buildLiveInkStrokeView as eY, buildMarkTooltip as eZ, buildModel3DContainerStyle as e_, aiToggleVisible as ea, alignPatch as eb, animationFor as ec, animationPresetLabelKey as ed, annotationMapToInkInserts as ee, applyAcceptedDiff as ef, applyAnimationPreset as eg, applyFindReplacements as eh, applyFormatToElement as ei, applyMove as ej, applyResize as ek, asMediaElement as el, assignUserColor as em, attachShowVisibilityPause as en, attachTouchGestures as eo, axisTickValues as ep, beginDrawingNodeEdit as eq, beginNodeEdit as er, bevelSizePatch as es, boolFromEvent as et, bringForward as eu, bringToFront as ev, buildBarActions as ew, buildBroadcastConfig as ex, buildBroadcastViewerUrl as ey, buildCategoryLabels as ez, AXIS_LABEL_COLOR as f, computeErrorBarPrimitives as f$, buildOleActionModel as f0, buildOleInfoRows as f1, buildPatternFillCss as f2, buildPieViewModel as f3, buildPrintHtmlDocument as f4, buildPropertiesPatch as f5, buildRadarViewModel as f6, buildRegionMapViewModel as f7, buildSaveSlides as f8, buildShareUrl as f9, changeIcon as fA, characterSpacingPatch as fB, chartPreserveAspectRatio as fC, checkFontAvailable as fD, clampCursorPosition as fE, clampGifDimensions as fF, clampIndex as fG, clampNotesFontSize as fH, clampScale as fI, clampStep as fJ, clearAllLocalViewerData as fK, clearAudienceContent as fL, cn as fM, collectAccessibilityIssues as fN, collectElementText as fO, collectSlideText as fP, collectStoredChats as fQ, collectUsedFontFamilies as fR, columnWidthStyle as fS, commitNodeText as fT, computeAlign as fU, computeAxisTitlePrimitives as fV, computeBarRects as fW, computeBubbleRadius as fX, computeCornerHandle as fY, computeDistribute as fZ, computeDrawingViewBox as f_, buildSmartArtInsertElement as fa, buildSmartArtNodes as fb, buildStockViewModel as fc, buildSurfaceViewModel as fd, buildTableViewModel as fe, buildTreemapViewModel as ff, buildTrimFragment as fg, buildWaterfallViewModel as fh, buildZeroLine as fi, buildZoomContainerStyle as fj, buildZoomViewModel as fk, bulletIndentPx as fl, canAddTopLevelNode as fm, canEditSmartArtNodes as fn, canGroupSelection as fo, canRemoveTopLevelNode as fp, canSetStrokeWidth as fq, canStartBroadcast as fr, canStartShare as fs, canUngroupSelection as ft, canUseClipboard as fu, captionDisplayText as fv, cellRunStyle as fw, cellStyleToStyleMap as fx, cellTdStyle as fy, changeCountLabel as fz, AccessibilityPanelComponent as g, encodeGif as g$, computeFocusTargets as g0, computeGridSpacingPx as g1, computeHandleBoxes as g2, computeHandoutLayout as g3, computeIsMobile as g4, computeIsTablet as g5, computeLinePoints as g6, computeLinearRegression as g7, computePageCount as g8, computePieLayout as g9, createWebsocketBundle as gA, cssObjectToStyleMap as gB, currentColorScheme as gC, currentLayout as gD, currentStyle as gE, defaultCssVars as gF, defaultRadius as gG, defaultThemeColors as gH, deleteElementsByIds as gI, deleteVersion as gJ, demoteNode as gK, deriveModel3DBlobUrl as gL, derivePresenceList as gM, describeSmartArtBounds as gN, disableGlowPatch as gO, disableInnerShadowPatch as gP, disableOuterShadowPatch as gQ, disableReflectionPatch as gR, disableSoftEdgePatch as gS, duplicateElementById as gT, durationOf as gU, effectsStateOf as gV, enableGlowPatch as gW, enableInnerShadowPatch as gX, enableOuterShadowPatch as gY, enableReflectionPatch as gZ, enableSoftEdgePatch as g_, computePieSlicePath as ga, computePieSlices as gb, computePlotLayout as gc, computeRSquared as gd, computeRadarPoints as ge, computeResizeHandleBoxes as gf, computeRotateHandleBox as gg, computeScatterDots as gh, computeScatterXDomain as gi, computeSelectionBoxes as gj, computeSingleSelected as gk, computeSlideIndices as gl, computeSnap as gm, computeStackedBarRects as gn, computeStackedValueRange as go, computeTrendlinePrimitives as gp, computeValueRange as gq, convertOmmlToMathMl as gr, copyFormatFromElement as gs, countAccessibilityIssues as gt, countAnnotationStrokes as gu, createAngularAiBridge as gv, createCustomShow as gw, createImageElementFromFile as gx, createSwipeDismissDrag as gy, createWebrtcBundle as gz, AccessibilityService as h, gradientStateFromStyle as h$, endShowMediaCleanup as h0, estimatePageCount as h1, exitPresentationFullscreen as h2, exportAiChatLogs as h3, extractPathPoints as h4, eyedropperAvailable as h5, fillColorOf$1 as h6, findInSlides as h7, findOwningSlideIndex as h8, findSlideIndexByElementId as h9, getEffectSoundState as hA, getImageSrc as hB, getLocalStorageUsageSummary as hC, getOleAriaLabel as hD, getOleBadgeLabel as hE, getOleDisplayName as hF, getOleDownloadFileName as hG, getOleTypeColor as hH, getOleTypeLabel as hI, getP14TransitionAnimations as hJ, getPasswordStrength as hK, getPatternSvg as hL, getPlaceholderStyle as hM, getVersions as hN, getResolvedShapeClipPath as hO, getResolvedShapeClipPathFor as hP, getSessionTabId as hQ, getShapeFillStrokeStyle as hR, getSlideBackgroundStyle as hS, getSlideTransitionAnimations as hT, getSmartArtNodeBounds as hU, getSpeechRecognitionCtor as hV, getTextBlockStyle as hW, getTextWarp as hX, getTouchDistance as hY, getWarpCategory as hZ, getWarpPath as h_, firstVisibleIndex as ha, fitPolynomial as hb, fitZoom as hc, focusTargetChips as hd, fontMimeForFormat as he, fontSizeOf as hf, forgetSessionDeck as hg, formatAxisValue as hh, formatBytes as hi, formatCursorLabel as hj, formatElapsed as hk, formatFileSize as hl, formatInlineListStyle as hm, formatPropertyDate as hn, formatTime as ho, fpsToFrameIntervalMs as hp, generateBroadcastRoomId as hq, generateCommentId as hr, generateCustomShowId as hs, generatePressureCircles as ht, generateTicks as hu, getCinematicTransitionAnimations as hv, getClrChangeParams as hw, getContainerStyle as hx, getDuotoneFilterDef as hy, getEffectSoundAsset as hz, AccessibilityTextPanelComponent as i, narrowToRect as i$, gradientStateOf as i0, gradientStatePatch as i1, gradientStopColorCommitPatch as i2, gridColumns as i3, groupIssuesBySeverity as i4, hasAnimation as i5, hasCopyableFormat as i6, hasExistingLink as i7, hasExitedFullscreen as i8, hasGradientFill as i9, isValidRoomId as iA, isViewportBackgroundPressTarget as iB, isZoomActivationKey as iC, issueTrackKey as iD, issueTypeLabel as iE, keyToLabel as iF, lastVisibleIndex as iG, latexToMathml as iH, layoutConnectorPaints as iI, layoutNodeLabels as iJ, linePointsToSvgString as iK, lineSpacingPatch as iL, loadAudienceContent as iM, loadSessionDeck as iN, mediaFallbackFor as iO, mediaSurfaceFor as iP, mergeCaptionResults as iQ, mergeDown as iR, mergeRight as iS, mergeSelection as iT, mergeTablesDirective as iU, moveElementBy as iV, moveNodeDown as iW, moveNodeUp as iX, msToFrameDelayCs as iY, narrowToCircle as iZ, narrowToPolygon as i_, hasPressureVariation as ia, hasVisibleSlideAfter as ib, headerLabel as ic, imageDimensions as id, inkViewBox as ie, insertTableElementColumn as ig, insertTableElementRow as ih, interpolateWidth as ii, isAudienceTab as ij, isBold as ik, isBrowserOpenableMime as il, isChildNode as im, isElementInteractive as io, isInjectableUrl as ip, isItalic as iq, isLegacyBinaryPresentation as ir, isPpactionUrl as is, isPresenterMessage as it, isSigned as iu, isSupportedPresentationFile as iv, isTextElement as iw, isTwoTableFocus as ix, isUnderline as iy, isUrlSafe as iz, AccountPageComponent as j, removeNode as j$, newChartElement as j0, newEquationElement as j1, newPresetShapeElement as j2, newShapeElement as j3, newSmartArtElement as j4, newTableElement as j5, newTextElement as j6, nextVisibleIndex as j7, nodeBold as j8, nodeEditBox as j9, planGifFrames as jA, planVideoSegments as jB, pointFromPointerEvent as jC, pointsToSvgPathD as jD, presenceToCursors as jE, presentationBaseName as jF, presentationStageStyle as jG, presenterTimerProgress as jH, presetByLayout as jI, presetsForCategory as jJ, pressuresToWidths as jK, prevVisibleIndex as jL, projectDrawingShapes as jM, promoteNode as jN, provideViewerTheme as jO, radarAngle as jP, radarRingPoints as jQ, readAsDataUrl as jR, recordWebm as jS, registerCrossSlideAudio as jT, rememberSessionDeck as jU, removeAnimation as jV, removeCategory as jW, removeTableElementColumn as jX, removeCommentFromList as jY, removeElementAnimation as jZ, removeGradientStopPatch as j_, nodeFillColor as ja, nodeFontColor as jb, nodeIdFromKey as jc, nodeItalic as jd, nodeStyle as je, normalizeFontFormat as jf, normalizeSlidesPerPage as jg, normalizeValue as jh, numFromEvent as ji, ommlToMathml as jj, ooxmlDashToCssBorderStyle as jk, openNativeEyeDropper as jl, overallStatus as jm, paletteColor as jn, parseAudienceNonce as jo, parseNodeTextarea as jp, partitionSlides as jq, patchChartData as jr, patchChartStyle as js, patchTableData as jt, patchTextStyle as ju, patternPresetOptions as jv, pendingElementStyles as jw, pickColorByClickFallback as jx, pickFile as jy, pickSupportedMimeType as jz, ActionSettingsPanelComponent as k, setAxisLogScale as k$, removeTableElementRow as k0, removeSeries as k1, renderToCanvas as k2, reorderAnimationDown as k3, reorderAnimationUp as k4, replaceInSlides as k5, replaceMatch as k6, requestPresentationFullscreen as k7, resizeElement as k8, resolveCaptionTracks as k9, rulerHighlight as kA, rulerStripTicks as kB, sampleColorFromSlide as kC, sanitizeColor as kD, sanitizeSlideIndex as kE, sanitizeUserName as kF, saveViewerProfile as kG, savedPresentationFileName as kH, scanAvailableFonts as kI, searchSlides as kJ, seedBroadcastFields as kK, seedHyperlinkDraft as kL, seedPropertiesDraft as kM, seedShareFields as kN, segmentFrameCount as kO, selectValue$3 as kP, sendBackward as kQ, sendToBack as kR, sequentialColorScale as kS, serializeWriteBack as kT, seriesColor as kU, setAfterAnimation as kV, setAfterAnimationColor as kW, setAnimationEmphasis as kX, setAnimationEntrance as kY, setAnimationExit as kZ, setAxis as k_, resolveChartKind as ka, resolveDirection as kb, resolveDirection8 as kc, resolveFontVariant as kd, resolveHyperlinkHref as ke, resolveInteractiveElementId as kf, resolveMediaSrc as kg, resolveOleType as kh, resolveOrientation as ki, resolveParagraphBullet as kj, resolvePresenterNotes as kk, resolveProfileInitial as kl, resolveRegionCode as km, resolveRibbonCanGroup as kn, resolveSlideAutoAdvanceMs as ko, resolveSlideTransition as kp, resolvePalette as kq, resolveThemeCatalogEntry as kr, resolveTransitionDuration as ks, resolveTransitionDurationMs as kt, resolveWheelSpokeCount as ku, restoreSessionDeck as kv, revealedElementStyles as kw, routeOrthogonalConnector as kx, rowStyle as ky, rulerDragToGuidePosition as kz, AdvancedChartEditorComponent as l, textAdvancedStateFromStyle as l$, setAxisTitleStyle as l0, setCategoryLabel as l1, setCellText as l2, setColorScheme as l3, setDataLabels as l4, setDataPointExplosion as l5, setDataPointFill as l6, setDataPointLabel as l7, setDataPointMarker as l8, setDelay as l9, sheetAfterNavigate as lA, shouldBlockClickAdvance as lB, shouldUseSvgWarp as lC, showDirectionPicker as lD, showsTemplateAffordance as lE, signatureCountLabel as lF, signatureKey as lG, signatureTimestamp as lH, signerName as lI, statusLabel as lJ, slideNumberOf as lK, slidesWithReappliedLayout as lL, smartArtNodes as lM, paletteColour as lN, snapToGridStep as lO, splitCursorCell as lP, splitMergedCell as lQ, statusKind as lR, statusLabel$1 as lS, storeAudienceContent as lT, stringFromEvent$5 as lU, strokeColorOf as lV, strokeToInkElement as lW, strokeWidthOf as lX, styleShadowFilter as lY, surfaceColor as lZ, textAdvancedPatch as l_, setDirection as la, setDuration as lb, setEffectSound as lc, setEffectStockSound as ld, setElementPosition as le, setGridlineStyle as lf, setLayout as lg, setLegend as lh, setNodeStyle as li, setNodeText as lj, setRepeatCount as lk, setRepeatMode as ll, setSequence as lm, setSeriesChartType as ln, setSeriesColor as lo, setSeriesErrorBars as lp, setSeriesMarker as lq, setSeriesName as lr, setSeriesTrendline as ls, setSeriesValue as lt, setStyle as lu, setTimingCurve as lv, setTitle as lw, setTrigger as lx, setTriggerShapeId as ly, shapeStylePatch$1 as lz, AiChangeOverlayComponent as m, textAdvancedStateOf as m0, textColorOf as m1, textDirectionPatch as m2, textFontSizePatch as m3, textStyleOf as m4, textStylePatch as m5, themeStyle as m6, themeToCssVars as m7, thumbnailHeight as m8, thumbnailZoom as m9, zoomTargetSlideIndex as mA, toggleCommentResolvedInList as ma, toggleNodeBold as mb, toggleNodeItalic as mc, toggleSheet as md, topLevelNodeCount as me, transformSelectedTextCase as mf, translationsEn as mg, updateElementById as mh, updateGlowPatch as mi, updateGradientStopPatch as mj, updateInnerShadowPatch as mk, updateOuterShadowPatch as ml, updateReflectionPatch as mm, vAlignPatch as mn, validatePassword as mo, validatePrintSettings as mp, validateRoomId as mq, valueToY as mr, vermilionDarkColors as ms, vermilionDarkTheme as mt, vermilionLightColors as mu, vermilionLightTheme as mv, vermilionRadius as mw, waypointsToPathD as mx, withManualLayouts as my, worstStatus as mz, AiChatPanelComponent as n, AiChatService as o, AiComposerComponent as p, AiFocusBarComponent as q, AiFocusHighlightOverlayComponent as r, AiHistoryMenuComponent as s, toChatSummary as t, AiHistoryService as u, AiMessageListComponent as v, AiPanelStore as w, AiProposalCardComponent as x, AiSettingsSectionComponent as y, AiToolCallCardComponent as z };
185882
- //# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-D2wmF5iB.mjs.map
187011
+ //# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-DP0uS-ib.mjs.map