pptx-angular-viewer 2.18.7 → 2.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import { NgStyle, NgTemplateOutlet, NgClass } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
3
  import { InjectionToken, input, output, inject, computed, ChangeDetectionStrategy, Component, signal, Injectable, DestroyRef, viewChild, effect, Injector, ElementRef, HostListener, afterNextRender, Renderer2, ViewEncapsulation, forwardRef, untracked } from '@angular/core';
4
4
  import { TranslateService, TranslatePipe, translate } from '@ngx-translate/core';
5
- import { THEME_PRESETS, guideEmuToPx, getShapeClipPath, getAdjustmentAwareClipPath, getShapeClipPathFromPreset, getCloudPathForRendering, ooxmlGradientAngleToCssDegrees, hasShapeProperties, evaluatePresetShape, getShapeType, isImageLikeElement, MIN_ELEMENT_SIZE as MIN_ELEMENT_SIZE$1, getElementTextContent, getRoundRectRadiusPx as getRoundRectRadiusPx$1, applyDrawingColorTransforms as applyDrawingColorTransforms$1, hslToRgb as hslToRgb$1, PRESET_COLOR_MAP, hasTextProperties, stripXmlOrderSuffix, orderedXmlKey, chartDataAddSeries, chartDataRemoveSeries, chartDataAddCategory, chartDataRemoveCategory, chartDataUpdatePoint, rebuildTableStructureInRawXml, updateCellTextInRawXml, deepCloneData, cloneTemplateElementsBySlideId, cloneSlide, DEFAULT_SCHEME_COLOR_MAP as DEFAULT_SCHEME_COLOR_MAP$1, formatAutoNumberMarker, getLinkedTextBoxSegments, getSubstituteFontFamily, parsePanoseString, deriveSlideTitle, svgPathToPolygons, THEME_COLOR_SCHEME_KEYS, createBuiltinVariables, resolveCoordinate, lookupPresetShape, normalizeStShapeType, evaluateGuides, PptxHandler, deobfuscateFont, detectFontFormat, createEditorId, makeStoreAwareId, reassignDescendantIds, checkMissingAltText, checkMissingSlideTitle, checkLowContrast, checkComplexTables, checkBlankSlide, checkDuplicateTitles, createChartElement, detectFileFormat, serializePptxToJson, PPTX_JSON_MIME_TYPE, PptxMarkdownConverter, ThemePresets, formatCommentTimestamp as formatCommentTimestamp$1, EncryptedFileError, decodeXmlEntities, parseSignatureXml, groupElements, ungroupElements, cloneElement, updateSmartArtNodeText, setSmartArtNodeStyle, isInkElement, parseDataUrlToBytes, isZoomElement, SvgExporter, applyThemeToData, pptxActionToElementAction, elementActionToPptxAction, setChartTitle, setChartLegend, setChartDataLabels, setChartAxis, setChartSeriesTrendline, setChartSeriesErrorBars, setChartDataPointLabel, setChartAxisLogScale, setChartAxisTitleStyle, setChartAxisGridlineStyle, setChartSeriesMarker, setChartSeriesChartType, setChartDataPointFill, setChartDataPointMarker, setChartDataPointExplosion, chartDataChangeType, getOleObjectTypeLabel, addSmartArtNodeAsChild, removeSmartArtNode, promoteSmartArtNode, demoteSmartArtNode, reorderSmartArtNode, switchSmartArtLayout, SWITCHABLE_LAYOUT_TYPES, COLOR_MAP_ALIAS_KEYS, DEFAULT_COLOR_MAP, applyThemeOverrideToSlide, TRANSITION_VALID_DIRECTIONS } from 'pptx-viewer-core';
5
+ import { THEME_PRESETS, guideEmuToPx, getShapeClipPath, getAdjustmentAwareClipPath, getShapeClipPathFromPreset, getCloudPathForRendering, ooxmlGradientAngleToCssDegrees, hasShapeProperties, evaluatePresetShape, getShapeType, isImageLikeElement, MIN_ELEMENT_SIZE as MIN_ELEMENT_SIZE$1, getElementTextContent, getRoundRectRadiusPx as getRoundRectRadiusPx$1, applyDrawingColorTransforms as applyDrawingColorTransforms$1, hslToRgb as hslToRgb$1, PRESET_COLOR_MAP, hasTextProperties, stripXmlOrderSuffix, orderedXmlKey, chartDataAddSeries, chartDataRemoveSeries, chartDataAddCategory, chartDataRemoveCategory, chartDataUpdatePoint, rebuildTableStructureInRawXml, updateCellTextInRawXml, deepCloneData, cloneTemplateElementsBySlideId, cloneSlide, DEFAULT_SCHEME_COLOR_MAP as DEFAULT_SCHEME_COLOR_MAP$1, formatAutoNumberMarker, getLinkedTextBoxSegments, getSubstituteFontFamily, parsePanoseString, deriveSlideTitle, svgPathToPolygons, THEME_COLOR_SCHEME_KEYS, createBuiltinVariables, resolveCoordinate, lookupPresetShape, normalizeStShapeType, evaluateGuides, PptxHandler, addSmartArtNode, removeSmartArtNode, demoteSmartArtNode, promoteSmartArtNode, reorderSmartArtNode, deobfuscateFont, detectFontFormat, createEditorId, makeStoreAwareId, reassignDescendantIds, checkMissingAltText, checkMissingSlideTitle, checkLowContrast, checkComplexTables, checkBlankSlide, checkDuplicateTitles, createChartElement, detectFileFormat, serializePptxToJson, PPTX_JSON_MIME_TYPE, PptxMarkdownConverter, ThemePresets, formatCommentTimestamp as formatCommentTimestamp$1, EncryptedFileError, decodeXmlEntities, parseSignatureXml, groupElements, ungroupElements, cloneElement, updateSmartArtNodeText, setSmartArtNodeStyle, isInkElement, parseDataUrlToBytes, isZoomElement, SvgExporter, applyThemeToData, pptxActionToElementAction, elementActionToPptxAction, setChartTitle, setChartLegend, setChartDataLabels, setChartAxis, setChartSeriesTrendline, setChartSeriesErrorBars, setChartDataPointLabel, setChartAxisLogScale, setChartAxisTitleStyle, setChartAxisGridlineStyle, setChartSeriesMarker, setChartSeriesChartType, setChartDataPointFill, setChartDataPointMarker, setChartDataPointExplosion, chartDataChangeType, getOleObjectTypeLabel, addSmartArtNodeAsChild, switchSmartArtLayout, SWITCHABLE_LAYOUT_TYPES, COLOR_MAP_ALIAS_KEYS, DEFAULT_COLOR_MAP, applyThemeOverrideToSlide, TRANSITION_VALID_DIRECTIONS } from 'pptx-viewer-core';
6
6
  import DOMPurify from 'dompurify';
7
7
  import { z } from 'zod';
8
8
  import * as mcp from 'pptx-viewer-mcp';
@@ -544,7 +544,7 @@ const DEFAULT_CANVAS_HEIGHT = 720;
544
544
  /** Fallback text colour. */
545
545
  const DEFAULT_TEXT_COLOR$1 = '#111827';
546
546
  /** Fallback shape fill colour. */
547
- const DEFAULT_FILL_COLOR = '#3b82f6';
547
+ const DEFAULT_FILL_COLOR$1 = '#3b82f6';
548
548
  /** Fallback shape stroke colour. */
549
549
  const DEFAULT_STROKE_COLOR = '#1f2937';
550
550
  /** Colour PowerPoint paints hyperlinked text in when the theme declares none. */
@@ -915,7 +915,7 @@ function sanitizeGradientStops(stops) {
915
915
  typeof stop?.position === 'number' &&
916
916
  Number.isFinite(stop.position))
917
917
  .map((stop) => ({
918
- color: normalizeHexColor$1(String(stop.color), DEFAULT_FILL_COLOR),
918
+ color: normalizeHexColor$1(String(stop.color), DEFAULT_FILL_COLOR$1),
919
919
  position: Math.max(0, Math.min(100, stop.position)),
920
920
  opacity: typeof stop.opacity === 'number' && Number.isFinite(stop.opacity)
921
921
  ? clampUnitInterval$1(stop.opacity)
@@ -8735,6 +8735,25 @@ function renderWinLossSparkline(data) {
8735
8735
  ].join('');
8736
8736
  }
8737
8737
 
8738
+ // Horizontal centre-to-centre spacing between legend swatches in a row, and
8739
+ // vertical spacing between legend swatches stacked in a side column.
8740
+ const LEGEND_ITEM_WIDTH = 80, LEGEND_ITEM_HEIGHT = 14;
8741
+ /**
8742
+ * Compute the positioned legend entries for a chart view model.
8743
+ *
8744
+ * @param vm - The chart view model (`vm.legend` / `vm.legendX` / `vm.legendY`
8745
+ * / `vm.legendAnchor`, as produced by `buildChartViewModel`).
8746
+ */
8747
+ function computeChartLegendLayout(vm) {
8748
+ const vertical = vm.legendAnchor === 'start';
8749
+ return vm.legend.map((entry, i) => {
8750
+ const x = vertical
8751
+ ? vm.legendX
8752
+ : vm.legendX - (vm.legend.length * LEGEND_ITEM_WIDTH) / 2 + i * LEGEND_ITEM_WIDTH, y = vertical ? vm.legendY + i * LEGEND_ITEM_HEIGHT : vm.legendY;
8753
+ return { x, y, color: entry.color, label: entry.label };
8754
+ });
8755
+ }
8756
+
8738
8757
  const CHART_TYPE_OPTIONS = [
8739
8758
  { value: 'bar', label: 'Bar', labelKey: 'pptx.chart.typeBar' },
8740
8759
  { value: 'line', label: 'Line', labelKey: 'pptx.chart.typeLine' },
@@ -9849,11 +9868,8 @@ function blendToWhite(color) {
9849
9868
  if (!match) {
9850
9869
  return color;
9851
9870
  }
9852
- const value = Number.parseInt(match[1], 16);
9853
- const mix = (channel) => Math.round(channel + (255 - channel) * 0.5);
9854
- const r = mix((value >> 16) & 0xff);
9855
- const g = mix((value >> 8) & 0xff);
9856
- const b = mix(value & 0xff);
9871
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
9872
+ const value = Number.parseInt(match[1], 16), mix = (channel) => Math.round(channel + (255 - channel) * 0.5), r = mix((value >> 16) & 0xff), g = mix((value >> 8) & 0xff), b = mix(value & 0xff);
9857
9873
  return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0').toUpperCase()}`;
9858
9874
  }
9859
9875
  /**
@@ -9868,8 +9884,7 @@ function invertNegativeFill(series, pointIndex, value, baseFill) {
9868
9884
  if (value >= 0) {
9869
9885
  return baseFill;
9870
9886
  }
9871
- const point = series.dataPoints?.find((p) => p.idx === pointIndex);
9872
- const invert = point?.invertIfNegative ?? series.invertIfNegative ?? false;
9887
+ const point = series.dataPoints?.find((p) => p.idx === pointIndex), invert = point?.invertIfNegative ?? series.invertIfNegative ?? false;
9873
9888
  return invert ? blendToWhite(baseFill) : baseFill;
9874
9889
  }
9875
9890
  /**
@@ -9878,40 +9893,25 @@ function invertNegativeFill(series, pointIndex, value, baseFill) {
9878
9893
  * labels. Mirrors React's `renderDefaultBarChart` / `renderStackedBarChart`.
9879
9894
  */
9880
9895
  function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, secondaryIdx, grouping, sourceIndices) {
9881
- const primitives = [];
9882
- const dataLabels = [];
9883
- const series = chartData.series;
9884
- const palette = chartData.colorPalette;
9885
- const showLabels = chartData.style?.hasDataLabels;
9896
+ const primitives = [], dataLabels = [], series = chartData.series, palette = chartData.colorPalette, showLabels = chartData.style?.hasDataLabels,
9886
9897
  // Single-series bar/column with c:varyColors=1 gives every category a distinct
9887
9898
  // palette colour (a per-point c:dPt fill still wins). Multi-series charts keep
9888
9899
  // their per-series colours (varyColors has no cross-series meaning there).
9889
- const varyColorsSingle = chartData.varyColors === true && series.length === 1;
9900
+ varyColorsSingle = chartData.varyColors === true && series.length === 1;
9890
9901
  if (grouping === 'clustered') {
9891
- const seriesCount = Math.max(series.length, 1);
9892
- const barGroupWidth = layout.plotWidth / Math.max(catCount, 1);
9902
+ const seriesCount = Math.max(series.length, 1), barGroupWidth = layout.plotWidth / Math.max(catCount, 1),
9893
9903
  // Honour c:gapWidth (gap between clusters, % of a bar width) when parsed;
9894
9904
  // otherwise keep the legacy 0.7-of-group heuristic byte-for-byte.
9895
- const singleBarWidth = chartData.barGapWidth !== undefined
9905
+ singleBarWidth = chartData.barGapWidth !== undefined
9896
9906
  ? barGroupWidth / (seriesCount + Math.max(chartData.barGapWidth, 0) / 100)
9897
- : (barGroupWidth * 0.7) / seriesCount;
9907
+ : (barGroupWidth * 0.7) / seriesCount,
9898
9908
  // Honour c:overlap (% overlap between adjacent series). overlap=0 reproduces
9899
9909
  // the original side-by-side layout exactly.
9900
- const overlap = chartData.barOverlap ?? 0;
9901
- const step = singleBarWidth * (1 - overlap / 100);
9902
- const clusterWidth = singleBarWidth + step * (seriesCount - 1);
9903
- const groupOffset = (barGroupWidth - clusterWidth) / 2;
9910
+ overlap = chartData.barOverlap ?? 0, step = singleBarWidth * (1 - overlap / 100), clusterWidth = singleBarWidth + step * (seriesCount - 1), groupOffset = (barGroupWidth - clusterWidth) / 2;
9904
9911
  for (let displayIndex = 0; displayIndex < catCount; displayIndex++) {
9905
9912
  const sourceIndex = sourceIndices[displayIndex] ?? displayIndex;
9906
9913
  for (let si = 0; si < series.length; si++) {
9907
- const val = series[si].values[sourceIndex] ?? 0;
9908
- const x = layout.plotLeft + barGroupWidth * displayIndex + groupOffset + step * si;
9909
- const activeRange = secondaryIdx.has(si) && secondaryRange ? secondaryRange : primaryRange;
9910
- const zeroY = valueToY(0, activeRange, layout.plotTop, layout.plotBottom);
9911
- const valY = valueToY(val, activeRange, layout.plotTop, layout.plotBottom);
9912
- const y = Math.min(zeroY, valY);
9913
- const h = Math.max(Math.abs(zeroY - valY), 1);
9914
- const baseFill = varyColorsSingle
9914
+ const val = series[si].values[sourceIndex] ?? 0, x = layout.plotLeft + barGroupWidth * displayIndex + groupOffset + step * si, activeRange = secondaryIdx.has(si) && secondaryRange ? secondaryRange : primaryRange, zeroY = valueToY(0, activeRange, layout.plotTop, layout.plotBottom), valY = valueToY(val, activeRange, layout.plotTop, layout.plotBottom), y = Math.min(zeroY, valY), h = Math.max(Math.abs(zeroY - valY), 1), baseFill = varyColorsSingle
9915
9915
  ? resolveVaryColorFill(series[si], sourceIndex, paletteColor(sourceIndex, palette))
9916
9916
  : (resolveDataPointFill(series[si], sourceIndex, paletteColor(si, palette)) ??
9917
9917
  seriesColor(series[si], si, palette));
@@ -9924,6 +9924,7 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
9924
9924
  fill: invertNegativeFill(series[si], sourceIndex, val, baseFill),
9925
9925
  rx: 1,
9926
9926
  part: { role: 'dataPoint', seriesIndex: si, pointIndex: sourceIndex },
9927
+ title: buildMarkTooltip(series[si].name, chartData.categories[sourceIndex], val, series[si].numberFormat),
9927
9928
  });
9928
9929
  if (showLabels) {
9929
9930
  // c:showVal / c:showCatName / c:showPercent decide what the label
@@ -9958,19 +9959,19 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
9958
9959
  const displaySeries = series.map((entry) => ({
9959
9960
  ...entry,
9960
9961
  values: sourceIndices.map((sourceIndex) => entry.values[sourceIndex] ?? 0),
9961
- }));
9962
- const rects = computeStackedBarRects(displaySeries, catCount, layout, primaryRange, palette);
9962
+ })), rects = computeStackedBarRects(displaySeries, catCount, layout, primaryRange, palette);
9963
9963
  for (const r of rects) {
9964
- let fill = r.fill;
9965
- let part;
9964
+ let fill = r.fill, part, title;
9966
9965
  if (r.seriesIndex !== undefined && r.pointIndex !== undefined) {
9967
9966
  const sourcePointIndex = sourceIndices[r.pointIndex] ?? r.pointIndex;
9968
9967
  fill = resolveDataPointFill(series[r.seriesIndex], sourcePointIndex, r.fill) ?? r.fill;
9968
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
9969
9969
  const value = series[r.seriesIndex].values[sourcePointIndex] ?? 0;
9970
9970
  fill = invertNegativeFill(series[r.seriesIndex], sourcePointIndex, value, fill);
9971
9971
  part = { role: 'dataPoint', seriesIndex: r.seriesIndex, pointIndex: sourcePointIndex };
9972
+ title = buildMarkTooltip(series[r.seriesIndex].name, chartData.categories[sourcePointIndex], value, series[r.seriesIndex].numberFormat);
9972
9973
  }
9973
- primitives.push({ kind: 'rect', x: r.x, y: r.y, w: r.w, h: r.h, fill, rx: 1, part });
9974
+ primitives.push({ kind: 'rect', x: r.x, y: r.y, w: r.w, h: r.h, fill, rx: 1, part, title });
9974
9975
  }
9975
9976
  if (showLabels) {
9976
9977
  pushClusteredStackedLabels(chartData, series, sourceIndices, catCount, layout, primaryRange, dataLabels);
@@ -9978,31 +9979,16 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
9978
9979
  return { primitives, dataLabels };
9979
9980
  }
9980
9981
  // percentStacked: normalise each category to 100% with in-bar percent labels.
9981
- const barGroupWidth = layout.plotWidth / Math.max(catCount, 1);
9982
- const barW = barGroupWidth * 0.6;
9983
- const barOffset = (barGroupWidth - barW) / 2;
9984
- const displaySeries = series.map((entry) => ({
9982
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
9983
+ const barGroupWidth = layout.plotWidth / Math.max(catCount, 1), barW = barGroupWidth * 0.6, barOffset = (barGroupWidth - barW) / 2, displaySeries = series.map((entry) => ({
9985
9984
  ...entry,
9986
9985
  values: sourceIndices.map((sourceIndex) => entry.values[sourceIndex] ?? 0),
9987
- }));
9988
- const totals = categoryTotals$1(displaySeries, catCount);
9986
+ })), totals = categoryTotals$1(displaySeries, catCount);
9989
9987
  for (let ci = 0; ci < catCount; ci++) {
9990
- let posRunning = 0;
9991
- let negRunning = 0;
9988
+ let posRunning = 0, negRunning = 0;
9992
9989
  const catTotal = totals[ci] || 1;
9993
9990
  for (let si = 0; si < series.length; si++) {
9994
- const sourceIndex = sourceIndices[ci] ?? ci;
9995
- const rawVal = series[si].values[sourceIndex] ?? 0;
9996
- const val = catTotal > 0 ? (rawVal / catTotal) * 100 : 0;
9997
- const isNeg = val < 0;
9998
- const base = isNeg ? negRunning : posRunning;
9999
- const top = base + val;
10000
- const x = layout.plotLeft + barGroupWidth * ci + barOffset;
10001
- const baseY = valueToY(base, primaryRange, layout.plotTop, layout.plotBottom);
10002
- const topY = valueToY(top, primaryRange, layout.plotTop, layout.plotBottom);
10003
- const y = Math.min(baseY, topY);
10004
- const h = Math.max(Math.abs(baseY - topY), 0.5);
10005
- const pctBaseFill = resolveDataPointFill(series[si], sourceIndex, paletteColor(si, palette)) ??
9991
+ const sourceIndex = sourceIndices[ci] ?? ci, rawVal = series[si].values[sourceIndex] ?? 0, val = catTotal > 0 ? (rawVal / catTotal) * 100 : 0, isNeg = val < 0, base = isNeg ? negRunning : posRunning, top = base + val, x = layout.plotLeft + barGroupWidth * ci + barOffset, baseY = valueToY(base, primaryRange, layout.plotTop, layout.plotBottom), topY = valueToY(top, primaryRange, layout.plotTop, layout.plotBottom), y = Math.min(baseY, topY), h = Math.max(Math.abs(baseY - topY), 0.5), pctBaseFill = resolveDataPointFill(series[si], sourceIndex, paletteColor(si, palette)) ??
10006
9992
  seriesColor(series[si], si, palette);
10007
9993
  primitives.push({
10008
9994
  kind: 'rect',
@@ -10012,6 +9998,7 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
10012
9998
  h,
10013
9999
  fill: invertNegativeFill(series[si], sourceIndex, rawVal, pctBaseFill),
10014
10000
  part: { role: 'dataPoint', seriesIndex: si, pointIndex: sourceIndex },
10001
+ title: buildMarkTooltip(series[si].name, chartData.categories[sourceIndex], rawVal, series[si].numberFormat),
10015
10002
  });
10016
10003
  if (showLabels && Math.abs(val) > 0) {
10017
10004
  dataLabels.push({
@@ -10042,23 +10029,15 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
10042
10029
  * reproduces that exact output for byte-identity.
10043
10030
  */
10044
10031
  function pushClusteredStackedLabels(chartData, series, sourceIndices, catCount, layout, range, dataLabels) {
10045
- const barGroupWidth = layout.plotWidth / catCount;
10046
- const seriesCount = Math.max(series.length, 1);
10047
- const singleBarWidth = (barGroupWidth * 0.7) / seriesCount;
10048
- const groupOffset = (barGroupWidth - singleBarWidth * seriesCount) / 2;
10032
+ const barGroupWidth = layout.plotWidth / catCount, seriesCount = Math.max(series.length, 1), singleBarWidth = (barGroupWidth * 0.7) / seriesCount, groupOffset = (barGroupWidth - singleBarWidth * seriesCount) / 2;
10049
10033
  for (let ci = 0; ci < catCount; ci++) {
10050
10034
  const sourceIndex = sourceIndices[ci] ?? ci;
10051
10035
  for (let si = 0; si < series.length; si++) {
10052
- const val = series[si].values[sourceIndex] ?? 0;
10053
- const x = layout.plotLeft +
10036
+ const val = series[si].values[sourceIndex] ?? 0, x = layout.plotLeft +
10054
10037
  barGroupWidth * ci +
10055
10038
  groupOffset +
10056
10039
  singleBarWidth * si +
10057
- singleBarWidth / 2;
10058
- const zeroY = valueToY(0, range, layout.plotTop, layout.plotBottom);
10059
- const valY = valueToY(val, range, layout.plotTop, layout.plotBottom);
10060
- const labelY = val >= 0 ? Math.min(zeroY, valY) - 4 : Math.max(zeroY, valY) + 10;
10061
- const text = buildDataLabelText({
10040
+ singleBarWidth / 2, zeroY = valueToY(0, range, layout.plotTop, layout.plotBottom), valY = valueToY(val, range, layout.plotTop, layout.plotBottom), labelY = val >= 0 ? Math.min(zeroY, valY) - 4 : Math.max(zeroY, valY) + 10, text = buildDataLabelText({
10062
10041
  chartData,
10063
10042
  series: series[si],
10064
10043
  pointIndex: sourceIndex,
@@ -10320,6 +10299,16 @@ function buildMarkerPrimitive(input) {
10320
10299
  }
10321
10300
  }
10322
10301
 
10302
+ /**
10303
+ * Hover-tooltip text for an XY (scatter/bubble) point: `"<series>: (x, y)"`.
10304
+ * Neither kind has a category label to hang the tooltip off, unlike
10305
+ * `buildMarkTooltip`'s bar/line/area/pie/radar marks, so this formats the raw
10306
+ * coordinate pair instead.
10307
+ */
10308
+ function xyMarkTooltip(seriesName, xVal, yVal, numberFormat) {
10309
+ const y = formatAxisValue(yVal, numberFormat), coords = xVal !== undefined ? `(${formatAxisValue(xVal)}, ${y})` : y;
10310
+ return seriesName.length > 0 ? `${seriesName}: ${coords}` : coords;
10311
+ }
10323
10312
  /**
10324
10313
  * Build the marker for a data point (honouring `marker.symbol`/`size`) and push
10325
10314
  * it, unless the symbol is `none`. Shared by the line / area / scatter builders.
@@ -10329,9 +10318,8 @@ function buildMarkerPrimitive(input) {
10329
10318
  * fewer points than the series declares and the override must still land on the
10330
10319
  * point the author picked.
10331
10320
  */
10332
- function pushMarker(out, series, pointIndex, cx, cy, fill, defaultRadius, part, opacity) {
10333
- const marker = resolveDataPointMarker(series, pointIndex);
10334
- const m = buildMarkerPrimitive({
10321
+ function pushMarker(out, series, pointIndex, cx, cy, fill, defaultRadius, part, opacity, title) {
10322
+ const marker = resolveDataPointMarker(series, pointIndex), m = buildMarkerPrimitive({
10335
10323
  symbol: marker.symbol,
10336
10324
  size: marker.size,
10337
10325
  cx,
@@ -10348,32 +10336,236 @@ function pushMarker(out, series, pointIndex, cx, cy, fill, defaultRadius, part,
10348
10336
  if (opacity !== undefined) {
10349
10337
  m.opacity = opacity;
10350
10338
  }
10339
+ if (title !== undefined) {
10340
+ m.title = title;
10341
+ }
10351
10342
  out.push(m);
10352
10343
  }
10353
- /** Build line-chart primitives, honouring a secondary value range per series. */
10354
- function buildLines(chartData, catCount, layout, primaryRange, secondaryRange, secondaryIdx, sourceIndices, xPositions) {
10355
- const primitives = [];
10356
- const dataLabels = [];
10357
- const showLabels = chartData.style?.hasDataLabels;
10344
+ /**
10345
+ * X values a scatter / bubble series is plotted against.
10346
+ *
10347
+ * The series' own `c:xVal` wins. Only when it has none does the chart-level
10348
+ * category list stand in, which is all the engine used to have and is why every
10349
+ * series in a multi-series scatter was plotted against series 1's x axis.
10350
+ */
10351
+ function seriesXValues(chartData, series) {
10352
+ if (series.xValues && series.xValues.length > 0) {
10353
+ return series.xValues;
10354
+ }
10355
+ const fromCategories = chartData.categories.map(Number);
10356
+ return fromCategories.length > 0 ? fromCategories : undefined;
10357
+ }
10358
+ /**
10359
+ * Whether `c:scatterStyle` joins the points with a line.
10360
+ *
10361
+ * `lineMarker` is what PowerPoint writes for essentially every scatter chart,
10362
+ * including the marker-only ones: it expresses "no line" as an `a:ln/a:noFill`
10363
+ * on the series, not by switching the style to `marker`. Both have to be
10364
+ * checked, and the series flag has to win, or a "Scatter with Straight Lines"
10365
+ * deck loses its lines and a marker-only deck grows some.
10366
+ */
10367
+ function scatterDrawsLine(chartData, series) {
10368
+ if (series.lineNoFill === true) {
10369
+ return false;
10370
+ }
10371
+ const style = chartData.scatterStyle;
10372
+ return (style === 'line' || style === 'lineMarker' || style === 'smooth' || style === 'smoothMarker');
10373
+ }
10374
+ /** Build scatter-chart primitives, honouring `c:scatterStyle` connecting lines. */
10375
+ function buildScatter(chartData, layout, range) {
10376
+ const primitives = [], dataLabels = [], showLabels = chartData.style?.hasDataLabels, allIndices = chartData.series.flatMap((s) => s.values.map((_, i) => i)), maxXIndex = Math.max(1, ...allIndices), perSeriesX = chartData.series.map((series) => seriesXValues(chartData, series)), xDomain = computeScatterXDomain(perSeriesX), smoothStyle = chartData.scatterStyle === 'smooth' || chartData.scatterStyle === 'smoothMarker';
10377
+ for (let si = 0; si < chartData.series.length; si++) {
10378
+ const series = chartData.series[si], c = seriesColor(series, si, chartData.colorPalette), dots = computeScatterDots(series.values, maxXIndex, layout, range, perSeriesX[si], xDomain);
10379
+ // The connecting line goes down FIRST so the markers sit on top of it.
10380
+ if (scatterDrawsLine(chartData, series) && dots.length >= 2) {
10381
+ const points = dots.map((dot) => ({ x: dot.cx, y: dot.cy })), seriesPart = { role: 'series', seriesIndex: si };
10382
+ primitives.push((series.smooth ?? smoothStyle)
10383
+ ? {
10384
+ kind: 'path',
10385
+ d: smoothLinePath(points),
10386
+ stroke: c,
10387
+ strokeWidth: 2.4,
10388
+ fill: 'none',
10389
+ part: seriesPart,
10390
+ }
10391
+ : {
10392
+ kind: 'polyline',
10393
+ points: linePointsToSvgString(points),
10394
+ stroke: c,
10395
+ strokeWidth: 2.4,
10396
+ fill: 'none',
10397
+ part: seriesPart,
10398
+ });
10399
+ }
10400
+ dots.forEach((dot, vi) => {
10401
+ const part = { role: 'dataPoint', seriesIndex: si, pointIndex: vi };
10402
+ pushMarker(primitives, series, vi, dot.cx, dot.cy, resolveDataPointFill(series, vi, c) ?? c, 4, part, 0.85, xyMarkTooltip(series.name, perSeriesX[si]?.[vi], series.values[vi] ?? 0, series.numberFormat));
10403
+ });
10404
+ if (showLabels) {
10405
+ series.values.forEach((val, vi) => {
10406
+ const dot = dots[vi], text = buildDataLabelText({ chartData, series, pointIndex: vi, value: val });
10407
+ if (!dot || text === undefined) {
10408
+ return;
10409
+ }
10410
+ dataLabels.push({
10411
+ kind: 'text',
10412
+ x: dot.cx,
10413
+ y: dot.cy - 6,
10414
+ text,
10415
+ fontSize: DEFAULT_CHART_DATA_LABEL_PX,
10416
+ fill: '#334155',
10417
+ textAnchor: 'middle',
10418
+ });
10419
+ });
10420
+ }
10421
+ }
10422
+ return { primitives, dataLabels };
10423
+ }
10424
+ /**
10425
+ * Build bubble-chart primitives.
10426
+ *
10427
+ * Every series carries its own `c:xVal`, `c:yVal` AND `c:bubbleSize`, so each
10428
+ * one is a complete bubble series. The engine used to read sizes off "the third
10429
+ * series", which drew equal-sized dots for the ordinary one-series bubble chart
10430
+ * and silently deleted every series past the second from a three-series one.
10431
+ */
10432
+ function buildBubbles(chartData, layout, range) {
10433
+ const primitives = [], dataLabels = [], showLabels = chartData.style?.hasDataLabels, allIndices = chartData.series.flatMap((s) => s.values.map((_, i) => i)), maxXIndex = Math.max(1, ...allIndices), perSeriesX = chartData.series.map((series) => seriesXValues(chartData, series)), xDomain = computeScatterXDomain(perSeriesX),
10434
+ // One size scale for the whole chart, so bubbles stay comparable across series.
10435
+ allSizes = chartData.series.flatMap((series) => series.bubbleSizes ?? []), maxBubble = allSizes.length > 0 ? Math.max(1, ...allSizes.map(Math.abs)) : 1, medianRadius = Math.min(layout.plotWidth, layout.plotHeight) * 0.04;
10358
10436
  for (let si = 0; si < chartData.series.length; si++) {
10359
- const series = chartData.series[si];
10437
+ const series = chartData.series[si], c = seriesColor(series, si, chartData.colorPalette), dots = computeScatterDots(series.values, maxXIndex, layout, range, perSeriesX[si], xDomain);
10438
+ dots.forEach((dot, vi) => {
10439
+ const r = computeBubbleRadius(series.bubbleSizes?.[vi], maxBubble, medianRadius), size = series.bubbleSizes?.[vi], label = size !== undefined
10440
+ ? `${xyMarkTooltip(series.name, perSeriesX[si]?.[vi], series.values[vi] ?? 0, series.numberFormat)}, size ${formatAxisValue(size)}`
10441
+ : xyMarkTooltip(series.name, perSeriesX[si]?.[vi], series.values[vi] ?? 0, series.numberFormat);
10442
+ primitives.push({
10443
+ kind: 'circle',
10444
+ cx: dot.cx,
10445
+ cy: dot.cy,
10446
+ r,
10447
+ fill: resolveDataPointFill(series, vi, c) ?? c,
10448
+ opacity: 0.6,
10449
+ part: { role: 'dataPoint', seriesIndex: si, pointIndex: vi },
10450
+ title: label,
10451
+ });
10452
+ });
10453
+ if (showLabels) {
10454
+ series.values.forEach((val, vi) => {
10455
+ const dot = dots[vi], text = buildDataLabelText({ chartData, series, pointIndex: vi, value: val });
10456
+ if (!dot || text === undefined) {
10457
+ return;
10458
+ }
10459
+ dataLabels.push({
10460
+ kind: 'text',
10461
+ x: dot.cx,
10462
+ y: dot.cy - 10,
10463
+ text,
10464
+ fontSize: DEFAULT_CHART_DATA_LABEL_PX,
10465
+ fill: '#334155',
10466
+ textAnchor: 'middle',
10467
+ });
10468
+ });
10469
+ }
10470
+ }
10471
+ return { primitives, dataLabels };
10472
+ }
10473
+
10474
+ /**
10475
+ * chart-stacked-series.ts: running-sum plot geometry for stacked and
10476
+ * percentStacked LINE / AREA charts.
10477
+ *
10478
+ * Bar/column stacking (`computeStackedBarRects` in chart-view-model.ts) draws
10479
+ * one rect per segment stacked on a shared baseline. Line/area stacking is
10480
+ * different: each series is plotted at its cumulative height, so the
10481
+ * top-most series traces the category total (line) or the topmost band edge
10482
+ * (area). This module owns that running-sum math, shared by `buildLines` and
10483
+ * `buildAreas` in chart-cartesian-line-area.ts so the two kinds cannot drift.
10484
+ *
10485
+ * @module chart-stacked-series
10486
+ */
10487
+ /**
10488
+ * Per-category sum of absolute values across all series, used to normalise
10489
+ * percentStacked shares. Mirrors `categoryTotals` in chart-cartesian-bars.ts
10490
+ * (bar's percentStacked normalisation) so bar/line/area agree on what "100%"
10491
+ * means for a category.
10492
+ */
10493
+ function categoryAbsTotals(seriesValues, catCount) {
10494
+ return Array.from({ length: catCount }, (_, ci) => seriesValues.reduce((sum, values) => sum + Math.abs(values[ci] ?? 0), 0));
10495
+ }
10496
+ /**
10497
+ * Build running-sum stacked/percentStacked plot values for a set of line or
10498
+ * area series, one entry per series in `seriesValues` order (series 0 sits at
10499
+ * the bottom of the stack, exactly like stacked bar segments). Positive and
10500
+ * negative values accumulate on separate running sums, so a chart mixing
10501
+ * signs stacks above and below zero independently instead of the values
10502
+ * cancelling out.
10503
+ */
10504
+ function computeStackedSeriesPlots(seriesValues, catCount, percent) {
10505
+ const totals = percent ? categoryAbsTotals(seriesValues, catCount) : undefined, posRunning = new Array(catCount).fill(0), negRunning = new Array(catCount).fill(0);
10506
+ return seriesValues.map((values) => {
10507
+ const cumulative = [], base = [], own = [];
10508
+ for (let ci = 0; ci < catCount; ci++) {
10509
+ const raw = values[ci] ?? 0, total = totals?.[ci] ?? 0, val = percent ? (total > 0 ? (raw / total) * 100 : 0) : raw, isNeg = val < 0, from = isNeg ? negRunning[ci] : posRunning[ci], to = from + val;
10510
+ base.push(from);
10511
+ cumulative.push(to);
10512
+ own.push(val);
10513
+ if (isNeg) {
10514
+ negRunning[ci] = to;
10515
+ }
10516
+ else {
10517
+ posRunning[ci] = to;
10518
+ }
10519
+ }
10520
+ return { cumulative, base, own };
10521
+ });
10522
+ }
10523
+
10524
+ /**
10525
+ * Build line-chart primitives, honouring a secondary value range per series
10526
+ * (clustered only: `stacking !== 'clustered'` always plots against
10527
+ * `primaryRange`, matching `isStacked` disabling the secondary-axis split in
10528
+ * chart-cartesian.ts).
10529
+ *
10530
+ * Stacked/percentStacked plots each series at its running-sum height (each
10531
+ * line sits on top of the ones below it, so the top-most line traces the
10532
+ * category total), computed from every series' own blank-resolved values via
10533
+ * `computeStackedSeriesPlots`. Data labels and marker tooltips still read the
10534
+ * series' own value (or percent share), not the cumulative height it is
10535
+ * plotted at, mirroring stacked bar's label convention.
10536
+ */
10537
+ function buildLines(chartData, catCount, layout, primaryRange, secondaryRange, secondaryIdx, sourceIndices, xPositions, stacking = 'clustered') {
10538
+ const primitives = [], dataLabels = [], showLabels = chartData.style?.hasDataLabels, isStackedMode = stacking !== 'clustered', isPercent = stacking === 'percentStacked',
10539
+ // Resolve every series' own (blank-handled) values first: stacking needs
10540
+ // all of them at once to compute the running sums.
10541
+ resolved = chartData.series.map((series) => {
10360
10542
  if (series.values.length === 0) {
10361
- continue;
10543
+ return undefined;
10362
10544
  }
10363
- const activeRange = secondaryIdx.has(si) && secondaryRange ? secondaryRange : primaryRange;
10364
- const rawValues = sourceIndices.map((sourceIndex) => series.values[sourceIndex] ?? 0);
10365
- const displayBlanks = sourceIndices.map((sourceIndex) => series.blanks?.[sourceIndex] ?? false);
10545
+ const rawValues = sourceIndices.map((sourceIndex) => series.values[sourceIndex] ?? 0), displayBlanks = sourceIndices.map((sourceIndex) => series.blanks?.[sourceIndex] ?? false),
10366
10546
  // Honour c:dispBlanksAs: gap breaks the line at blanks, span interpolates,
10367
10547
  // zero/unset keep the placeholder 0 (existing behaviour).
10368
- const { values: displayValues, visible } = resolveBlankDisplay(rawValues, displayBlanks, chartData.chartChrome?.dispBlanksAs);
10369
- const pts = computeLinePoints(displayValues, catCount, layout, activeRange).map((point, index) => ({
10548
+ { values: displayValues, visible } = resolveBlankDisplay(rawValues, displayBlanks, chartData.chartChrome?.dispBlanksAs);
10549
+ return { displayValues, visible };
10550
+ }), stackedPlots = isStackedMode
10551
+ ? computeStackedSeriesPlots(resolved.map((entry) => entry?.displayValues ?? []), catCount, isPercent)
10552
+ : undefined;
10553
+ for (let si = 0; si < chartData.series.length; si++) {
10554
+ const series = chartData.series[si], entry = resolved[si];
10555
+ if (!entry) {
10556
+ continue;
10557
+ }
10558
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
10559
+ const { displayValues, visible } = entry, activeRange = isStackedMode
10560
+ ? primaryRange
10561
+ : secondaryIdx.has(si) && secondaryRange
10562
+ ? secondaryRange
10563
+ : primaryRange, plotValues = stackedPlots ? stackedPlots[si].cumulative : displayValues, pts = computeLinePoints(plotValues, catCount, layout, activeRange).map((point, index) => ({
10370
10564
  ...point,
10371
10565
  x: xPositions?.[index] ?? point.x,
10372
- }));
10373
- const c = seriesColor(series, si, chartData.colorPalette);
10566
+ })), c = seriesColor(series, si, chartData.colorPalette),
10374
10567
  // c:smooth draws a bezier path through the points; otherwise a polyline.
10375
- const seriesPart = { role: 'series', seriesIndex: si };
10376
- const allVisible = visible.every(Boolean);
10568
+ seriesPart = { role: 'series', seriesIndex: si }, allVisible = visible.every(Boolean);
10377
10569
  if (allVisible) {
10378
10570
  primitives.push(series.smooth
10379
10571
  ? {
@@ -10413,20 +10605,42 @@ function buildLines(chartData, catCount, layout, primaryRange, secondaryRange, s
10413
10605
  if (!visible[displayIndex]) {
10414
10606
  return;
10415
10607
  }
10416
- const idx = sourceIndices[displayIndex] ?? displayIndex;
10417
- const part = { role: 'dataPoint', seriesIndex: si, pointIndex: idx };
10418
- pushMarker(primitives, series, idx, pt.x, pt.y, resolveDataPointFill(series, idx, c) ?? c, 2.5, part);
10608
+ const idx = sourceIndices[displayIndex] ?? displayIndex, part = { role: 'dataPoint', seriesIndex: si, pointIndex: idx };
10609
+ pushMarker(primitives, series, idx, pt.x, pt.y, resolveDataPointFill(series, idx, c) ?? c, 2.5, part, undefined, buildMarkTooltip(series.name, chartData.categories[idx], series.values[idx] ?? 0, series.numberFormat));
10419
10610
  });
10420
10611
  if (showLabels) {
10421
- displayValues.forEach((val, displayIndex) => {
10612
+ // percentStacked reads like the bar case: a plain rounded percent, not
10613
+ // routed through c:dLbls content options (showValue/showCategory/...),
10614
+ // since the plotted quantity is a derived share, not the raw datum.
10615
+ const labelValues = stackedPlots ? stackedPlots[si].own : displayValues;
10616
+ labelValues.forEach((val, displayIndex) => {
10422
10617
  const pt = pts[displayIndex];
10618
+ if (!pt || !visible[displayIndex]) {
10619
+ return;
10620
+ }
10621
+ if (isPercent) {
10622
+ if (val === 0) {
10623
+ return;
10624
+ }
10625
+ dataLabels.push({
10626
+ kind: 'text',
10627
+ x: pt.x,
10628
+ y: pt.y - 7,
10629
+ text: `${Math.round(val)}%`,
10630
+ fontSize: DEFAULT_CHART_DATA_LABEL_PX,
10631
+ fill: '#334155',
10632
+ textAnchor: 'middle',
10633
+ });
10634
+ return;
10635
+ }
10636
+ // eslint-disable-next-line one-var -- an early return sits between this const and the previous one
10423
10637
  const text = buildDataLabelText({
10424
10638
  chartData,
10425
10639
  series,
10426
10640
  pointIndex: sourceIndices[displayIndex] ?? displayIndex,
10427
10641
  value: val,
10428
10642
  });
10429
- if (!pt || !visible[displayIndex] || text === undefined) {
10643
+ if (text === undefined) {
10430
10644
  return;
10431
10645
  }
10432
10646
  dataLabels.push({
@@ -10443,27 +10657,52 @@ function buildLines(chartData, catCount, layout, primaryRange, secondaryRange, s
10443
10657
  }
10444
10658
  return { primitives, dataLabels };
10445
10659
  }
10446
- /** Build area-chart primitives (fill polygon + outline). */
10447
- function buildAreas(chartData, catCount, layout, range, sourceIndices, xPositions) {
10448
- const primitives = [];
10449
- const dataLabels = [];
10450
- const showLabels = chartData.style?.hasDataLabels;
10451
- const baselineY = valueToY(0, range, layout.plotTop, layout.plotBottom);
10660
+ /**
10661
+ * Build area-chart primitives (fill polygon + outline).
10662
+ *
10663
+ * Stacked/percentStacked fills the band between this series' running-sum top
10664
+ * and the previous series' top (its own base) instead of down to the zero
10665
+ * baseline, so layers stack visually like a PowerPoint stacked area chart
10666
+ * rather than each series washing over the ones below it. Data labels and
10667
+ * marker tooltips read the series' own value (or percent share), matching
10668
+ * `buildLines` and stacked bar's label convention.
10669
+ */
10670
+ function buildAreas(chartData, catCount, layout, range, sourceIndices, xPositions, stacking = 'clustered') {
10671
+ const primitives = [], dataLabels = [], showLabels = chartData.style?.hasDataLabels, isStackedMode = stacking !== 'clustered', isPercent = stacking === 'percentStacked', baselineY = valueToY(0, range, layout.plotTop, layout.plotBottom), allDisplayValues = chartData.series.map((series) => series.values.length === 0
10672
+ ? undefined
10673
+ : sourceIndices.map((sourceIndex) => series.values[sourceIndex] ?? 0)), stackedPlots = isStackedMode
10674
+ ? computeStackedSeriesPlots(allDisplayValues.map((values) => values ?? []), catCount, isPercent)
10675
+ : undefined;
10452
10676
  for (let si = 0; si < chartData.series.length; si++) {
10453
- const series = chartData.series[si];
10454
- if (series.values.length === 0) {
10677
+ const series = chartData.series[si], displayValues = allDisplayValues[si];
10678
+ if (!displayValues) {
10455
10679
  continue;
10456
10680
  }
10457
- const displayValues = sourceIndices.map((sourceIndex) => series.values[sourceIndex] ?? 0);
10458
- const pts = computeLinePoints(displayValues, catCount, layout, range).map((point, index) => ({
10681
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
10682
+ const plot = stackedPlots?.[si], topValues = plot ? plot.cumulative : displayValues, pts = computeLinePoints(topValues, catCount, layout, range).map((point, index) => ({
10459
10683
  ...point,
10460
10684
  x: xPositions?.[index] ?? point.x,
10461
- }));
10462
- const c = seriesColor(series, si, chartData.colorPalette);
10463
- const lineStr = linePointsToSvgString(pts);
10464
- const firstPt = pts[0];
10465
- const lastPt = pts[pts.length - 1];
10466
- if (firstPt && lastPt) {
10685
+ })), c = seriesColor(series, si, chartData.colorPalette), lineStr = linePointsToSvgString(pts), firstPt = pts[0], lastPt = pts[pts.length - 1];
10686
+ if (plot) {
10687
+ // Stacked band: fill between this series' cumulative top and its own
10688
+ // base (the previous series' top), like a stream-graph layer, at full
10689
+ // opacity so adjacent bands read as distinct rather than washed.
10690
+ const baseStr = linePointsToSvgString([
10691
+ ...computeLinePoints(plot.base, catCount, layout, range).map((point, index) => ({
10692
+ ...point,
10693
+ x: xPositions?.[index] ?? point.x,
10694
+ })),
10695
+ ].reverse());
10696
+ primitives.push({
10697
+ kind: 'polyline',
10698
+ points: `${lineStr} ${baseStr}`,
10699
+ stroke: 'none',
10700
+ strokeWidth: 0,
10701
+ fill: c,
10702
+ part: { role: 'series', seriesIndex: si },
10703
+ });
10704
+ }
10705
+ else if (firstPt && lastPt) {
10467
10706
  primitives.push({
10468
10707
  kind: 'polyline',
10469
10708
  points: `${firstPt.x.toFixed(2)},${baselineY.toFixed(2)} ${lineStr} ${lastPt.x.toFixed(2)},${baselineY.toFixed(2)}`,
@@ -10483,20 +10722,39 @@ function buildAreas(chartData, catCount, layout, range, sourceIndices, xPosition
10483
10722
  part: { role: 'series', seriesIndex: si },
10484
10723
  });
10485
10724
  pts.forEach((pt, displayIndex) => {
10486
- const idx = sourceIndices[displayIndex] ?? displayIndex;
10487
- const part = { role: 'dataPoint', seriesIndex: si, pointIndex: idx };
10488
- pushMarker(primitives, series, idx, pt.x, pt.y, resolveDataPointFill(series, idx, c) ?? c, 2, part);
10725
+ const idx = sourceIndices[displayIndex] ?? displayIndex, part = { role: 'dataPoint', seriesIndex: si, pointIndex: idx };
10726
+ pushMarker(primitives, series, idx, pt.x, pt.y, resolveDataPointFill(series, idx, c) ?? c, 2, part, undefined, buildMarkTooltip(series.name, chartData.categories[idx], series.values[idx] ?? 0, series.numberFormat));
10489
10727
  });
10490
10728
  if (showLabels) {
10491
- displayValues.forEach((val, displayIndex) => {
10729
+ const labelValues = plot ? plot.own : displayValues;
10730
+ labelValues.forEach((val, displayIndex) => {
10492
10731
  const pt = pts[displayIndex];
10732
+ if (!pt) {
10733
+ return;
10734
+ }
10735
+ if (isPercent) {
10736
+ if (val === 0) {
10737
+ return;
10738
+ }
10739
+ dataLabels.push({
10740
+ kind: 'text',
10741
+ x: pt.x,
10742
+ y: pt.y - 6,
10743
+ text: `${Math.round(val)}%`,
10744
+ fontSize: DEFAULT_CHART_DATA_LABEL_PX,
10745
+ fill: '#334155',
10746
+ textAnchor: 'middle',
10747
+ });
10748
+ return;
10749
+ }
10750
+ // eslint-disable-next-line one-var -- an early return sits between this const and the previous one
10493
10751
  const text = buildDataLabelText({
10494
10752
  chartData,
10495
10753
  series,
10496
10754
  pointIndex: sourceIndices[displayIndex] ?? displayIndex,
10497
10755
  value: val,
10498
10756
  });
10499
- if (!pt || text === undefined) {
10757
+ if (text === undefined) {
10500
10758
  return;
10501
10759
  }
10502
10760
  dataLabels.push({
@@ -10513,154 +10771,6 @@ function buildAreas(chartData, catCount, layout, range, sourceIndices, xPosition
10513
10771
  }
10514
10772
  return { primitives, dataLabels };
10515
10773
  }
10516
- /**
10517
- * X values a scatter / bubble series is plotted against.
10518
- *
10519
- * The series' own `c:xVal` wins. Only when it has none does the chart-level
10520
- * category list stand in, which is all the engine used to have and is why every
10521
- * series in a multi-series scatter was plotted against series 1's x axis.
10522
- */
10523
- function seriesXValues(chartData, series) {
10524
- if (series.xValues && series.xValues.length > 0) {
10525
- return series.xValues;
10526
- }
10527
- const fromCategories = chartData.categories.map(Number);
10528
- return fromCategories.length > 0 ? fromCategories : undefined;
10529
- }
10530
- /**
10531
- * Whether `c:scatterStyle` joins the points with a line.
10532
- *
10533
- * `lineMarker` is what PowerPoint writes for essentially every scatter chart,
10534
- * including the marker-only ones: it expresses "no line" as an `a:ln/a:noFill`
10535
- * on the series, not by switching the style to `marker`. Both have to be
10536
- * checked, and the series flag has to win, or a "Scatter with Straight Lines"
10537
- * deck loses its lines and a marker-only deck grows some.
10538
- */
10539
- function scatterDrawsLine(chartData, series) {
10540
- if (series.lineNoFill === true) {
10541
- return false;
10542
- }
10543
- const style = chartData.scatterStyle;
10544
- return (style === 'line' || style === 'lineMarker' || style === 'smooth' || style === 'smoothMarker');
10545
- }
10546
- /** Build scatter-chart primitives, honouring `c:scatterStyle` connecting lines. */
10547
- function buildScatter(chartData, layout, range) {
10548
- const primitives = [];
10549
- const dataLabels = [];
10550
- const showLabels = chartData.style?.hasDataLabels;
10551
- const allIndices = chartData.series.flatMap((s) => s.values.map((_, i) => i));
10552
- const maxXIndex = Math.max(1, ...allIndices);
10553
- const perSeriesX = chartData.series.map((series) => seriesXValues(chartData, series));
10554
- const xDomain = computeScatterXDomain(perSeriesX);
10555
- const smoothStyle = chartData.scatterStyle === 'smooth' || chartData.scatterStyle === 'smoothMarker';
10556
- for (let si = 0; si < chartData.series.length; si++) {
10557
- const series = chartData.series[si];
10558
- const c = seriesColor(series, si, chartData.colorPalette);
10559
- const dots = computeScatterDots(series.values, maxXIndex, layout, range, perSeriesX[si], xDomain);
10560
- // The connecting line goes down FIRST so the markers sit on top of it.
10561
- if (scatterDrawsLine(chartData, series) && dots.length >= 2) {
10562
- const points = dots.map((dot) => ({ x: dot.cx, y: dot.cy }));
10563
- const seriesPart = { role: 'series', seriesIndex: si };
10564
- primitives.push((series.smooth ?? smoothStyle)
10565
- ? {
10566
- kind: 'path',
10567
- d: smoothLinePath(points),
10568
- stroke: c,
10569
- strokeWidth: 2.4,
10570
- fill: 'none',
10571
- part: seriesPart,
10572
- }
10573
- : {
10574
- kind: 'polyline',
10575
- points: linePointsToSvgString(points),
10576
- stroke: c,
10577
- strokeWidth: 2.4,
10578
- fill: 'none',
10579
- part: seriesPart,
10580
- });
10581
- }
10582
- dots.forEach((dot, vi) => {
10583
- const part = { role: 'dataPoint', seriesIndex: si, pointIndex: vi };
10584
- pushMarker(primitives, series, vi, dot.cx, dot.cy, resolveDataPointFill(series, vi, c) ?? c, 4, part, 0.85);
10585
- });
10586
- if (showLabels) {
10587
- series.values.forEach((val, vi) => {
10588
- const dot = dots[vi];
10589
- const text = buildDataLabelText({ chartData, series, pointIndex: vi, value: val });
10590
- if (!dot || text === undefined) {
10591
- return;
10592
- }
10593
- dataLabels.push({
10594
- kind: 'text',
10595
- x: dot.cx,
10596
- y: dot.cy - 6,
10597
- text,
10598
- fontSize: DEFAULT_CHART_DATA_LABEL_PX,
10599
- fill: '#334155',
10600
- textAnchor: 'middle',
10601
- });
10602
- });
10603
- }
10604
- }
10605
- return { primitives, dataLabels };
10606
- }
10607
- /**
10608
- * Build bubble-chart primitives.
10609
- *
10610
- * Every series carries its own `c:xVal`, `c:yVal` AND `c:bubbleSize`, so each
10611
- * one is a complete bubble series. The engine used to read sizes off "the third
10612
- * series", which drew equal-sized dots for the ordinary one-series bubble chart
10613
- * and silently deleted every series past the second from a three-series one.
10614
- */
10615
- function buildBubbles(chartData, layout, range) {
10616
- const primitives = [];
10617
- const dataLabels = [];
10618
- const showLabels = chartData.style?.hasDataLabels;
10619
- const allIndices = chartData.series.flatMap((s) => s.values.map((_, i) => i));
10620
- const maxXIndex = Math.max(1, ...allIndices);
10621
- const perSeriesX = chartData.series.map((series) => seriesXValues(chartData, series));
10622
- const xDomain = computeScatterXDomain(perSeriesX);
10623
- // One size scale for the whole chart, so bubbles stay comparable across series.
10624
- const allSizes = chartData.series.flatMap((series) => series.bubbleSizes ?? []);
10625
- const maxBubble = allSizes.length > 0 ? Math.max(1, ...allSizes.map(Math.abs)) : 1;
10626
- const medianRadius = Math.min(layout.plotWidth, layout.plotHeight) * 0.04;
10627
- for (let si = 0; si < chartData.series.length; si++) {
10628
- const series = chartData.series[si];
10629
- const c = seriesColor(series, si, chartData.colorPalette);
10630
- const dots = computeScatterDots(series.values, maxXIndex, layout, range, perSeriesX[si], xDomain);
10631
- dots.forEach((dot, vi) => {
10632
- const r = computeBubbleRadius(series.bubbleSizes?.[vi], maxBubble, medianRadius);
10633
- primitives.push({
10634
- kind: 'circle',
10635
- cx: dot.cx,
10636
- cy: dot.cy,
10637
- r,
10638
- fill: resolveDataPointFill(series, vi, c) ?? c,
10639
- opacity: 0.6,
10640
- part: { role: 'dataPoint', seriesIndex: si, pointIndex: vi },
10641
- });
10642
- });
10643
- if (showLabels) {
10644
- series.values.forEach((val, vi) => {
10645
- const dot = dots[vi];
10646
- const text = buildDataLabelText({ chartData, series, pointIndex: vi, value: val });
10647
- if (!dot || text === undefined) {
10648
- return;
10649
- }
10650
- dataLabels.push({
10651
- kind: 'text',
10652
- x: dot.cx,
10653
- y: dot.cy - 10,
10654
- text,
10655
- fontSize: DEFAULT_CHART_DATA_LABEL_PX,
10656
- fill: '#334155',
10657
- textAnchor: 'middle',
10658
- });
10659
- });
10660
- }
10661
- }
10662
- return { primitives, dataLabels };
10663
- }
10664
10774
 
10665
10775
  /**
10666
10776
  * chart-helper-lines.ts: shared drop-line / hi-low-line / up-down-bar primitives.
@@ -11211,10 +11321,7 @@ function valueToX(val, range, leftX, rightX) {
11211
11321
  const usable = rightX - leftX;
11212
11322
  let ratio;
11213
11323
  if (range.logScale && range.logBase) {
11214
- const base = range.logBase;
11215
- const clampedVal = Math.max(val, range.min);
11216
- const logVal = Math.log(clampedVal) / Math.log(base);
11217
- const logMin = Math.log(range.min) / Math.log(base);
11324
+ const base = range.logBase, clampedVal = Math.max(val, range.min), logVal = Math.log(clampedVal) / Math.log(base), logMin = Math.log(range.min) / Math.log(base);
11218
11325
  ratio = (logVal - logMin) / range.span;
11219
11326
  }
11220
11327
  else {
@@ -11224,8 +11331,7 @@ function valueToX(val, range, leftX, rightX) {
11224
11331
  }
11225
11332
  /** Vertical value gridlines + bottom tick labels (the transposed value axis). */
11226
11333
  function buildTransposedValueAxis(range, layout) {
11227
- const gridlines = [];
11228
- const axisLabels = [];
11334
+ const gridlines = [], axisLabels = [];
11229
11335
  for (const val of axisTickValues(range)) {
11230
11336
  const x = valueToX(val, range, layout.plotLeft, layout.plotRight);
11231
11337
  gridlines.push({
@@ -11251,8 +11357,7 @@ function buildTransposedValueAxis(range, layout) {
11251
11357
  }
11252
11358
  /** Left-anchored category labels, one per band, centred on the band. */
11253
11359
  function buildSideCategoryLabels(categoryLabels, layout) {
11254
- const catCount = Math.max(categoryLabels.length, 1);
11255
- const band = layout.plotHeight / catCount;
11360
+ const catCount = Math.max(categoryLabels.length, 1), band = layout.plotHeight / catCount;
11256
11361
  return categoryLabels.map((label, i) => ({
11257
11362
  kind: 'text',
11258
11363
  x: layout.plotLeft - 4,
@@ -11282,20 +11387,11 @@ function barFill(chartData, series, seriesIndex, pointIndex) {
11282
11387
  * clustered / stacked / percentStacked geometry with the axes transposed.
11283
11388
  */
11284
11389
  function buildHorizontalBarViewModel(element, chartData, categoryLabels) {
11285
- const layout = computePlotLayout(element.width, element.height, chartData, true);
11286
- const catCount = Math.max(categoryLabels.length, 1);
11287
- const series = chartData.series;
11288
- const grouping = chartData.grouping ?? 'clustered';
11289
- const isStacked = grouping === 'stacked' || grouping === 'percentStacked';
11290
- const isPercent = grouping === 'percentStacked';
11291
- const range = isPercent
11390
+ 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
11292
11391
  ? { min: 0, max: 100, span: 100 }
11293
11392
  : isStacked
11294
11393
  ? computeStackedValueRange(series, catCount)
11295
- : computeValueRangeForChart(series, chartData.axes);
11296
- const { gridlines, axisLabels } = buildTransposedValueAxis(range, layout);
11297
- const zeroX = valueToX(0, range, layout.plotLeft, layout.plotRight);
11298
- const zeroLine = range.min < 0 && range.max > 0
11394
+ : computeValueRangeForChart(series, chartData.axes), { gridlines, axisLabels } = buildTransposedValueAxis(range, layout), zeroX = valueToX(0, range, layout.plotLeft, layout.plotRight), zeroLine = range.min < 0 && range.max > 0
11299
11395
  ? {
11300
11396
  kind: 'line',
11301
11397
  x1: zeroX,
@@ -11305,27 +11401,14 @@ function buildHorizontalBarViewModel(element, chartData, categoryLabels) {
11305
11401
  stroke: ZERO_LINE_COLOR,
11306
11402
  strokeWidth: 1,
11307
11403
  }
11308
- : undefined;
11309
- const primitives = [];
11310
- const dataLabels = [];
11311
- const showLabels = chartData.style?.hasDataLabels;
11312
- const band = layout.plotHeight / catCount;
11404
+ : undefined, primitives = [], dataLabels = [], showLabels = chartData.style?.hasDataLabels, band = layout.plotHeight / catCount;
11313
11405
  if (!isStacked) {
11314
- const seriesCount = Math.max(series.length, 1);
11315
- const singleBarHeight = chartData.barGapWidth !== undefined
11406
+ const seriesCount = Math.max(series.length, 1), singleBarHeight = chartData.barGapWidth !== undefined
11316
11407
  ? band / (seriesCount + Math.max(chartData.barGapWidth, 0) / 100)
11317
- : (band * 0.7) / seriesCount;
11318
- const overlap = chartData.barOverlap ?? 0;
11319
- const step = singleBarHeight * (1 - overlap / 100);
11320
- const clusterHeight = singleBarHeight + step * (seriesCount - 1);
11321
- const groupOffset = (band - clusterHeight) / 2;
11408
+ : (band * 0.7) / seriesCount, overlap = chartData.barOverlap ?? 0, step = singleBarHeight * (1 - overlap / 100), clusterHeight = singleBarHeight + step * (seriesCount - 1), groupOffset = (band - clusterHeight) / 2;
11322
11409
  for (let ci = 0; ci < catCount; ci++) {
11323
11410
  for (let si = 0; si < series.length; si++) {
11324
- const val = series[si].values[ci] ?? 0;
11325
- const y = layout.plotTop + band * ci + groupOffset + step * si;
11326
- const valX = valueToX(val, range, layout.plotLeft, layout.plotRight);
11327
- const x = Math.min(zeroX, valX);
11328
- const w = Math.max(Math.abs(valX - zeroX), 1);
11411
+ 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);
11329
11412
  primitives.push({
11330
11413
  kind: 'rect',
11331
11414
  x,
@@ -11335,6 +11418,7 @@ function buildHorizontalBarViewModel(element, chartData, categoryLabels) {
11335
11418
  fill: barFill(chartData, series[si], si, ci),
11336
11419
  rx: 1,
11337
11420
  part: { role: 'dataPoint', seriesIndex: si, pointIndex: ci },
11421
+ title: buildMarkTooltip(series[si].name, categoryLabels[ci], val, series[si].numberFormat),
11338
11422
  });
11339
11423
  if (showLabels) {
11340
11424
  dataLabels.push({
@@ -11352,27 +11436,17 @@ function buildHorizontalBarViewModel(element, chartData, categoryLabels) {
11352
11436
  }
11353
11437
  }
11354
11438
  else {
11355
- const totals = isPercent ? categoryTotals(series, catCount) : [];
11356
- const barH = band * (isPercent ? 0.6 : 0.7);
11357
- const barOffset = (band - barH) / 2;
11439
+ const totals = isPercent ? categoryTotals(series, catCount) : [], barH = band * (isPercent ? 0.6 : 0.7), barOffset = (band - barH) / 2;
11358
11440
  for (let ci = 0; ci < catCount; ci++) {
11359
- let posRunning = 0;
11360
- let negRunning = 0;
11441
+ let posRunning = 0, negRunning = 0;
11361
11442
  const catTotal = isPercent ? totals[ci] || 1 : 1;
11362
11443
  for (let si = 0; si < series.length; si++) {
11363
- const rawVal = series[si].values[ci] ?? 0;
11364
- const val = isPercent ? (rawVal / catTotal) * 100 : rawVal;
11444
+ const rawVal = series[si].values[ci] ?? 0, val = isPercent ? (rawVal / catTotal) * 100 : rawVal;
11365
11445
  if (val === 0) {
11366
11446
  continue;
11367
11447
  }
11368
- const isNeg = val < 0;
11369
- const base = isNeg ? negRunning : posRunning;
11370
- const top = base + val;
11371
- const y = layout.plotTop + band * ci + barOffset;
11372
- const baseX = valueToX(base, range, layout.plotLeft, layout.plotRight);
11373
- const topX = valueToX(top, range, layout.plotLeft, layout.plotRight);
11374
- const x = Math.min(baseX, topX);
11375
- const w = Math.max(Math.abs(topX - baseX), 0.5);
11448
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
11449
+ const isNeg = val < 0, base = isNeg ? negRunning : posRunning, top = base + val, y = layout.plotTop + band * ci + barOffset, baseX = valueToX(base, range, layout.plotLeft, layout.plotRight), topX = valueToX(top, range, layout.plotLeft, layout.plotRight), x = Math.min(baseX, topX), w = Math.max(Math.abs(topX - baseX), 0.5);
11376
11450
  primitives.push({
11377
11451
  kind: 'rect',
11378
11452
  x,
@@ -11382,6 +11456,7 @@ function buildHorizontalBarViewModel(element, chartData, categoryLabels) {
11382
11456
  fill: barFill(chartData, series[si], si, ci),
11383
11457
  rx: 1,
11384
11458
  part: { role: 'dataPoint', seriesIndex: si, pointIndex: ci },
11459
+ title: buildMarkTooltip(series[si].name, categoryLabels[ci], rawVal, series[si].numberFormat),
11385
11460
  });
11386
11461
  if (showLabels && Math.abs(val) > 0) {
11387
11462
  dataLabels.push({
@@ -11407,8 +11482,8 @@ function buildHorizontalBarViewModel(element, chartData, categoryLabels) {
11407
11482
  }
11408
11483
  }
11409
11484
  }
11410
- const legendPos = chartData.style?.legendPosition ?? 'b';
11411
- const { legend, legendX, legendY, legendAnchor } = buildLegend(series, chartData.colorPalette, layout.svgWidth, legendPos, layout.svgHeight, layout.plotTop);
11485
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
11486
+ const legendPos = chartData.style?.legendPosition ?? 'b', { legend, legendX, legendY, legendAnchor } = buildLegend(series, chartData.colorPalette, layout.svgWidth, legendPos, layout.svgHeight, layout.plotTop);
11412
11487
  return {
11413
11488
  svgWidth: layout.svgWidth,
11414
11489
  svgHeight: layout.svgHeight,
@@ -12188,46 +12263,71 @@ function buildCartesianViewModel(element, chartData, categoryLabels, kind) {
12188
12263
  return buildHorizontalBarViewModel(element, chartData, categoryLabels);
12189
12264
  }
12190
12265
  const seriesCount = chartData.series.length;
12266
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12191
12267
  const layoutOpts = computeLayoutOptions(chartData.axes, chartData.dataTable, seriesCount);
12268
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12192
12269
  const layout = computePlotLayout(element.width, element.height, chartData, true, layoutOpts);
12270
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12193
12271
  const catCount = Math.max(categoryLabels.length, 1);
12194
- const isStacked = kind === 'bar' && (chartData.grouping === 'stacked' || chartData.grouping === 'percentStacked');
12272
+ // Stacking applies to bar, line and area (`GROUPING_SUPPORTED_TYPES` in
12273
+ // chart-editor-options.ts, and lineChart/areaChart both parse a real
12274
+ // `c:grouping`): a stacked or percentStacked line/area chart must render
12275
+ // running-sum geometry and go static (no value-drag) the same way stacked
12276
+ // bar segments do, not silently fall back to clustered/draggable.
12277
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12278
+ const isStacked = (kind === 'bar' || kind === 'line' || kind === 'area') &&
12279
+ (chartData.grouping === 'stacked' || chartData.grouping === 'percentStacked');
12280
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12195
12281
  const isPercent = isStacked && chartData.grouping === 'percentStacked';
12196
12282
  // Split series across primary/secondary value axes (clustered cartesian only).
12283
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12197
12284
  const { secondary } = splitSeriesByAxis(chartData.series, chartData.axes);
12285
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12198
12286
  const secondaryIdx = new Set(secondary.map((e) => e.index));
12287
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12199
12288
  const useSecondary = !isStacked && secondaryIdx.size > 0;
12289
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12200
12290
  const primaryPlotSeries = useSecondary
12201
12291
  ? chartData.series.filter((_s, i) => !secondaryIdx.has(i))
12202
12292
  : chartData.series;
12293
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12203
12294
  const secondaryPlotSeries = useSecondary
12204
12295
  ? chartData.series.filter((_s, i) => secondaryIdx.has(i))
12205
12296
  : [];
12297
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12206
12298
  const primaryAxis = chartData.axes?.find((axis) => axis.axisType === 'valAx' && axis.axPos !== 'r');
12299
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12207
12300
  const primaryRange = isStacked
12208
12301
  ? {
12209
12302
  ...stackedRange(chartData, catCount, isPercent),
12210
12303
  ...(primaryAxis?.orientation === 'maxMin' ? { reverseOrder: true } : {}),
12211
12304
  }
12212
12305
  : computeValueRangeForChart(primaryPlotSeries.length > 0 ? primaryPlotSeries : chartData.series, chartData.axes);
12306
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12213
12307
  const secondaryRange = useSecondary && secondaryPlotSeries.length > 0
12214
12308
  ? computeValueRangeForAxis(secondaryPlotSeries, chartData.axes?.find((axis) => axis.axisType === 'valAx' && axis.axPos === 'r'))
12215
12309
  : undefined;
12310
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12216
12311
  const axisRes = buildCartesianAxes(chartData, layout, primaryRange, secondaryRange, catCount);
12312
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12217
12313
  const zeroLine = primaryRange.logScale ? undefined : buildZeroLine(primaryRange, layout);
12314
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12218
12315
  const horizontalAxis = buildCartesianHorizontalAxis(chartData, categoryLabels, layout, kind, primaryRange, secondaryRange);
12316
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12219
12317
  const { catAxisStyle, sourceIndices, displayChartData } = horizontalAxis;
12318
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12220
12319
  const legendPos = chartData.style?.legendPosition ?? 'b';
12320
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12221
12321
  const { legend, legendX, legendY, legendAnchor } = buildLegend(chartData.series, chartData.colorPalette, layout.svgWidth, legendPos, layout.svgHeight, layout.plotTop);
12222
12322
  let plot;
12223
12323
  if (kind === 'bar') {
12224
12324
  plot = buildBars(chartData, catCount, layout, primaryRange, secondaryRange, secondaryIdx, isStacked ? (isPercent ? 'percentStacked' : 'stacked') : 'clustered', sourceIndices);
12225
12325
  }
12226
12326
  else if (kind === 'line') {
12227
- plot = buildLines(chartData, catCount, layout, primaryRange, secondaryRange, secondaryIdx, sourceIndices, horizontalAxis.xPositions);
12327
+ plot = buildLines(chartData, catCount, layout, primaryRange, secondaryRange, secondaryIdx, sourceIndices, horizontalAxis.xPositions, isStacked ? (isPercent ? 'percentStacked' : 'stacked') : 'clustered');
12228
12328
  }
12229
12329
  else if (kind === 'area') {
12230
- plot = buildAreas(chartData, catCount, layout, primaryRange, sourceIndices, horizontalAxis.xPositions);
12330
+ plot = buildAreas(chartData, catCount, layout, primaryRange, sourceIndices, horizontalAxis.xPositions, isStacked ? (isPercent ? 'percentStacked' : 'stacked') : 'clustered');
12231
12331
  }
12232
12332
  else if (kind === 'scatter') {
12233
12333
  plot = buildScatter(chartData, layout, primaryRange);
@@ -12237,29 +12337,35 @@ function buildCartesianViewModel(element, chartData, categoryLabels, kind) {
12237
12337
  }
12238
12338
  // Drop lines / hi-low lines / up-down bars (line + area kinds). Drawn behind
12239
12339
  // the series marks so the data stays legible on top.
12340
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12240
12341
  const helperLines = kind === 'line' || kind === 'area'
12241
12342
  ? computeHelperLinePrimitives(chartData, layout, primaryRange, catCount, {
12242
12343
  mode: 'line',
12243
12344
  xPositions: horizontalAxis.xPositions,
12244
12345
  })
12245
12346
  : [];
12347
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12246
12348
  const primitives = [
12247
12349
  ...helperLines,
12248
12350
  ...plot.primitives,
12249
12351
  ...horizontalAxis.tickMarks,
12250
12352
  ];
12251
12353
  // Overlays (depth): regression trendlines, error bars, axis titles, data table.
12354
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12252
12355
  const overlays = [
12253
12356
  ...computeTrendlinePrimitives(displayChartData, catCount, layout, primaryRange, catAxisStyle, chartData.colorPalette),
12254
12357
  ...computeErrorBarPrimitives(displayChartData, catCount, layout, primaryRange, catAxisStyle),
12255
12358
  ...computeAxisTitlePrimitives(chartData, layout),
12256
12359
  ];
12360
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12257
12361
  const dataTablePrims = computeDataTablePrimitives(displayChartData, layout, chartData.colorPalette);
12258
12362
  primitives.push(...overlays, ...dataTablePrims);
12363
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12259
12364
  const title = chartData.style?.hasTitle && chartData.title ? chartData.title : undefined;
12260
12365
  // Vertical drag-to-value only has a single-value meaning for un-stacked marks:
12261
12366
  // stacked/percentStacked bar segments sit on running sums, so dragging one
12262
12367
  // would not track the pointer.
12368
+ // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
12263
12369
  const valueDrag = isStacked
12264
12370
  ? undefined
12265
12371
  : {
@@ -15116,6 +15222,10 @@ function scalePathD(d, scale, dx, dy) {
15116
15222
  *
15117
15223
  * @module chart-view-model
15118
15224
  */
15225
+ /* eslint-disable one-var -- this module predates the rule and combining every
15226
+ sibling `const`/`let` in a function into one comma-list (oxlint's own
15227
+ `--fix` cannot do this safely once a non-declaration statement sits between
15228
+ them) would churn geometry code far beyond this change's scope. */
15119
15229
  // ─────────────────────────────────────────────────────────────────────────────
15120
15230
  // Palette
15121
15231
  // ─────────────────────────────────────────────────────────────────────────────
@@ -15147,8 +15257,7 @@ function seriesColor(series, index, colorPalette) {
15147
15257
  * mirrors `computeValueRange` in `chart-helpers.ts`.
15148
15258
  */
15149
15259
  function computeValueRange(series) {
15150
- let dataMin = Number.POSITIVE_INFINITY;
15151
- let dataMax = Number.NEGATIVE_INFINITY;
15260
+ let dataMin = Number.POSITIVE_INFINITY, dataMax = Number.NEGATIVE_INFINITY;
15152
15261
  for (const item of series) {
15153
15262
  for (const value of item.values) {
15154
15263
  if (value < dataMin) {
@@ -15170,11 +15279,9 @@ function computeValueRange(series) {
15170
15279
  * scale as any other value axis.
15171
15280
  */
15172
15281
  function computeStackedValueRange(series, catCount) {
15173
- let maxSum = 0;
15174
- let minSum = 0;
15282
+ let maxSum = 0, minSum = 0;
15175
15283
  for (let ci = 0; ci < catCount; ci++) {
15176
- let pos = 0;
15177
- let neg = 0;
15284
+ let pos = 0, neg = 0;
15178
15285
  for (const s of series) {
15179
15286
  const v = s.values[ci] ?? 0;
15180
15287
  if (v >= 0) {
@@ -15200,10 +15307,7 @@ function valueToY(val, range, topY, bottomY) {
15200
15307
  const usable = bottomY - topY;
15201
15308
  let ratio;
15202
15309
  if (range.logScale && range.logBase) {
15203
- const base = range.logBase;
15204
- const clampedVal = Math.max(val, range.min);
15205
- const logVal = Math.log(clampedVal) / Math.log(base);
15206
- const logMin = Math.log(range.min) / Math.log(base);
15310
+ const base = range.logBase, clampedVal = Math.max(val, range.min), logVal = Math.log(clampedVal) / Math.log(base), logMin = Math.log(range.min) / Math.log(base);
15207
15311
  ratio = (logVal - logMin) / range.span;
15208
15312
  }
15209
15313
  else {
@@ -15235,6 +15339,23 @@ function formatAxisValue(val, formatCode) {
15235
15339
  }
15236
15340
  return val.toFixed(1);
15237
15341
  }
15342
+ /**
15343
+ * Build the hover-tooltip text for a plain data mark (bar / line / area /
15344
+ * scatter / bubble / pie / radar point), projected as each primitive's `title`
15345
+ * field (see the doc comment on `SvgPath.title`).
15346
+ *
15347
+ * Mirrors the region map's own `"<name>: <value>"` tooltip (chart-waterfall-map.ts):
15348
+ * join whichever of the series name and category/point label are known, then
15349
+ * append the formatted value. Either label may be absent (a scatter/bubble
15350
+ * point has no category; an un-named series has no name); the result degrades
15351
+ * to just the value when neither is.
15352
+ */
15353
+ function buildMarkTooltip(seriesName, categoryLabel, value, numberFormat) {
15354
+ const label = [seriesName, categoryLabel]
15355
+ .filter((part) => Boolean(part && part.length > 0))
15356
+ .join(', '), formatted = formatAxisValue(value, numberFormat);
15357
+ return label.length > 0 ? `${label}: ${formatted}` : formatted;
15358
+ }
15238
15359
  /**
15239
15360
  * Compute the plot layout for a chart element.
15240
15361
  * Mirrors `computeLayout` from chart-layout.ts (React). When `options` is omitted
@@ -15256,9 +15377,7 @@ function formatAxisValue(val, formatCode) {
15256
15377
  * @returns Pixels to reserve below the plot area.
15257
15378
  */
15258
15379
  function categoryAxisBand(chartData) {
15259
- const axis = chartData.axes?.find((candidate) => candidate.axisType === 'catAx' || candidate.axisType === 'dateAx');
15260
- const fontPx = axis?.fontSize !== undefined ? chartFontPx(axis.fontSize) : DEFAULT_CHART_TEXT_PX;
15261
- const offset = 4 + 8 * ((axis?.labelOffset ?? 100) / 100);
15380
+ const axis = chartData.axes?.find((candidate) => candidate.axisType === 'catAx' || candidate.axisType === 'dateAx'), fontPx = axis?.fontSize !== undefined ? chartFontPx(axis.fontSize) : DEFAULT_CHART_TEXT_PX, offset = 4 + 8 * ((axis?.labelOffset ?? 100) / 100);
15262
15381
  return Math.max(24, offset + fontPx * 1.2);
15263
15382
  }
15264
15383
  function computePlotLayout(elementWidth, elementHeight, chartData, hasAxes, options) {
@@ -15266,14 +15385,9 @@ function computePlotLayout(elementWidth, elementHeight, chartData, hasAxes, opti
15266
15385
  // it with `preserveAspectRatio="none"`, so ANY minimum here (historically
15267
15386
  // 320x180) makes the chart scale non-uniformly inside its host (issue #132:
15268
15387
  // a 475x174 frame got a 475x180 viewBox, squeezing y by 0.967).
15269
- const svgWidth = Math.max(1, elementWidth);
15270
- const svgHeight = Math.max(1, elementHeight);
15271
- let plotLeft = hasAxes ? 48 : 8;
15272
- let plotTop = 8;
15273
- let plotRight = svgWidth - 8;
15274
- let plotBottom = svgHeight - (hasAxes ? categoryAxisBand(chartData) : 8);
15275
- const style = chartData.style;
15276
- const legendPos = style?.legendPosition ?? 'b';
15388
+ const svgWidth = Math.max(1, elementWidth), svgHeight = Math.max(1, elementHeight);
15389
+ let plotLeft = hasAxes ? 48 : 8, plotTop = 8, plotRight = svgWidth - 8, plotBottom = svgHeight - (hasAxes ? categoryAxisBand(chartData) : 8);
15390
+ const style = chartData.style, legendPos = style?.legendPosition ?? 'b';
15277
15391
  if (style?.hasTitle) {
15278
15392
  plotTop += 20;
15279
15393
  }
@@ -15304,8 +15418,7 @@ function computePlotLayout(elementWidth, elementHeight, chartData, hasAxes, opti
15304
15418
  const rowCount = options.dataTableRowCount ?? 1;
15305
15419
  plotBottom -= 14 + rowCount * 14;
15306
15420
  }
15307
- const plotWidth = Math.max(plotRight - plotLeft, 1);
15308
- const plotHeight = Math.max(plotBottom - plotTop, 1);
15421
+ const plotWidth = Math.max(plotRight - plotLeft, 1), plotHeight = Math.max(plotBottom - plotTop, 1);
15309
15422
  return {
15310
15423
  svgWidth,
15311
15424
  svgHeight,
@@ -15341,8 +15454,7 @@ function axisTickValues(range) {
15341
15454
  return Array.from({ length: TICK_COUNT + 1 }, (_unused, index) => range.min + (range.span / TICK_COUNT) * index);
15342
15455
  }
15343
15456
  function buildGridlinesAndLabels(range, layout) {
15344
- const gridlines = [];
15345
- const axisLabels = [];
15457
+ const gridlines = [], axisLabels = [];
15346
15458
  for (const val of axisTickValues(range)) {
15347
15459
  const y = valueToY(val, range, layout.plotTop, layout.plotBottom);
15348
15460
  gridlines.push({
@@ -15406,9 +15518,7 @@ function buildLegend(series, colorPalette, svgWidth, legendPos, svgHeight, plotT
15406
15518
  color: seriesColor(s, i, colorPalette),
15407
15519
  label: s.name,
15408
15520
  }));
15409
- let legendX = svgWidth / 2;
15410
- let legendY = svgHeight - 8;
15411
- let legendAnchor = 'middle';
15521
+ let legendX = svgWidth / 2, legendY = svgHeight - 8, legendAnchor = 'middle';
15412
15522
  if (legendPos === 'r') {
15413
15523
  legendX = svgWidth - 75;
15414
15524
  legendY = plotTop;
@@ -15425,19 +15535,10 @@ function buildLegend(series, colorPalette, svgWidth, legendPos, svgHeight, plotT
15425
15535
  return { legend, legendX, legendY, legendAnchor };
15426
15536
  }
15427
15537
  function computeBarRects(series, catCount, layout, range, colorPalette) {
15428
- const rects = [];
15429
- const seriesCount = Math.max(series.length, 1);
15430
- const barGroupWidth = layout.plotWidth / Math.max(catCount, 1);
15431
- const singleBarWidth = (barGroupWidth * 0.7) / seriesCount;
15432
- const groupOffset = (barGroupWidth - singleBarWidth * seriesCount) / 2;
15538
+ const rects = [], seriesCount = Math.max(series.length, 1), barGroupWidth = layout.plotWidth / Math.max(catCount, 1), singleBarWidth = (barGroupWidth * 0.7) / seriesCount, groupOffset = (barGroupWidth - singleBarWidth * seriesCount) / 2;
15433
15539
  for (let ci = 0; ci < catCount; ci++) {
15434
15540
  for (let si = 0; si < series.length; si++) {
15435
- const val = series[si].values[ci] ?? 0;
15436
- const x = layout.plotLeft + barGroupWidth * ci + groupOffset + singleBarWidth * si;
15437
- const zeroY = valueToY(0, range, layout.plotTop, layout.plotBottom);
15438
- const valY = valueToY(val, range, layout.plotTop, layout.plotBottom);
15439
- const y = Math.min(zeroY, valY);
15440
- const h = Math.max(Math.abs(zeroY - valY), 1);
15541
+ const val = series[si].values[ci] ?? 0, x = layout.plotLeft + barGroupWidth * ci + groupOffset + singleBarWidth * si, zeroY = valueToY(0, range, layout.plotTop, layout.plotBottom), valY = valueToY(val, range, layout.plotTop, layout.plotBottom), y = Math.min(zeroY, valY), h = Math.max(Math.abs(zeroY - valY), 1);
15441
15542
  rects.push({
15442
15543
  x,
15443
15544
  y,
@@ -15450,20 +15551,15 @@ function computeBarRects(series, catCount, layout, range, colorPalette) {
15450
15551
  return rects;
15451
15552
  }
15452
15553
  function computeStackedBarRects(series, catCount, layout, range, colorPalette) {
15453
- const rects = [];
15454
- const barW = (layout.plotWidth / Math.max(catCount, 1)) * 0.7;
15455
- const barOffset = (layout.plotWidth / Math.max(catCount, 1) - barW) / 2;
15456
- const zeroY = valueToY(0, range, layout.plotTop, layout.plotBottom);
15554
+ const rects = [], barW = (layout.plotWidth / Math.max(catCount, 1)) * 0.7, barOffset = (layout.plotWidth / Math.max(catCount, 1) - barW) / 2, zeroY = valueToY(0, range, layout.plotTop, layout.plotBottom);
15457
15555
  for (let ci = 0; ci < catCount; ci++) {
15458
- let posTop = zeroY;
15459
- let negBottom = zeroY;
15556
+ let posTop = zeroY, negBottom = zeroY;
15460
15557
  for (let si = 0; si < series.length; si++) {
15461
15558
  const val = series[si].values[ci] ?? 0;
15462
15559
  if (val === 0) {
15463
15560
  continue;
15464
15561
  }
15465
- const x = layout.plotLeft + (layout.plotWidth / Math.max(catCount, 1)) * ci + barOffset;
15466
- const h = Math.max(Math.abs(valueToY(val, range, layout.plotTop, layout.plotBottom) -
15562
+ const x = layout.plotLeft + (layout.plotWidth / Math.max(catCount, 1)) * ci + barOffset, h = Math.max(Math.abs(valueToY(val, range, layout.plotTop, layout.plotBottom) -
15467
15563
  valueToY(0, range, layout.plotTop, layout.plotBottom)), 1);
15468
15564
  if (val > 0) {
15469
15565
  const y = posTop - h;
@@ -15498,9 +15594,7 @@ function computeStackedBarRects(series, catCount, layout, range, colorPalette) {
15498
15594
  function computeLinePoints(values, catCount, layout, range) {
15499
15595
  const n = Math.max(catCount, 2);
15500
15596
  return values.map((val, i) => {
15501
- const nx = n > 1 ? i / (n - 1) : 0;
15502
- const x = layout.plotLeft + layout.plotWidth * nx;
15503
- const y = valueToY(val, range, layout.plotTop, layout.plotBottom);
15597
+ const nx = n > 1 ? i / (n - 1) : 0, x = layout.plotLeft + layout.plotWidth * nx, y = valueToY(val, range, layout.plotTop, layout.plotBottom);
15504
15598
  return { x, y };
15505
15599
  });
15506
15600
  }
@@ -15508,55 +15602,37 @@ function linePointsToSvgString(points) {
15508
15602
  return points.map((p) => `${p.x.toFixed(2)},${p.y.toFixed(2)}`).join(' ');
15509
15603
  }
15510
15604
  function computePieSlicePath(cx, cy, outerR, innerR, startAngle, endAngle) {
15511
- const largeArc = endAngle - startAngle > Math.PI ? 1 : 0;
15512
- const x1 = cx + outerR * Math.cos(startAngle);
15513
- const y1 = cy + outerR * Math.sin(startAngle);
15514
- const x2 = cx + outerR * Math.cos(endAngle);
15515
- const y2 = cy + outerR * Math.sin(endAngle);
15605
+ const largeArc = endAngle - startAngle > Math.PI ? 1 : 0, x1 = cx + outerR * Math.cos(startAngle), y1 = cy + outerR * Math.sin(startAngle), x2 = cx + outerR * Math.cos(endAngle), y2 = cy + outerR * Math.sin(endAngle);
15516
15606
  let d;
15517
15607
  if (innerR > 0) {
15518
- const ix1 = cx + innerR * Math.cos(startAngle);
15519
- const iy1 = cy + innerR * Math.sin(startAngle);
15520
- const ix2 = cx + innerR * Math.cos(endAngle);
15521
- const iy2 = cy + innerR * Math.sin(endAngle);
15608
+ const ix1 = cx + innerR * Math.cos(startAngle), iy1 = cy + innerR * Math.sin(startAngle), ix2 = cx + innerR * Math.cos(endAngle), iy2 = cy + innerR * Math.sin(endAngle);
15522
15609
  d = `M${x1},${y1} A${outerR},${outerR} 0 ${largeArc} 1 ${x2},${y2} L${ix2},${iy2} A${innerR},${innerR} 0 ${largeArc} 0 ${ix1},${iy1} Z`;
15523
15610
  }
15524
15611
  else {
15525
15612
  d = `M${cx},${cy} L${x1},${y1} A${outerR},${outerR} 0 ${largeArc} 1 ${x2},${y2} Z`;
15526
15613
  }
15527
- const midAngle = (startAngle + endAngle) / 2;
15528
- const labelR = outerR * 0.7;
15529
- const labelX = cx + labelR * Math.cos(midAngle);
15530
- const labelY = cy + labelR * Math.sin(midAngle);
15614
+ const midAngle = (startAngle + endAngle) / 2, labelR = outerR * 0.7, labelX = cx + labelR * Math.cos(midAngle), labelY = cy + labelR * Math.sin(midAngle);
15531
15615
  return { d, midAngle, labelX, labelY };
15532
15616
  }
15533
15617
  function computePieLayout(elementWidth, elementHeight, chartData, isDoughnut) {
15534
- const size = Math.min(Math.max(elementWidth, 1), Math.max(elementHeight, 1));
15535
- const titleOffset = chartData.style?.hasTitle ? 20 : 0;
15536
- const legendOffset = chartData.style?.hasLegend ? 20 : 0;
15537
- const cx = size / 2;
15538
- const cy = titleOffset + (size - titleOffset - legendOffset) / 2;
15539
- const outerR = Math.max((size - titleOffset - legendOffset) * 0.42, 0);
15618
+ const size = Math.min(Math.max(elementWidth, 1), Math.max(elementHeight, 1)), titleOffset = chartData.style?.hasTitle ? 20 : 0, legendOffset = chartData.style?.hasLegend ? 20 : 0, cx = size / 2, cy = titleOffset + (size - titleOffset - legendOffset) / 2, outerR = Math.max((size - titleOffset - legendOffset) * 0.42, 0),
15540
15619
  // Honour c:holeSize (10-90% of the outer diameter) when parsed; otherwise
15541
15620
  // keep the legacy 0.55 ratio byte-for-byte.
15542
- const holeRatio = isDoughnut && chartData.doughnutHoleSize !== undefined
15621
+ holeRatio = isDoughnut && chartData.doughnutHoleSize !== undefined
15543
15622
  ? Math.min(Math.max(chartData.doughnutHoleSize, 10), 90) / 100
15544
- : 0.55;
15545
- const innerR = isDoughnut ? outerR * holeRatio : 0;
15623
+ : 0.55, innerR = isDoughnut ? outerR * holeRatio : 0;
15546
15624
  return { cx, cy, outerR, innerR, size };
15547
15625
  }
15548
15626
  function computePieSlices(values, cx, cy, outerR, innerR, options) {
15549
15627
  const total = values.reduce((s, v) => s + Math.abs(v), 0) || 1;
15550
15628
  let cumAngle = options?.startAngle ?? -Math.PI / 2;
15551
15629
  return values.map((val, i) => {
15552
- const sliceAngle = (Math.abs(val) / total) * Math.PI * 2;
15553
- const startAngle = cumAngle;
15630
+ const sliceAngle = (Math.abs(val) / total) * Math.PI * 2, startAngle = cumAngle;
15554
15631
  cumAngle += sliceAngle;
15555
15632
  // A c:explosion pulls the slice outward along its bisector.
15556
15633
  const explosion = options?.explosions?.[i] ?? 0;
15557
15634
  if (explosion > 0) {
15558
- const mid = (startAngle + cumAngle) / 2;
15559
- const offset = outerR * (explosion / 100);
15635
+ const mid = (startAngle + cumAngle) / 2, offset = outerR * (explosion / 100);
15560
15636
  return computePieSlicePath(cx + Math.cos(mid) * offset, cy + Math.sin(mid) * offset, outerR, innerR, startAngle, cumAngle);
15561
15637
  }
15562
15638
  return computePieSlicePath(cx, cy, outerR, innerR, startAngle, cumAngle);
@@ -15583,9 +15659,7 @@ function computeScatterXDomain(seriesXValues) {
15583
15659
  return { min, span: Math.max(Math.max(...finite) - min, 1) };
15584
15660
  }
15585
15661
  function computeScatterDots(values, maxXIndex, layout, range, xValues, xDomain) {
15586
- const finiteX = xValues?.slice(0, values.length).filter(Number.isFinite);
15587
- const minX = xDomain ? xDomain.min : finiteX?.length ? Math.min(...finiteX) : 0;
15588
- const spanX = xDomain
15662
+ const finiteX = xValues?.slice(0, values.length).filter(Number.isFinite), minX = xDomain ? xDomain.min : finiteX?.length ? Math.min(...finiteX) : 0, spanX = xDomain
15589
15663
  ? xDomain.span
15590
15664
  : finiteX?.length
15591
15665
  ? Math.max(Math.max(...finiteX) - minX, 1)
@@ -15625,8 +15699,7 @@ function radarAngle(index, catCount) {
15625
15699
  function computeRadarPoints(values, maxVal, radius, cx, cy, catCount) {
15626
15700
  const denom = maxVal > 0 ? maxVal : 1;
15627
15701
  return values.slice(0, Math.max(catCount, 1)).map((val, i) => {
15628
- const angle = radarAngle(i, catCount);
15629
- const r = (Math.abs(val) / denom) * radius;
15702
+ const angle = radarAngle(i, catCount), r = (Math.abs(val) / denom) * radius;
15630
15703
  return { x: cx + r * Math.cos(angle), y: cy + r * Math.sin(angle) };
15631
15704
  });
15632
15705
  }
@@ -15710,18 +15783,15 @@ function buildChartViewModel(element) {
15710
15783
  if (element.type !== 'chart') {
15711
15784
  return buildFallbackViewModel(element.width, element.height, 'Chart');
15712
15785
  }
15713
- const chartEl = element;
15714
- const chartData = chartEl.chartData;
15786
+ const chartEl = element, chartData = chartEl.chartData;
15715
15787
  if (!chartData || chartData.series.length === 0) {
15716
15788
  return buildFallbackViewModel(element.width, element.height, chartData?.title ?? 'Chart');
15717
15789
  }
15718
- const chartType = chartData.chartType ?? 'bar';
15719
- const kind = resolveChartKind(chartType);
15790
+ const chartType = chartData.chartType ?? 'bar', kind = resolveChartKind(chartType);
15720
15791
  if (kind === 'unsupported') {
15721
15792
  return buildFallbackViewModel(element.width, element.height, chartData.title ?? chartType);
15722
15793
  }
15723
- const longestLen = chartData.series.reduce((m, s) => Math.max(m, s.values.length), 0);
15724
- const categoryLabels = chartData.categories.length > 0
15794
+ const longestLen = chartData.series.reduce((m, s) => Math.max(m, s.values.length), 0), categoryLabels = chartData.categories.length > 0
15725
15795
  ? chartData.categories
15726
15796
  : Array.from({ length: longestLen }, (_, i) => String(i + 1));
15727
15797
  // Pie-of-pie / bar-of-pie splits one series across a primary + secondary plot.
@@ -15816,8 +15886,7 @@ function buildFlatViewModel(element, chartData, categoryLabels, kind) {
15816
15886
  function buildFallbackViewModel(width, height, label) {
15817
15887
  // Match the frame box exactly (bindings stretch with preserveAspectRatio
15818
15888
  // "none"; a minimum here would scale the fallback non-uniformly).
15819
- const svgWidth = Math.max(width, 1);
15820
- const svgHeight = Math.max(height, 1);
15889
+ const svgWidth = Math.max(width, 1), svgHeight = Math.max(height, 1);
15821
15890
  return {
15822
15891
  svgWidth,
15823
15892
  svgHeight,
@@ -15860,19 +15929,12 @@ function buildFallbackViewModel(width, height, label) {
15860
15929
  };
15861
15930
  }
15862
15931
  function buildPieViewModel(element, chartData, categoryLabels, isDoughnut) {
15863
- const { cx, cy, outerR, innerR, size } = computePieLayout(element.width, element.height, chartData, isDoughnut);
15864
- const svgWidth = Math.max(size, 100);
15865
- const svgHeight = Math.max(size, 60);
15866
- const pieSeries = chartData.series[0];
15867
- const values = pieSeries?.values ?? [];
15932
+ const { cx, cy, outerR, innerR, size } = computePieLayout(element.width, element.height, chartData, isDoughnut), svgWidth = Math.max(size, 100), svgHeight = Math.max(size, 60), pieSeries = chartData.series[0], values = pieSeries?.values ?? [],
15868
15933
  // c:firstSliceAng rotates the pie clockwise from 12 o'clock; c:explosion (per
15869
15934
  // series or per c:dPt) pulls slices outward.
15870
- const startAngle = -Math.PI / 2 + ((chartData.firstSliceAngle ?? 0) * Math.PI) / 180;
15871
- const explosions = pieSeries
15935
+ startAngle = -Math.PI / 2 + ((chartData.firstSliceAngle ?? 0) * Math.PI) / 180, explosions = pieSeries
15872
15936
  ? values.map((_v, i) => resolveDataPointExplosion(pieSeries, i))
15873
- : undefined;
15874
- const slices = computePieSlices(values, cx, cy, outerR, innerR, { startAngle, explosions });
15875
- const primitives = slices.map(({ d }, i) => ({
15937
+ : undefined, slices = computePieSlices(values, cx, cy, outerR, innerR, { startAngle, explosions }), primitives = slices.map(({ d }, i) => ({
15876
15938
  kind: 'path',
15877
15939
  d,
15878
15940
  // Pie/doughnut vary colours per slice (c:varyColors defaults on), so each
@@ -15883,15 +15945,14 @@ function buildPieViewModel(element, chartData, categoryLabels, isDoughnut) {
15883
15945
  stroke: '#ffffff',
15884
15946
  strokeWidth: 1.5,
15885
15947
  part: { role: 'dataPoint', seriesIndex: 0, pointIndex: i },
15886
- }));
15887
- const dataLabels = [];
15948
+ title: buildMarkTooltip(pieSeries?.name, categoryLabels[i], values[i] ?? 0, pieSeries?.numberFormat),
15949
+ })), dataLabels = [];
15888
15950
  if (chartData.style?.hasDataLabels) {
15889
15951
  // Offset (outEnd / bestFit) labels sit outside the rim with c:leaderLines.
15890
15952
  // A pie's percentage base is the whole series, and `c:showPercent` is the
15891
15953
  // flag that makes the difference between "40" and "40%" on the commonest
15892
15954
  // labelled chart in a business deck.
15893
- const percentBase = values.reduce((total, entry) => total + Math.abs(entry), 0);
15894
- const labelResult = buildPieDataLabels({
15955
+ const percentBase = values.reduce((total, entry) => total + Math.abs(entry), 0), labelResult = buildPieDataLabels({
15895
15956
  slices,
15896
15957
  values,
15897
15958
  cx,
@@ -15913,16 +15974,15 @@ function buildPieViewModel(element, chartData, categoryLabels, isDoughnut) {
15913
15974
  dataLabels.push(...labelResult.labels);
15914
15975
  primitives.push(...labelResult.leaderLines);
15915
15976
  }
15916
- const legendPos = chartData.style?.legendPosition ?? 'b';
15977
+ const legendPos = chartData.style?.legendPosition ?? 'b',
15917
15978
  // Legend swatches must match the slices: a per-point `c:dPt` fill overrides
15918
15979
  // the palette on the slice, so it overrides it on the swatch too.
15919
- const legend = categoryLabels.map((label, i) => ({
15980
+ legend = categoryLabels.map((label, i) => ({
15920
15981
  color: pieSeries
15921
15982
  ? resolveVaryColorFill(pieSeries, i, paletteColor(i, chartData.colorPalette))
15922
15983
  : paletteColor(i, chartData.colorPalette),
15923
15984
  label,
15924
- }));
15925
- const legendX = svgWidth / 2;
15985
+ })), legendX = svgWidth / 2;
15926
15986
  let legendY = svgHeight - 8;
15927
15987
  const legendAnchor = 'middle';
15928
15988
  if (legendPos === 't') {
@@ -15947,10 +16007,7 @@ function buildPieViewModel(element, chartData, categoryLabels, isDoughnut) {
15947
16007
  legendAnchor,
15948
16008
  };
15949
16009
  }
15950
- const RADAR_RINGS = 4;
15951
- const RADAR_RING_COLOR = '#cbd5e1';
15952
- const RADAR_SPOKE_COLOR = '#94a3b8';
15953
- const RADAR_LABEL_COLOR = '#64748b';
16010
+ const RADAR_RINGS = 4, RADAR_RING_COLOR = '#cbd5e1', RADAR_SPOKE_COLOR = '#94a3b8', RADAR_LABEL_COLOR = '#64748b';
15954
16011
  /**
15955
16012
  * Build the view-model for a radar / spider chart. Polar, so it has no
15956
16013
  * cartesian gridlines/axes; ring + spoke geometry and the data polygons all
@@ -15958,14 +16015,7 @@ const RADAR_LABEL_COLOR = '#64748b';
15958
16015
  * Mirrors React's `renderRadarChart` (chart-radar.tsx).
15959
16016
  */
15960
16017
  function buildRadarViewModel(element, chartData, categoryLabels) {
15961
- const layout = computePlotLayout(element.width, element.height, chartData, false);
15962
- const cx = layout.plotLeft + layout.plotWidth / 2;
15963
- const cy = layout.plotTop + layout.plotHeight / 2;
15964
- const radius = Math.max(Math.min(layout.plotWidth, layout.plotHeight) / 2 - 4, 1);
15965
- const catCount = Math.max(categoryLabels.length, 1);
15966
- const maxVal = Math.max(1, ...chartData.series.flatMap((s) => s.values.map((v) => Math.abs(v))));
15967
- const primitives = [];
15968
- const perimeterLabels = [];
16018
+ const layout = computePlotLayout(element.width, element.height, chartData, false), cx = layout.plotLeft + layout.plotWidth / 2, cy = layout.plotTop + layout.plotHeight / 2, radius = Math.max(Math.min(layout.plotWidth, layout.plotHeight) / 2 - 4, 1), catCount = Math.max(categoryLabels.length, 1), maxVal = Math.max(1, ...chartData.series.flatMap((s) => s.values.map((v) => Math.abs(v)))), primitives = [], perimeterLabels = [];
15969
16019
  // Concentric gridline rings (dashed except the outermost).
15970
16020
  for (let r = 1; r <= RADAR_RINGS; r++) {
15971
16021
  const rr = (radius * r) / RADAR_RINGS;
@@ -16005,8 +16055,7 @@ function buildRadarViewModel(element, chartData, categoryLabels) {
16005
16055
  // Per-series data polygons + vertex dots.
16006
16056
  const dataLabels = [];
16007
16057
  chartData.series.forEach((series, si) => {
16008
- const c = seriesColor(series, si, chartData.colorPalette);
16009
- const pts = computeRadarPoints(series.values, maxVal, radius, cx, cy, catCount);
16058
+ const c = seriesColor(series, si, chartData.colorPalette), pts = computeRadarPoints(series.values, maxVal, radius, cx, cy, catCount);
16010
16059
  if (pts.length === 0) {
16011
16060
  return;
16012
16061
  }
@@ -16019,6 +16068,7 @@ function buildRadarViewModel(element, chartData, categoryLabels) {
16019
16068
  stroke: c,
16020
16069
  strokeWidth: 1.5,
16021
16070
  part: { role: 'series', seriesIndex: si },
16071
+ title: series.name.length > 0 ? series.name : undefined,
16022
16072
  });
16023
16073
  pts.forEach((p, vi) => {
16024
16074
  primitives.push({
@@ -16028,6 +16078,7 @@ function buildRadarViewModel(element, chartData, categoryLabels) {
16028
16078
  r: 3,
16029
16079
  fill: c,
16030
16080
  part: { role: 'dataPoint', seriesIndex: si, pointIndex: vi },
16081
+ title: buildMarkTooltip(series.name, categoryLabels[vi], series.values[vi] ?? 0, series.numberFormat),
16031
16082
  });
16032
16083
  });
16033
16084
  if (chartData.style?.hasDataLabels) {
@@ -16048,9 +16099,7 @@ function buildRadarViewModel(element, chartData, categoryLabels) {
16048
16099
  });
16049
16100
  }
16050
16101
  });
16051
- const legendPos = chartData.style?.legendPosition ?? 'b';
16052
- const { legend, legendX, legendY, legendAnchor } = buildLegend(chartData.series, chartData.colorPalette, layout.svgWidth, legendPos, layout.svgHeight, layout.plotTop);
16053
- const title = chartData.style?.hasTitle && chartData.title ? chartData.title : undefined;
16102
+ const legendPos = chartData.style?.legendPosition ?? 'b', { legend, legendX, legendY, legendAnchor } = buildLegend(chartData.series, chartData.colorPalette, layout.svgWidth, legendPos, layout.svgHeight, layout.plotTop), title = chartData.style?.hasTitle && chartData.title ? chartData.title : undefined;
16054
16103
  return {
16055
16104
  svgWidth: layout.svgWidth,
16056
16105
  svgHeight: layout.svgHeight,
@@ -17059,6 +17108,9 @@ const ANIMATION_KEYFRAMES_CSS = `
17059
17108
  *
17060
17109
  * @module render/animation-authoring
17061
17110
  */
17111
+ /* oxlint-disable eslint/one-var -- each exported helper below declares its own
17112
+ independent locals; merging unrelated declarations across the many
17113
+ functions here would hurt readability, not help it. */
17062
17114
  // ==========================================================================
17063
17115
  // Option catalogs (value-only — bindings supply labels/icons)
17064
17116
  // ==========================================================================
@@ -17330,6 +17382,39 @@ function reorderAnimationUp(anims, elementId) {
17330
17382
  function reorderAnimationDown(anims, elementId) {
17331
17383
  return reorderByDelta(anims, elementId, +1);
17332
17384
  }
17385
+ /**
17386
+ * Moves one animation entry to an arbitrary position and re-normalises
17387
+ * `order` on the result. This is the drag-and-drop / move-to-index
17388
+ * algorithm shared by every binding's animation timeline: sort by `order`,
17389
+ * splice the moved entry out of its source position, splice it into the
17390
+ * target position, then reassign `order` 0..n-1.
17391
+ *
17392
+ * `source` identifies the entry being moved, either as a resolved index into
17393
+ * the `order`-sorted sequence (row-index drag-and-drop, used by most
17394
+ * bindings) or as `{ elementId }` to resolve that position by id (the
17395
+ * Svelte ribbon tab drags by the element under the pointer rather than a
17396
+ * row index). `targetIndex` is always a resolved index into the sorted
17397
+ * sequence: a caller that only has a target elementId (also the Svelte
17398
+ * ribbon tab) resolves it with `animations.findIndex` first.
17399
+ *
17400
+ * Returns a sorted, re-indexed copy without moving anything when `source`
17401
+ * cannot be resolved, `targetIndex` is out of range, or the entry is
17402
+ * already at `targetIndex`.
17403
+ */
17404
+ function reorderAnimationTo(animations, source, targetIndex) {
17405
+ const sorted = [...animations].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
17406
+ const sourceIndex = typeof source === 'number' ? source : sorted.findIndex((a) => a.elementId === source.elementId);
17407
+ if (sourceIndex < 0 ||
17408
+ sourceIndex >= sorted.length ||
17409
+ targetIndex < 0 ||
17410
+ targetIndex >= sorted.length ||
17411
+ sourceIndex === targetIndex) {
17412
+ return reindexOrder(sorted);
17413
+ }
17414
+ const [moved] = sorted.splice(sourceIndex, 1);
17415
+ sorted.splice(targetIndex, 0, moved);
17416
+ return reindexOrder(sorted);
17417
+ }
17333
17418
  // ==========================================================================
17334
17419
  // Immutable patch builders — coarse group preset apply/remove (Vue model)
17335
17420
  // ==========================================================================
@@ -17361,6 +17446,23 @@ function applyAnimationPreset(animations, elementId, group, preset) {
17361
17446
  function removeElementAnimation(animations, elementId) {
17362
17447
  return animations.filter((a) => a.elementId !== elementId);
17363
17448
  }
17449
+ /**
17450
+ * Lays out the proportional timeline-strip bars for a slide's animations:
17451
+ * each bar's `leftPercent`/`widthPercent` express its delay/duration as a
17452
+ * percentage of the longest end time (`delayMs + durationMs`) across every
17453
+ * entry, so the strip always spans the full authored sequence. Returned in
17454
+ * `order` order (matching the reorderable list every binding renders beside
17455
+ * the strip).
17456
+ */
17457
+ function buildAnimationTimelineBars(animations) {
17458
+ const sorted = [...animations].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
17459
+ const total = Math.max(1, ...sorted.map((animation) => (animation.delayMs ?? 0) + (animation.durationMs ?? 500)));
17460
+ return sorted.map((animation) => ({
17461
+ elementId: animation.elementId,
17462
+ leftPercent: ((animation.delayMs ?? 0) / total) * 100,
17463
+ widthPercent: ((animation.durationMs ?? 500) / total) * 100,
17464
+ }));
17465
+ }
17364
17466
  // ==========================================================================
17365
17467
  // Internal ordering helpers
17366
17468
  // ==========================================================================
@@ -24962,15 +25064,6 @@ function computeSplitCell(td, rowIndex, columnIndex) {
24962
25064
  });
24963
25065
  }
24964
25066
 
24965
- /**
24966
- * table-style-presets.ts - framework-agnostic table style preset catalogue.
24967
- *
24968
- * Pure data: the quick-style swatches (light / medium / dark families) shown in
24969
- * every binding's table properties panel. Each preset carries the header fill /
24970
- * foreground, banded-row background, and border colour applied when the user
24971
- * picks it. Ported from the React viewer's `constants/table-styles.ts` so React,
24972
- * Vue, and Angular consume one copy.
24973
- */
24974
25067
  const TABLE_STYLE_PRESETS = [
24975
25068
  // Light styles
24976
25069
  {
@@ -25048,6 +25141,29 @@ const TABLE_STYLE_PRESETS = [
25048
25141
  borderColor: '#548235',
25049
25142
  },
25050
25143
  ];
25144
+ /**
25145
+ * Apply a table quick-style preset to every cell's style. Header cells (the
25146
+ * first row, when `firstRowHeader` is set) get the header fill / foreground
25147
+ * + bold; banded body rows get the band background; every cell gets the
25148
+ * preset border colour. Returns a new rows array; does not mutate `td`.
25149
+ */
25150
+ function applyTableStylePreset(td, preset) {
25151
+ return td.rows.map((row, ri) => ({
25152
+ ...row,
25153
+ cells: row.cells.map((cell) => {
25154
+ const isHeader = ri === 0 && Boolean(td.firstRowHeader);
25155
+ const isBand = Boolean(td.bandedRows) && (ri - (td.firstRowHeader ? 1 : 0)) % 2 === 0;
25156
+ const style = {
25157
+ ...cell.style,
25158
+ backgroundColor: isHeader ? preset.headerBg : isBand ? preset.bandBg : undefined,
25159
+ color: isHeader ? preset.headerFg : cell.style?.color,
25160
+ bold: isHeader ? true : cell.style?.bold,
25161
+ borderColor: preset.borderColor,
25162
+ };
25163
+ return { ...cell, style };
25164
+ }),
25165
+ }));
25166
+ }
25051
25167
 
25052
25168
  // ---------------------------------------------------------------------------
25053
25169
  // Shared class tokens (match InspectorPane)
@@ -25072,22 +25188,14 @@ const GRADIENT_TYPE_OPTIONS = [
25072
25188
  // Subset of common patterns shown in the cell-fill pattern picker.
25073
25189
  const PATTERN_OPTIONS = OOXML_PATTERN_PRESETS.slice(0, 20);
25074
25190
 
25075
- /**
25076
- * table-resize.ts - framework-agnostic table drag-resize geometry.
25077
- *
25078
- * The pure math behind the column / row drag handles: cumulative column-boundary
25079
- * positions, redistributing two adjacent column proportions by a drag delta
25080
- * (clamped + renormalised to sum to 1), and clamping a dragged row height.
25081
- * Extracted from the React viewer's `utils/table-render-resize.tsx` overlay so
25082
- * every binding drives its own overlay component from one copy of the maths.
25083
- * No framework imports.
25084
- */
25085
25191
  /** Minimum proportion a single column may shrink to during a drag. */
25086
25192
  const MIN_COLUMN_PROPORTION = 0.03;
25087
25193
  /** Minimum height (px) a row may shrink to during a drag. */
25088
25194
  const MIN_ROW_HEIGHT = 16;
25089
25195
  /** Default row height (px) assumed when an actual measurement is unavailable. */
25090
25196
  const DEFAULT_ROW_HEIGHT = 32;
25197
+ /** Minimum proportion a column may be set to via the properties-panel width control. */
25198
+ const MIN_COLUMN_WIDTH_FRACTION = 0.05;
25091
25199
  /**
25092
25200
  * Cumulative left-edge positions (as percentages, 0-100) of the internal column
25093
25201
  * boundaries, i.e. one entry between each adjacent pair of columns. The leading
@@ -25134,6 +25242,58 @@ function computeResizedColumnWidths(initialWidths, index, deltaProportion) {
25134
25242
  function computeResizedRowHeight(initialRowHeight, deltaY) {
25135
25243
  return Math.round(Math.max(MIN_ROW_HEIGHT, initialRowHeight + deltaY));
25136
25244
  }
25245
+ /**
25246
+ * Redistribute column widths when column `index` is set to `newFraction` (a
25247
+ * proportion of the table width, 0-1), scaling every OTHER column
25248
+ * proportionally so their relative ratios to each other are preserved and the
25249
+ * array still sums to 1. This is the table-properties-inspector "set this
25250
+ * column's width" slider/input, shared by every binding so a drag on one
25251
+ * behaves identically to a drag on any other.
25252
+ *
25253
+ * Each non-target column is floored at {@link MIN_COLUMN_WIDTH_FRACTION}
25254
+ * before the whole array is renormalised to sum to 1.
25255
+ *
25256
+ * Returns `widths` unchanged if `index` is out of range.
25257
+ */
25258
+ function redistributeColumnWidth(widths, index, newFraction) {
25259
+ const oldFraction = widths[index];
25260
+ if (oldFraction === undefined) {
25261
+ return widths;
25262
+ }
25263
+ const diff = newFraction - oldFraction;
25264
+ const next = [...widths];
25265
+ next[index] = newFraction;
25266
+ const othersTotal = 1 - oldFraction;
25267
+ if (othersTotal > 0) {
25268
+ for (let j = 0; j < next.length; j++) {
25269
+ if (j !== index) {
25270
+ next[j] = Math.max(MIN_COLUMN_WIDTH_FRACTION, widths[j] - diff * (widths[j] / othersTotal));
25271
+ }
25272
+ }
25273
+ }
25274
+ const sum = next.reduce((a, b) => a + b, 0);
25275
+ return sum > 0 ? next.map((w) => w / sum) : next;
25276
+ }
25277
+ /** An equal-width column array for `count` columns, each `1 / count` (empty for `count <= 0`). */
25278
+ function evenColumnWidths(count) {
25279
+ if (count <= 0) {
25280
+ return [];
25281
+ }
25282
+ return Array.from({ length: count }, () => 1 / count);
25283
+ }
25284
+ /**
25285
+ * `rows` with a uniform height applied: every row's height is set to the
25286
+ * rounded average of the rows' current heights (falling back to
25287
+ * {@link DEFAULT_ROW_HEIGHT} for a row with no explicit height).
25288
+ */
25289
+ function evenRowHeights(rows) {
25290
+ const count = rows.length;
25291
+ if (count === 0) {
25292
+ return rows;
25293
+ }
25294
+ const avg = Math.round(rows.reduce((s, r) => s + (r.height ?? DEFAULT_ROW_HEIGHT), 0) / count);
25295
+ return rows.map((r) => ({ ...r, height: avg }));
25296
+ }
25137
25297
 
25138
25298
  /**
25139
25299
  * Framework-agnostic helpers for inline (on-canvas) SmartArt node text editing.
@@ -29597,6 +29757,8 @@ const translationsEn = {
29597
29757
  'pptx.ribbon.customColour': 'Custom colour...',
29598
29758
  'pptx.ribbon.textHighlightColour': 'Text highlight colour',
29599
29759
  'pptx.ribbon.highlightColourValue': 'Highlight colour {{color}}',
29760
+ 'pptx.ribbon.fillColourValue': 'Fill colour {{color}}',
29761
+ 'pptx.ribbon.outlineColourValue': 'Outline colour {{color}}',
29600
29762
  'pptx.ribbon.bulletList': 'Bullet list',
29601
29763
  'pptx.ribbon.justify': 'Justify',
29602
29764
  'pptx.toolbar.findAndReplace': 'Find and replace',
@@ -50100,6 +50262,165 @@ function buildSmartArtA11y(data) {
50100
50262
  };
50101
50263
  }
50102
50264
 
50265
+ /**
50266
+ * Per-layout bounds table. Layouts not listed here fall back to
50267
+ * {@link DEFAULT_BOUNDS}.
50268
+ */
50269
+ const LAYOUT_BOUNDS = {
50270
+ // A Venn diagram is typically drawn with 2-3 overlapping sets; beyond a
50271
+ // handful of circles it becomes unreadable.
50272
+ venn: { min: 2, max: 3 },
50273
+ // A 2x2 matrix has exactly four quadrants.
50274
+ matrix: { min: 4, max: 4 },
50275
+ // Pyramids and funnels need at least two tiers to convey a hierarchy.
50276
+ pyramid: { min: 2, max: 5 },
50277
+ funnel: { min: 2, max: 5 },
50278
+ // A target is a small set of concentric rings.
50279
+ target: { min: 2, max: 5 },
50280
+ // Gears mesh in small clusters.
50281
+ gear: { min: 2, max: 3 },
50282
+ // Cycles need at least three steps to read as a loop.
50283
+ cycle: { min: 3 },
50284
+ // Relationship / process / list / hierarchy / timeline are flexible but
50285
+ // still need at least one node to exist.
50286
+ };
50287
+ // Fallback bounds for any layout without an explicit entry. Kept as a
50288
+ // separate `export const` statement rather than combined with `LAYOUT_BOUNDS`
50289
+ // above: they have different export-ness, and combining them into one
50290
+ // declarator list (with a trailing `export { DEFAULT_BOUNDS };`) breaks the
50291
+ // generated .d.ts, which drops a name introduced only via a re-export.
50292
+ // eslint-disable-next-line one-var -- see comment above
50293
+ const DEFAULT_BOUNDS = { min: 1 };
50294
+ /**
50295
+ * Resolve the node-count bounds for a given layout category.
50296
+ * Returns {@link DEFAULT_BOUNDS} when the layout has no specific table entry.
50297
+ */
50298
+ function getSmartArtNodeBounds(layout) {
50299
+ if (!layout) {
50300
+ return DEFAULT_BOUNDS;
50301
+ }
50302
+ return LAYOUT_BOUNDS[layout] ?? DEFAULT_BOUNDS;
50303
+ }
50304
+ /** Whether adding another top-level node stays within the layout's max. */
50305
+ function canAddTopLevelNode(layout, topLevelCount) {
50306
+ const { max } = getSmartArtNodeBounds(layout);
50307
+ return max === undefined || topLevelCount < max;
50308
+ }
50309
+ /** Whether removing a top-level node keeps the count at or above the min. */
50310
+ function canRemoveTopLevelNode(layout, topLevelCount) {
50311
+ const { min } = getSmartArtNodeBounds(layout);
50312
+ return topLevelCount > min;
50313
+ }
50314
+ /**
50315
+ * Build a short, human-readable explanation of the bounds for a layout, or
50316
+ * `undefined` when the layout imposes no meaningful limit (min <= 1, no max).
50317
+ * The returned string is intended as a tooltip / hint, not a hard error.
50318
+ *
50319
+ * This text is deliberately framework-neutral English (matching the
50320
+ * `pptx.smartArt.boundsHint*` i18n keys' English fallback text): a binding
50321
+ * that wants a translated string applies its own i18n lookup keyed off
50322
+ * {@link getSmartArtNodeBounds} on top of this, rather than this function
50323
+ * taking a translation dependency.
50324
+ */
50325
+ function describeSmartArtBounds$1(layout) {
50326
+ const { min, max } = getSmartArtNodeBounds(layout);
50327
+ if (min <= 1 && max === undefined) {
50328
+ return undefined;
50329
+ }
50330
+ if (max === undefined) {
50331
+ return `Works best with at least ${min} items.`;
50332
+ }
50333
+ if (min === max) {
50334
+ return `This layout uses exactly ${max} items.`;
50335
+ }
50336
+ return `Works best with ${min} to ${max} items.`;
50337
+ }
50338
+
50339
+ /** Count of top-level nodes (no parentId) in the data model. */
50340
+ function countTopLevel(data) {
50341
+ return data.nodes.filter((n) => !n.parentId).length;
50342
+ }
50343
+ /**
50344
+ * Enter: insert a sibling node immediately after the current one.
50345
+ *
50346
+ * Returns the new data and the id of the inserted sibling so the caller can
50347
+ * move focus to it.
50348
+ */
50349
+ function addSiblingAfter(data, nodeId) {
50350
+ const next = addSmartArtNode(data, '', nodeId);
50351
+ if (next === data) {
50352
+ return undefined;
50353
+ }
50354
+ // The inserted node is the one directly after `nodeId` in the new list.
50355
+ // oxlint-disable-next-line eslint/one-var -- depends on `next` past the conditional return above; can't merge.
50356
+ const idx = next.nodes.findIndex((n) => n.id === nodeId), inserted = idx >= 0 ? next.nodes[idx + 1] : undefined;
50357
+ return { data: next, focusNodeId: inserted?.id };
50358
+ }
50359
+ /**
50360
+ * Delete / Backspace on an empty node: remove it (when more than one node
50361
+ * remains). Returns the previous sibling / node id to focus when available.
50362
+ */
50363
+ function removeEmptyNode(data, nodeId) {
50364
+ if (data.nodes.length <= 1) {
50365
+ return undefined;
50366
+ }
50367
+ const removedIndex = data.nodes.findIndex((n) => n.id === nodeId), next = removeSmartArtNode(data, nodeId);
50368
+ if (next === data) {
50369
+ return undefined;
50370
+ }
50371
+ // Prefer focusing the node that took the removed slot, else the previous one.
50372
+ // oxlint-disable-next-line eslint/one-var -- depends on `next` past the conditional return above; can't merge.
50373
+ const focusNode = next.nodes[Math.max(0, removedIndex - 1)];
50374
+ return { data: next, focusNodeId: focusNode?.id };
50375
+ }
50376
+ /** Tab: demote a node under its preceding sibling (connection-aware). */
50377
+ function demote(data, nodeId) {
50378
+ const next = demoteSmartArtNode(data, nodeId);
50379
+ return next === data ? undefined : next;
50380
+ }
50381
+ /** Shift+Tab: promote a node to its parent's level (connection-aware). */
50382
+ function promote(data, nodeId) {
50383
+ const next = promoteSmartArtNode(data, nodeId);
50384
+ return next === data ? undefined : next;
50385
+ }
50386
+ /** Move a node up (-1) or down (+1) within its sibling group. */
50387
+ function reorder(data, nodeId, direction) {
50388
+ const next = reorderSmartArtNode(data, nodeId, direction);
50389
+ return next === data ? undefined : next;
50390
+ }
50391
+ /** Index of a node among its siblings (nodes sharing its parentId). */
50392
+ function siblingIndex(data, nodeId) {
50393
+ const node = data.nodes.find((n) => n.id === nodeId);
50394
+ if (!node) {
50395
+ return -1;
50396
+ }
50397
+ // oxlint-disable-next-line eslint/one-var -- depends on `node` past the conditional return above; can't merge.
50398
+ const siblings = data.nodes.filter((n) => n.parentId === node.parentId);
50399
+ return siblings.findIndex((n) => n.id === nodeId);
50400
+ }
50401
+ /** Number of siblings (including the node itself) sharing its parentId. */
50402
+ function siblingCount(data, nodeId) {
50403
+ const node = data.nodes.find((n) => n.id === nodeId);
50404
+ if (!node) {
50405
+ return 0;
50406
+ }
50407
+ return data.nodes.filter((n) => n.parentId === node.parentId).length;
50408
+ }
50409
+ /** Connection types that represent the plain parent/child tree we edit inline. */
50410
+ const TREE_CONNECTION_TYPES = new Set(['parOf', 'presParOf', undefined]);
50411
+ /**
50412
+ * Connections beyond the editable parent/child tree (e.g. `sibTrans`,
50413
+ * `presOf`). Surfaced read-only so the user knows the diagram carries
50414
+ * relationships the simple text pane does not edit.
50415
+ */
50416
+ function classifyExtraConnections(data) {
50417
+ return (data.connections ?? []).filter((c) => !TREE_CONNECTION_TYPES.has(c.type));
50418
+ }
50419
+ /** Count of connections beyond the editable parent/child tree. */
50420
+ function extraConnectionCount(data) {
50421
+ return classifyExtraConnections(data).length;
50422
+ }
50423
+
50103
50424
  /**
50104
50425
  * Vanilla three.js GLTF/GLB model scene controller (framework-agnostic).
50105
50426
  *
@@ -51090,7 +51411,7 @@ const DEFAULT_FONT_SIZE$1 = 12;
51090
51411
  * Returns the fill colour of the element's shapeStyle, or a white default.
51091
51412
  * Only meaningful for elements that pass `hasShapeProperties`.
51092
51413
  */
51093
- function fillColorOf(el) {
51414
+ function fillColorOf$1(el) {
51094
51415
  if (hasShapeProperties(el)) {
51095
51416
  return el.shapeStyle?.fillColor ?? DEFAULT_FILL;
51096
51417
  }
@@ -51151,7 +51472,7 @@ function isUnderline(el) {
51151
51472
  * Builds a Partial<PptxElement> patch that merges the given changes into the
51152
51473
  * element's existing shapeStyle without dropping any other shapeStyle fields.
51153
51474
  */
51154
- function shapeStylePatch(el, changes) {
51475
+ function shapeStylePatch$1(el, changes) {
51155
51476
  const base = hasShapeProperties(el) ? (el.shapeStyle ?? {}) : {};
51156
51477
  return {
51157
51478
  shapeStyle: {
@@ -66163,16 +66484,24 @@ function overlayStored(base, stored) {
66163
66484
  * download, short enough not to leak the object URL.
66164
66485
  */
66165
66486
  /** Default fallback name used when a download filename is empty or unusable. */
66166
- const FALLBACK_DOWNLOAD_NAME = 'presentation.pptx';
66487
+ const FALLBACK_DOWNLOAD_NAME = 'presentation.pptx',
66488
+ /** Fallback stem used by {@link resolveExportBaseName} when no usable name remains. */
66489
+ EXPORT_BASE_NAME_FALLBACK = 'presentation',
66490
+ /**
66491
+ * Extensions {@link resolveExportBaseName} strips by default: every format the
66492
+ * export surface (PNG/PDF/GIF/WebM) or the original source deck (pptx) can be
66493
+ * named after. Bare, lower-case, no leading dot.
66494
+ */
66495
+ DEFAULT_EXPORT_STRIP_EXTENSIONS = ['pptx', 'pdf', 'png', 'gif', 'webm'],
66167
66496
  /** Revoke delay (ms) after triggering a download click. */
66168
- const REVOKE_DELAY_MS = 200;
66497
+ REVOKE_DELAY_MS = 200,
66169
66498
  /**
66170
66499
  * Revoke delay (ms) after opening a payload in a new tab. Longer than the
66171
66500
  * download delay: the new document may still be fetching the object URL (a PDF
66172
66501
  * viewer, an image) well after the current task finishes, so we keep the URL
66173
66502
  * alive for a minute before releasing it.
66174
66503
  */
66175
- const OPEN_REVOKE_DELAY_MS = 60_000;
66504
+ OPEN_REVOKE_DELAY_MS = 60_000;
66176
66505
  /**
66177
66506
  * Strip control characters, filesystem-reserved characters, and path-traversal
66178
66507
  * sequences from a user-supplied download filename. CR/LF in particular can
@@ -66213,6 +66542,28 @@ function sanitizeDownloadFilename(input) {
66213
66542
  }
66214
66543
  return cleaned;
66215
66544
  }
66545
+ /**
66546
+ * Derive the base (extensionless) file name for an export download from a
66547
+ * user-supplied source file name: trims whitespace, strips one trailing
66548
+ * extension from `extensions` (case-insensitive), and falls back to
66549
+ * `presentation` when `sourceName` is `undefined` or the result is empty.
66550
+ *
66551
+ * Used by every binding's export controller to turn a loaded deck's source
66552
+ * name (e.g. `Deck.pptx`) into the stem for its own export downloads (e.g.
66553
+ * `Deck-slide-1.png`, `Deck.gif`) without doubling up the extension.
66554
+ *
66555
+ * @param sourceName - The raw source file name, or `undefined` when none is
66556
+ * known yet.
66557
+ * @param extensions - Extensions to strip, without the leading dot. Defaults
66558
+ * to the full export-surface set (`pptx`, `pdf`, `png`, `gif`, `webm`).
66559
+ */
66560
+ function resolveExportBaseName(sourceName, extensions = DEFAULT_EXPORT_STRIP_EXTENSIONS) {
66561
+ if (sourceName === undefined) {
66562
+ return EXPORT_BASE_NAME_FALLBACK;
66563
+ }
66564
+ const pattern = new RegExp(`\\.(?:${extensions.join('|')})$`, 'iu'), trimmed = sourceName.trim().replace(pattern, '');
66565
+ return trimmed === '' ? EXPORT_BASE_NAME_FALLBACK : trimmed;
66566
+ }
66216
66567
  /**
66217
66568
  * Trigger a browser download for a Blob. The `filename` is used verbatim: pass a
66218
66569
  * name through {@link sanitizeDownloadFilename} first if it may be hostile.
@@ -66221,8 +66572,7 @@ function sanitizeDownloadFilename(input) {
66221
66572
  * @param filename - The suggested download name.
66222
66573
  */
66223
66574
  function downloadBlob(blob, filename) {
66224
- const url = URL.createObjectURL(blob);
66225
- const anchor = document.createElement('a');
66575
+ const url = URL.createObjectURL(blob), anchor = document.createElement('a');
66226
66576
  anchor.href = url;
66227
66577
  anchor.download = filename;
66228
66578
  document.body.appendChild(anchor);
@@ -66262,13 +66612,11 @@ function dataUrlToBlob(dataUrl) {
66262
66612
  if (!match?.groups) {
66263
66613
  return undefined;
66264
66614
  }
66265
- const mime = match.groups.mime || 'application/octet-stream';
66266
- const isBase64 = Boolean(match.groups.base64);
66267
- const payload = match.groups.payload ?? '';
66615
+ // eslint-disable-next-line one-var -- separated from `match` above by a guard clause
66616
+ const mime = match.groups.mime || 'application/octet-stream', isBase64 = Boolean(match.groups.base64), payload = match.groups.payload ?? '';
66268
66617
  try {
66269
66618
  if (isBase64) {
66270
- const binary = atob(payload);
66271
- const bytes = new Uint8Array(binary.length);
66619
+ const binary = atob(payload), bytes = new Uint8Array(binary.length);
66272
66620
  for (let i = 0; i < binary.length; i++) {
66273
66621
  bytes[i] = binary.charCodeAt(i);
66274
66622
  }
@@ -66291,14 +66639,13 @@ function dataUrlToBlob(dataUrl) {
66291
66639
  * @param url - The payload URL (typically a recovered `data:` URL).
66292
66640
  */
66293
66641
  function openUrlInNewTab(url) {
66294
- const blob = url.startsWith('data:') ? dataUrlToBlob(url) : undefined;
66295
- const target = blob ? URL.createObjectURL(blob) : url;
66642
+ const blob = url.startsWith('data:') ? dataUrlToBlob(url) : undefined, target = blob ? URL.createObjectURL(blob) : url,
66296
66643
  // NB: no `noopener` here. A `blob:` object URL is resolved from the opener's
66297
66644
  // origin-partitioned blob store, and Chromium refuses to resolve it in the
66298
66645
  // disconnected browsing context `noopener` creates (the new tab lands on an
66299
66646
  // empty document). We instead sever the child's back-reference to us
66300
66647
  // afterwards, which mitigates reverse-tabnabbing without breaking the blob.
66301
- const opened = window.open(target, '_blank');
66648
+ opened = window.open(target, '_blank');
66302
66649
  if (opened) {
66303
66650
  try {
66304
66651
  opened.opener = null;
@@ -70857,7 +71204,7 @@ function createLocalStorageBackend(namespace) {
70857
71204
  /** Try IndexedDB first; fall back to localStorage on any failure. */
70858
71205
  async function resolveBackend(dbName, namespace) {
70859
71206
  try {
70860
- const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-IYWfGg1_.mjs');
71207
+ const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-BwJthigo.mjs');
70861
71208
  const db = await openChatDb(dbName);
70862
71209
  return createIdbBackend(db);
70863
71210
  }
@@ -80675,7 +81022,11 @@ class ChartPrimitivesComponent {
80675
81022
  [attr.data-chart-part]="partRole(prim)"
80676
81023
  [attr.data-chart-series]="partSeries(prim)"
80677
81024
  [attr.data-chart-point]="partPoint(prim)"
80678
- />
81025
+ >
81026
+ @if (asRect(prim).title !== undefined) {
81027
+ <svg:title>{{ asRect(prim).title }}</svg:title>
81028
+ }
81029
+ </svg:rect>
80679
81030
  }
80680
81031
  @case ('path') {
80681
81032
  <svg:path
@@ -80709,7 +81060,11 @@ class ChartPrimitivesComponent {
80709
81060
  [attr.data-chart-part]="partRole(prim)"
80710
81061
  [attr.data-chart-series]="partSeries(prim)"
80711
81062
  [attr.data-chart-point]="partPoint(prim)"
80712
- />
81063
+ >
81064
+ @if (asPolyline(prim).title !== undefined) {
81065
+ <svg:title>{{ asPolyline(prim).title }}</svg:title>
81066
+ }
81067
+ </svg:polyline>
80713
81068
  }
80714
81069
  @case ('circle') {
80715
81070
  <svg:circle
@@ -80721,7 +81076,11 @@ class ChartPrimitivesComponent {
80721
81076
  [attr.data-chart-part]="partRole(prim)"
80722
81077
  [attr.data-chart-series]="partSeries(prim)"
80723
81078
  [attr.data-chart-point]="partPoint(prim)"
80724
- />
81079
+ >
81080
+ @if (asCircle(prim).title !== undefined) {
81081
+ <svg:title>{{ asCircle(prim).title }}</svg:title>
81082
+ }
81083
+ </svg:circle>
80725
81084
  }
80726
81085
  @case ('line') {
80727
81086
  <svg:line
@@ -80731,7 +81090,11 @@ class ChartPrimitivesComponent {
80731
81090
  [attr.y2]="asLine(prim).y2"
80732
81091
  [attr.stroke]="asLine(prim).stroke"
80733
81092
  [attr.stroke-width]="asLine(prim).strokeWidth"
80734
- />
81093
+ >
81094
+ @if (asLine(prim).title !== undefined) {
81095
+ <svg:title>{{ asLine(prim).title }}</svg:title>
81096
+ }
81097
+ </svg:line>
80735
81098
  }
80736
81099
  @case ('polygon') {
80737
81100
  <svg:polygon
@@ -80744,7 +81107,11 @@ class ChartPrimitivesComponent {
80744
81107
  [attr.data-chart-part]="partRole(prim)"
80745
81108
  [attr.data-chart-series]="partSeries(prim)"
80746
81109
  [attr.data-chart-point]="partPoint(prim)"
80747
- />
81110
+ >
81111
+ @if (asPolygon(prim).title !== undefined) {
81112
+ <svg:title>{{ asPolygon(prim).title }}</svg:title>
81113
+ }
81114
+ </svg:polygon>
80748
81115
  }
80749
81116
  @case ('text') {
80750
81117
  <svg:text
@@ -80786,7 +81153,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
80786
81153
  [attr.data-chart-part]="partRole(prim)"
80787
81154
  [attr.data-chart-series]="partSeries(prim)"
80788
81155
  [attr.data-chart-point]="partPoint(prim)"
80789
- />
81156
+ >
81157
+ @if (asRect(prim).title !== undefined) {
81158
+ <svg:title>{{ asRect(prim).title }}</svg:title>
81159
+ }
81160
+ </svg:rect>
80790
81161
  }
80791
81162
  @case ('path') {
80792
81163
  <svg:path
@@ -80820,7 +81191,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
80820
81191
  [attr.data-chart-part]="partRole(prim)"
80821
81192
  [attr.data-chart-series]="partSeries(prim)"
80822
81193
  [attr.data-chart-point]="partPoint(prim)"
80823
- />
81194
+ >
81195
+ @if (asPolyline(prim).title !== undefined) {
81196
+ <svg:title>{{ asPolyline(prim).title }}</svg:title>
81197
+ }
81198
+ </svg:polyline>
80824
81199
  }
80825
81200
  @case ('circle') {
80826
81201
  <svg:circle
@@ -80832,7 +81207,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
80832
81207
  [attr.data-chart-part]="partRole(prim)"
80833
81208
  [attr.data-chart-series]="partSeries(prim)"
80834
81209
  [attr.data-chart-point]="partPoint(prim)"
80835
- />
81210
+ >
81211
+ @if (asCircle(prim).title !== undefined) {
81212
+ <svg:title>{{ asCircle(prim).title }}</svg:title>
81213
+ }
81214
+ </svg:circle>
80836
81215
  }
80837
81216
  @case ('line') {
80838
81217
  <svg:line
@@ -80842,7 +81221,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
80842
81221
  [attr.y2]="asLine(prim).y2"
80843
81222
  [attr.stroke]="asLine(prim).stroke"
80844
81223
  [attr.stroke-width]="asLine(prim).strokeWidth"
80845
- />
81224
+ >
81225
+ @if (asLine(prim).title !== undefined) {
81226
+ <svg:title>{{ asLine(prim).title }}</svg:title>
81227
+ }
81228
+ </svg:line>
80846
81229
  }
80847
81230
  @case ('polygon') {
80848
81231
  <svg:polygon
@@ -80855,7 +81238,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
80855
81238
  [attr.data-chart-part]="partRole(prim)"
80856
81239
  [attr.data-chart-series]="partSeries(prim)"
80857
81240
  [attr.data-chart-point]="partPoint(prim)"
80858
- />
81241
+ >
81242
+ @if (asPolygon(prim).title !== undefined) {
81243
+ <svg:title>{{ asPolygon(prim).title }}</svg:title>
81244
+ }
81245
+ </svg:polygon>
80859
81246
  }
80860
81247
  @case ('text') {
80861
81248
  <svg:text
@@ -80904,7 +81291,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
80904
81291
  * Input: `element` - required `PptxElement` narrowed to `type === 'chart'`
80905
81292
  */
80906
81293
  const LEGEND_SWATCH_SIZE = 10;
80907
- const LEGEND_ITEM_WIDTH = 80;
80908
81294
  class ChartRendererComponent {
80909
81295
  constructor() {
80910
81296
  this.element = input.required(/* @ts-ignore */
@@ -80914,15 +81300,11 @@ class ChartRendererComponent {
80914
81300
  this.viewBox = computed(() => `0 0 ${this.vm().svgWidth} ${this.vm().svgHeight}`, /* @ts-ignore */
80915
81301
  ...(ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []));
80916
81302
  this.swatchSize = LEGEND_SWATCH_SIZE;
81303
+ this.legendItems = computed(() => computeChartLegendLayout(this.vm()), /* @ts-ignore */
81304
+ ...(ngDevMode ? [{ debugName: "legendItems" }] : /* istanbul ignore next */ []));
80917
81305
  }
80918
- legendTransform(index) {
80919
- const v = this.vm();
80920
- const isVertical = v.legendAnchor === 'start';
80921
- const x = isVertical
80922
- ? v.legendX
80923
- : v.legendX - (v.legend.length * LEGEND_ITEM_WIDTH) / 2 + index * LEGEND_ITEM_WIDTH;
80924
- const y = isVertical ? v.legendY + index * 14 : v.legendY;
80925
- return `translate(${x.toFixed(1)},${y.toFixed(1)})`;
81306
+ legendTransform(item) {
81307
+ return `translate(${item.x.toFixed(1)},${item.y.toFixed(1)})`;
80926
81308
  }
80927
81309
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ChartRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
80928
81310
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: ChartRendererComponent, isStandalone: true, selector: "pptx-chart-renderer", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
@@ -81061,19 +81443,19 @@ class ChartRendererComponent {
81061
81443
  }
81062
81444
 
81063
81445
  <!-- Legend -->
81064
- @if (vm().legend.length > 0) {
81065
- @for (entry of vm().legend; track $index) {
81066
- <g [attr.transform]="legendTransform($index)">
81446
+ @if (legendItems().length > 0) {
81447
+ @for (item of legendItems(); track $index) {
81448
+ <g [attr.transform]="legendTransform(item)">
81067
81449
  <rect
81068
81450
  x="0"
81069
81451
  y="-7"
81070
81452
  [attr.width]="swatchSize"
81071
81453
  [attr.height]="swatchSize"
81072
81454
  rx="2"
81073
- [attr.fill]="entry.color"
81455
+ [attr.fill]="item.color"
81074
81456
  />
81075
81457
  <text [attr.x]="swatchSize + 3" y="3" font-size="9" fill="#475569">
81076
- {{ entry.label }}
81458
+ {{ item.label }}
81077
81459
  </text>
81078
81460
  </g>
81079
81461
  }
@@ -81224,19 +81606,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
81224
81606
  }
81225
81607
 
81226
81608
  <!-- Legend -->
81227
- @if (vm().legend.length > 0) {
81228
- @for (entry of vm().legend; track $index) {
81229
- <g [attr.transform]="legendTransform($index)">
81609
+ @if (legendItems().length > 0) {
81610
+ @for (item of legendItems(); track $index) {
81611
+ <g [attr.transform]="legendTransform(item)">
81230
81612
  <rect
81231
81613
  x="0"
81232
81614
  y="-7"
81233
81615
  [attr.width]="swatchSize"
81234
81616
  [attr.height]="swatchSize"
81235
81617
  rx="2"
81236
- [attr.fill]="entry.color"
81618
+ [attr.fill]="item.color"
81237
81619
  />
81238
81620
  <text [attr.x]="swatchSize + 3" y="3" font-size="9" fill="#475569">
81239
- {{ entry.label }}
81621
+ {{ item.label }}
81240
81622
  </text>
81241
81623
  </g>
81242
81624
  }
@@ -88739,36 +89121,40 @@ class MobileBottomBarComponent {
88739
89121
  /** User tapped the Notes button. */
88740
89122
  this.notes = output();
88741
89123
  // ── Derived action list ───────────────────────────────────────────────────
89124
+ // Shared `buildBarActions` decides which slots are disabled (no slides
89125
+ // loaded); this binding only maps the resulting descriptor's `key` and
89126
+ // `disabled` onto its own labels, icons and outputs.
88742
89127
  this.actions = computed(() => {
88743
- const count = this.slideCount();
88744
- const noSlides = count === 0;
88745
- const active = this.activeSheet();
89128
+ const active = this.activeSheet(), disabledByKey = new Map(buildBarActions({ slideCount: this.slideCount() }).map((descriptor) => [
89129
+ descriptor.key,
89130
+ descriptor.disabled,
89131
+ ]));
88746
89132
  return [
88747
89133
  {
88748
89134
  key: 'slides',
88749
89135
  labelKey: 'pptx.sections.slides',
88750
- disabled: noSlides,
89136
+ disabled: disabledByKey.get('slides') ?? false,
88751
89137
  active: active === 'slides',
88752
89138
  emit: () => this.openSlides.emit(),
88753
89139
  },
88754
89140
  {
88755
89141
  key: 'insert',
88756
89142
  labelKey: 'pptx.editorToolbar.insert',
88757
- disabled: noSlides,
89143
+ disabled: disabledByKey.get('insert') ?? false,
88758
89144
  active: false,
88759
89145
  emit: () => this.insert.emit(),
88760
89146
  },
88761
89147
  {
88762
89148
  key: 'inspector',
88763
89149
  labelKey: 'pptx.arrange.format',
88764
- disabled: noSlides,
89150
+ disabled: disabledByKey.get('inspector') ?? false,
88765
89151
  active: active === 'inspector',
88766
89152
  emit: () => this.openFormat.emit(),
88767
89153
  },
88768
89154
  {
88769
89155
  key: 'comments',
88770
89156
  labelKey: 'pptx.toolbar.comments',
88771
- disabled: noSlides,
89157
+ disabled: disabledByKey.get('comments') ?? false,
88772
89158
  active: active === 'comments',
88773
89159
  badge: this.commentCount(),
88774
89160
  emit: () => this.openComments.emit(),
@@ -88777,7 +89163,7 @@ class MobileBottomBarComponent {
88777
89163
  key: 'notes',
88778
89164
  labelKey: 'pptx.notes.title',
88779
89165
  ariaLabelKey: 'pptx.statusBar.toggleNotes',
88780
- disabled: noSlides,
89166
+ disabled: disabledByKey.get('notes') ?? false,
88781
89167
  active: active === 'notes',
88782
89168
  emit: () => this.notes.emit(),
88783
89169
  },
@@ -104025,9 +104411,138 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
104025
104411
  }]
104026
104412
  }], propDecorators: { slideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideIndex", required: false }] }], selectedElement: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedElement", required: false }] }], canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], formatPainterActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatPainterActive", required: false }] }], canActivateFormatPainter: [{ type: i0.Input, args: [{ isSignal: true, alias: "canActivateFormatPainter", required: false }] }], toggleFormatPainter: [{ type: i0.Output, args: ["toggleFormatPainter"] }] } });
104027
104413
 
104414
+ /**
104415
+ * ribbon-color-popover.component.ts: the hover-reveal swatch popover used for the
104416
+ * Font-colour and Text-highlight pickers in {@link RibbonFontControlsComponent}.
104417
+ * The trigger icon is projected via `<ng-content>`; the swatch grid + custom
104418
+ * colour input are shared. Split out to keep the font controls under the 300-LOC
104419
+ * cap; behaviour and markup are unchanged.
104420
+ */
104421
+ class RibbonColorPopoverComponent {
104422
+ constructor() {
104423
+ this.current = input('#000000', /* @ts-ignore */
104424
+ ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
104425
+ this.presets = input([], /* @ts-ignore */
104426
+ ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
104427
+ this.disabled = input(false, /* @ts-ignore */
104428
+ ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
104429
+ this.titleKey = input('', /* @ts-ignore */
104430
+ ...(ngDevMode ? [{ debugName: "titleKey" }] : /* istanbul ignore next */ []));
104431
+ this.swatchAriaKey = input('', /* @ts-ignore */
104432
+ ...(ngDevMode ? [{ debugName: "swatchAriaKey" }] : /* istanbul ignore next */ []));
104433
+ this.pick = output();
104434
+ }
104435
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: RibbonColorPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
104436
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: RibbonColorPopoverComponent, isStandalone: true, selector: "pptx-ribbon-color-popover", inputs: { current: { classPropertyName: "current", publicName: "current", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, swatchAriaKey: { classPropertyName: "swatchAriaKey", publicName: "swatchAriaKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pick: "pick" }, ngImport: i0, template: `
104437
+ <div class="group relative">
104438
+ <button
104439
+ type="button"
104440
+ class="pptx-rb-pill"
104441
+ [disabled]="disabled()"
104442
+ [title]="titleKey() | translate"
104443
+ (mousedown)="$event.preventDefault()"
104444
+ >
104445
+ <ng-content />
104446
+ <span class="-mt-0.5 block h-1 w-4 rounded-sm" [style.background]="current()"></span>
104447
+ </button>
104448
+ <div class="absolute left-0 top-full z-50 hidden pt-1 group-hover:block">
104449
+ <div class="w-36 rounded-lg border border-border bg-card p-2 shadow-2xl">
104450
+ <div class="mb-2 grid grid-cols-5 gap-1.5">
104451
+ @for (c of presets(); track c) {
104452
+ <button
104453
+ type="button"
104454
+ data-pptx-compact
104455
+ class="h-5 w-5 rounded-full border transition-transform hover:scale-125"
104456
+ [ngClass]="
104457
+ current().toLowerCase() === c
104458
+ ? 'border-primary ring-1 ring-primary'
104459
+ : 'border-border'
104460
+ "
104461
+ [style.background]="c"
104462
+ [attr.aria-label]="swatchAriaKey() | translate: { color: c }"
104463
+ (mousedown)="$event.preventDefault()"
104464
+ (click)="pick.emit(c)"
104465
+ ></button>
104466
+ }
104467
+ </div>
104468
+ <label
104469
+ class="block w-full cursor-pointer py-1 text-center text-[10px] text-muted-foreground transition-colors hover:text-foreground"
104470
+ >
104471
+ {{ 'pptx.ribbon.customColour' | translate }}
104472
+ <input
104473
+ type="color"
104474
+ class="sr-only"
104475
+ [value]="current()"
104476
+ (change)="pick.emit($any($event.target).value)"
104477
+ />
104478
+ </label>
104479
+ </div>
104480
+ </div>
104481
+ </div>
104482
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
104483
+ }
104484
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: RibbonColorPopoverComponent, decorators: [{
104485
+ type: Component,
104486
+ args: [{
104487
+ selector: 'pptx-ribbon-color-popover',
104488
+ standalone: true,
104489
+ changeDetection: ChangeDetectionStrategy.OnPush,
104490
+ imports: [NgClass, TranslatePipe],
104491
+ template: `
104492
+ <div class="group relative">
104493
+ <button
104494
+ type="button"
104495
+ class="pptx-rb-pill"
104496
+ [disabled]="disabled()"
104497
+ [title]="titleKey() | translate"
104498
+ (mousedown)="$event.preventDefault()"
104499
+ >
104500
+ <ng-content />
104501
+ <span class="-mt-0.5 block h-1 w-4 rounded-sm" [style.background]="current()"></span>
104502
+ </button>
104503
+ <div class="absolute left-0 top-full z-50 hidden pt-1 group-hover:block">
104504
+ <div class="w-36 rounded-lg border border-border bg-card p-2 shadow-2xl">
104505
+ <div class="mb-2 grid grid-cols-5 gap-1.5">
104506
+ @for (c of presets(); track c) {
104507
+ <button
104508
+ type="button"
104509
+ data-pptx-compact
104510
+ class="h-5 w-5 rounded-full border transition-transform hover:scale-125"
104511
+ [ngClass]="
104512
+ current().toLowerCase() === c
104513
+ ? 'border-primary ring-1 ring-primary'
104514
+ : 'border-border'
104515
+ "
104516
+ [style.background]="c"
104517
+ [attr.aria-label]="swatchAriaKey() | translate: { color: c }"
104518
+ (mousedown)="$event.preventDefault()"
104519
+ (click)="pick.emit(c)"
104520
+ ></button>
104521
+ }
104522
+ </div>
104523
+ <label
104524
+ class="block w-full cursor-pointer py-1 text-center text-[10px] text-muted-foreground transition-colors hover:text-foreground"
104525
+ >
104526
+ {{ 'pptx.ribbon.customColour' | translate }}
104527
+ <input
104528
+ type="color"
104529
+ class="sr-only"
104530
+ [value]="current()"
104531
+ (change)="pick.emit($any($event.target).value)"
104532
+ />
104533
+ </label>
104534
+ </div>
104535
+ </div>
104536
+ </div>
104537
+ `,
104538
+ }]
104539
+ }], propDecorators: { current: [{ type: i0.Input, args: [{ isSignal: true, alias: "current", required: false }] }], presets: [{ type: i0.Input, args: [{ isSignal: true, alias: "presets", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], swatchAriaKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "swatchAriaKey", required: false }] }], pick: [{ type: i0.Output, args: ["pick"] }] } });
104540
+
104028
104541
  /**
104029
104542
  * ribbon-drawing-group.component.ts: Drawing group for the Home tab ribbon.
104030
- * Provides shape insertion, layer arrangement, and shape formatting placeholders.
104543
+ * Provides shape insertion, layer arrangement, and shape Fill/Outline colour
104544
+ * pickers (Shape Effects stays a disabled placeholder: it is genuinely
104545
+ * unimplemented in every binding).
104031
104546
  *
104032
104547
  * The Arrange menu also carries Group / Ungroup, which is where PowerPoint
104033
104548
  * puts them (Home > Drawing > Arrange) and where the other bindings expose
@@ -104040,9 +104555,50 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
104040
104555
  * The picker lists the first 12 entries of the shared preset catalogue
104041
104556
  * ({@link SHAPE_PRESET_DEFS}), so the geometry ids are valid OOXML
104042
104557
  * `a:prstGeom` values shared with every binding.
104558
+ *
104559
+ * Fill/Outline commit through `pptx-viewer-shared`'s `shapeFillChange` /
104560
+ * `shapeOutlineChange` decision functions (the same ones React's and Vue's
104561
+ * DrawingGroup use), so the two keys written (`fillColor`+`fillMode` /
104562
+ * `strokeColor`) can't drift from the other bindings a third time. The
104563
+ * buttons used to be `disabled` placeholders that did nothing when clicked.
104043
104564
  */
104044
- /** The quick "top shapes" row shared with React's toolbar (first 12 presets). */
104045
- const TOP_SHAPES = SHAPE_PRESET_DEFS.slice(0, 12);
104565
+ /**
104566
+ * The quick "top shapes" row shared with React's toolbar (first 12 presets),
104567
+ * and the fallbacks the Fill/Outline swatch dots show when the selection has
104568
+ * none set (declared together: oxlint's `one-var` wants one `const` statement
104569
+ * per top-level scope).
104570
+ */
104571
+ const TOP_SHAPES = SHAPE_PRESET_DEFS.slice(0, 12), DEFAULT_FILL_COLOR = '#ffffff', DEFAULT_OUTLINE_COLOR = '#000000';
104572
+ /** Fill/Outline only apply to an editable, selected shape-like element. */
104573
+ function canFormatShapeSelection(canEdit, element) {
104574
+ return canEdit && element !== null && hasShapeProperties(element);
104575
+ }
104576
+ /** The colour the Fill swatch dot shows for the current selection. */
104577
+ function fillColorOf(element) {
104578
+ if (element === null || !hasShapeProperties(element)) {
104579
+ return DEFAULT_FILL_COLOR;
104580
+ }
104581
+ return element.shapeStyle?.fillColor ?? DEFAULT_FILL_COLOR;
104582
+ }
104583
+ /** The colour the Outline swatch dot shows for the current selection. */
104584
+ function outlineColorOf(element) {
104585
+ if (element === null || !hasShapeProperties(element)) {
104586
+ return DEFAULT_OUTLINE_COLOR;
104587
+ }
104588
+ return element.shapeStyle?.strokeColor ?? DEFAULT_OUTLINE_COLOR;
104589
+ }
104590
+ /**
104591
+ * The element patch a Fill/Outline swatch pick commits, or `undefined` when
104592
+ * the selection has no shape style to patch. Merges into the EXISTING
104593
+ * `shapeStyle` (not a replace) so picking a fill colour never clears an
104594
+ * already-set outline, and vice versa.
104595
+ */
104596
+ function shapeStylePatch(element, style) {
104597
+ if (element === null || !hasShapeProperties(element)) {
104598
+ return undefined;
104599
+ }
104600
+ return { shapeStyle: { ...element.shapeStyle, ...style } };
104601
+ }
104046
104602
  class RibbonDrawingGroupComponent {
104047
104603
  constructor() {
104048
104604
  this.editor = inject(EditorStateService);
@@ -104051,11 +104607,36 @@ class RibbonDrawingGroupComponent {
104051
104607
  /** Index of the active slide (insertion target). */
104052
104608
  this.slideIndex = input(0, /* @ts-ignore */
104053
104609
  ...(ngDevMode ? [{ debugName: "slideIndex" }] : /* istanbul ignore next */ []));
104610
+ this.selectedElement = input(null, /* @ts-ignore */
104611
+ ...(ngDevMode ? [{ debugName: "selectedElement" }] : /* istanbul ignore next */ []));
104054
104612
  this.shapes = TOP_SHAPES;
104055
104613
  this.shapesOpen = signal(false, /* @ts-ignore */
104056
104614
  ...(ngDevMode ? [{ debugName: "shapesOpen" }] : /* istanbul ignore next */ []));
104057
104615
  this.arrangeOpen = signal(false, /* @ts-ignore */
104058
104616
  ...(ngDevMode ? [{ debugName: "arrangeOpen" }] : /* istanbul ignore next */ []));
104617
+ this.swatches = RIBBON_SHAPE_SWATCHES;
104618
+ this.canFormatShape = computed(() => canFormatShapeSelection(this.canEdit(), this.selectedElement()), /* @ts-ignore */
104619
+ ...(ngDevMode ? [{ debugName: "canFormatShape" }] : /* istanbul ignore next */ []));
104620
+ this.fillColor = computed(() => fillColorOf(this.selectedElement()), /* @ts-ignore */
104621
+ ...(ngDevMode ? [{ debugName: "fillColor" }] : /* istanbul ignore next */ []));
104622
+ this.outlineColor = computed(() => outlineColorOf(this.selectedElement()), /* @ts-ignore */
104623
+ ...(ngDevMode ? [{ debugName: "outlineColor" }] : /* istanbul ignore next */ []));
104624
+ }
104625
+ /** Commit a picked Fill swatch through the shared decision function. */
104626
+ onFill(color) {
104627
+ this.patchShapeStyle(shapeFillChange(color));
104628
+ }
104629
+ /** Commit a picked Outline swatch through the shared decision function. */
104630
+ onOutline(color) {
104631
+ this.patchShapeStyle(shapeOutlineChange(color));
104632
+ }
104633
+ /** Merge a Fill/Outline patch into the selection's shape style, if it has one. */
104634
+ patchShapeStyle(style) {
104635
+ const el = this.selectedElement(), patch = shapeStylePatch(el, style);
104636
+ if (!this.canFormatShape() || el === null || !patch) {
104637
+ return;
104638
+ }
104639
+ this.editor.updateElement(this.slideIndex(), el.id, patch);
104059
104640
  }
104060
104641
  /** Insert the picked preset immediately (selects it and records history). */
104061
104642
  onShapeSelect(shape) {
@@ -104088,7 +104669,7 @@ class RibbonDrawingGroupComponent {
104088
104669
  this.editor.sendSelectedToBack(this.slideIndex());
104089
104670
  }
104090
104671
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: RibbonDrawingGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
104091
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: RibbonDrawingGroupComponent, isStandalone: true, selector: "pptx-ribbon-drawing-group", inputs: { canEdit: { classPropertyName: "canEdit", publicName: "canEdit", isSignal: true, isRequired: false, transformFunction: null }, slideIndex: { classPropertyName: "slideIndex", publicName: "slideIndex", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "contents" }, ngImport: i0, template: `
104672
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: RibbonDrawingGroupComponent, isStandalone: true, selector: "pptx-ribbon-drawing-group", inputs: { canEdit: { classPropertyName: "canEdit", publicName: "canEdit", isSignal: true, isRequired: false, transformFunction: null }, slideIndex: { classPropertyName: "slideIndex", publicName: "slideIndex", isSignal: true, isRequired: false, transformFunction: null }, selectedElement: { classPropertyName: "selectedElement", publicName: "selectedElement", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "contents" }, ngImport: i0, template: `
104092
104673
  <!-- Shapes -->
104093
104674
  <div class="flex flex-col items-center gap-0.5">
104094
104675
  <div class="pptx-rb-grp">
@@ -104185,16 +104766,36 @@ class RibbonDrawingGroupComponent {
104185
104766
  </span>
104186
104767
  </div>
104187
104768
  <span class="pptx-rb-sep"></span>
104188
- <!-- Shape formatting (placeholders) -->
104769
+ <!-- Shape formatting -->
104189
104770
  <div class="flex flex-col items-center gap-0.5">
104190
- <div class="pptx-rb-grp">
104191
- <button type="button" class="pptx-rb-gb" disabled>
104771
+ <div class="flex items-center gap-1">
104772
+ <pptx-ribbon-color-popover
104773
+ [current]="fillColor()"
104774
+ [presets]="swatches"
104775
+ [disabled]="!canFormatShape()"
104776
+ titleKey="pptx.drawing.shapeFill"
104777
+ swatchAriaKey="pptx.ribbon.fillColourValue"
104778
+ (pick)="onFill($event)"
104779
+ >
104192
104780
  {{ 'pptx.drawing.shapeFill' | translate }}
104193
- </button>
104194
- <button type="button" class="pptx-rb-gb" disabled>
104781
+ </pptx-ribbon-color-popover>
104782
+ <pptx-ribbon-color-popover
104783
+ [current]="outlineColor()"
104784
+ [presets]="swatches"
104785
+ [disabled]="!canFormatShape()"
104786
+ titleKey="pptx.drawing.shapeOutline"
104787
+ swatchAriaKey="pptx.ribbon.outlineColourValue"
104788
+ (pick)="onOutline($event)"
104789
+ >
104195
104790
  {{ 'pptx.drawing.shapeOutline' | translate }}
104196
- </button>
104197
- <button type="button" class="pptx-rb-gb" disabled>
104791
+ </pptx-ribbon-color-popover>
104792
+ <!-- Shape Effects: genuinely unimplemented (React/Vue parity), left as a placeholder. -->
104793
+ <button
104794
+ type="button"
104795
+ class="pptx-rb-gb"
104796
+ disabled
104797
+ [title]="'pptx.drawing.shapeEffectsUnavailable' | translate"
104798
+ >
104198
104799
  {{ 'pptx.drawing.shapeEffectsUnavailable' | translate }}
104199
104800
  </button>
104200
104801
  </div>
@@ -104202,7 +104803,7 @@ class RibbonDrawingGroupComponent {
104202
104803
  {{ 'pptx.ribbon.groupShapeStyles' | translate }}
104203
104804
  </span>
104204
104805
  </div>
104205
- `, isInline: true, dependencies: [{ kind: "component", type: LucideChevronDown, selector: "svg[lucideChevronDown]" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
104806
+ `, isInline: true, dependencies: [{ kind: "component", type: LucideChevronDown, selector: "svg[lucideChevronDown]" }, { kind: "component", type: RibbonColorPopoverComponent, selector: "pptx-ribbon-color-popover", inputs: ["current", "presets", "disabled", "titleKey", "swatchAriaKey"], outputs: ["pick"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
104206
104807
  }
104207
104808
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: RibbonDrawingGroupComponent, decorators: [{
104208
104809
  type: Component,
@@ -104211,7 +104812,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
104211
104812
  standalone: true,
104212
104813
  changeDetection: ChangeDetectionStrategy.OnPush,
104213
104814
  host: { class: 'contents' },
104214
- imports: [TranslatePipe, LucideChevronDown],
104815
+ imports: [TranslatePipe, LucideChevronDown, RibbonColorPopoverComponent],
104215
104816
  template: `
104216
104817
  <!-- Shapes -->
104217
104818
  <div class="flex flex-col items-center gap-0.5">
@@ -104309,16 +104910,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
104309
104910
  </span>
104310
104911
  </div>
104311
104912
  <span class="pptx-rb-sep"></span>
104312
- <!-- Shape formatting (placeholders) -->
104913
+ <!-- Shape formatting -->
104313
104914
  <div class="flex flex-col items-center gap-0.5">
104314
- <div class="pptx-rb-grp">
104315
- <button type="button" class="pptx-rb-gb" disabled>
104915
+ <div class="flex items-center gap-1">
104916
+ <pptx-ribbon-color-popover
104917
+ [current]="fillColor()"
104918
+ [presets]="swatches"
104919
+ [disabled]="!canFormatShape()"
104920
+ titleKey="pptx.drawing.shapeFill"
104921
+ swatchAriaKey="pptx.ribbon.fillColourValue"
104922
+ (pick)="onFill($event)"
104923
+ >
104316
104924
  {{ 'pptx.drawing.shapeFill' | translate }}
104317
- </button>
104318
- <button type="button" class="pptx-rb-gb" disabled>
104925
+ </pptx-ribbon-color-popover>
104926
+ <pptx-ribbon-color-popover
104927
+ [current]="outlineColor()"
104928
+ [presets]="swatches"
104929
+ [disabled]="!canFormatShape()"
104930
+ titleKey="pptx.drawing.shapeOutline"
104931
+ swatchAriaKey="pptx.ribbon.outlineColourValue"
104932
+ (pick)="onOutline($event)"
104933
+ >
104319
104934
  {{ 'pptx.drawing.shapeOutline' | translate }}
104320
- </button>
104321
- <button type="button" class="pptx-rb-gb" disabled>
104935
+ </pptx-ribbon-color-popover>
104936
+ <!-- Shape Effects: genuinely unimplemented (React/Vue parity), left as a placeholder. -->
104937
+ <button
104938
+ type="button"
104939
+ class="pptx-rb-gb"
104940
+ disabled
104941
+ [title]="'pptx.drawing.shapeEffectsUnavailable' | translate"
104942
+ >
104322
104943
  {{ 'pptx.drawing.shapeEffectsUnavailable' | translate }}
104323
104944
  </button>
104324
104945
  </div>
@@ -104328,10 +104949,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
104328
104949
  </div>
104329
104950
  `,
104330
104951
  }]
104331
- }], propDecorators: { canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], slideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideIndex", required: false }] }] } });
104952
+ }], 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 }] }] } });
104332
104953
 
104333
104954
  // Generated by scripts/inline-shared.mjs from package.json. Do not edit.
104334
- const PPTX_ANGULAR_VIEWER_VERSION = "2.18.6";
104955
+ const PPTX_ANGULAR_VIEWER_VERSION = "2.18.7";
104335
104956
 
104336
104957
  /**
104337
104958
  * account-page.component.ts: File > Account content.
@@ -104682,133 +105303,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
104682
105303
  args: [{ selector: 'pptx-ribbon-file-section', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'contents' }, imports: [BackstageNavIconComponent, AccountPageComponent, TranslatePipe], template: "<div\n\tclass=\"bs\"\n\trole=\"dialog\"\n\taria-modal=\"true\"\n\t[attr.aria-label]=\"'pptx.backstage.title' | translate\"\n>\n\t<aside>\n\t\t<button\n\t\t\tclass=\"back\"\n\t\t\ttype=\"button\"\n\t\t\t[attr.aria-label]=\"'pptx.backstage.back' | translate\"\n\t\t\t(click)=\"close.emit()\"\n\t\t>\n\t\t\t<pptx-backstage-nav-icon page=\"back\" />\n\t\t</button>\n\t\t<nav>\n\t\t\t@for (item of mainNav(); track item.id) {\n\t\t\t\t<button type=\"button\" [class.active]=\"page() === item.id\" (click)=\"selectPage(item.id)\">\n\t\t\t\t\t<span><pptx-backstage-nav-icon [page]=\"item.id\" /></span>{{ item.labelKey | translate }}\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t<i></i>\n\t\t\t@for (item of footerNav; track item.id) {\n\t\t\t\t<button type=\"button\" [class.active]=\"page() === item.id\" (click)=\"selectPage(item.id)\">\n\t\t\t\t\t<span><pptx-backstage-nav-icon [page]=\"item.id\" /></span>{{ item.labelKey | translate }}\n\t\t\t\t</button>\n\t\t\t}\n\t\t</nav>\n\t</aside>\n\t<main>\n\t\t<h1>{{ (page() === 'home' ? 'pptx.backstage.greeting' : titleKey()) | translate }}</h1>\n\t\t@if (page() === 'home' || page() === 'new') {\n\t\t\t<h2>{{ 'pptx.backstage.newHeading' | translate }}</h2>\n\t\t\t<div class=\"templates\">\n\t\t\t\t@for (template of templates; track template.id) {\n\t\t\t\t\t<button type=\"button\" (click)=\"create(template.id)\">\n\t\t\t\t\t\t<b [style.background]=\"template.preview\"></b\n\t\t\t\t\t\t><strong>{{ template.nameKey | translate }}</strong\n\t\t\t\t\t\t><small>{{ template.descriptionKey | translate }}</small>\n\t\t\t\t\t</button>\n\t\t\t\t}\n\t\t\t</div>\n\t\t}\n\t\t@if (page() === 'home' || page() === 'open') {\n\t\t\t<input\n\t\t\t\tclass=\"search\"\n\t\t\t\ttype=\"search\"\n\t\t\t\t[attr.placeholder]=\"'pptx.backstage.searchPlaceholder' | translate\"\n\t\t\t\t[value]=\"query()\"\n\t\t\t\t(input)=\"query.set($any($event.target).value)\"\n\t\t\t/>\n\t\t\t@if (page() === 'open') {\n\t\t\t\t<button class=\"primary\" type=\"button\" (click)=\"run(openFile)\">\n\t\t\t\t\t{{ 'pptx.backstage.browseDevice' | translate }}\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t<h2>{{ 'pptx.backstage.recentHeading' | translate }}</h2>\n\t\t\t<div class=\"recent\">\n\t\t\t\t<header>\n\t\t\t\t\t<span>{{ 'pptx.backstage.columnName' | translate }}</span\n\t\t\t\t\t><span>{{ 'pptx.backstage.columnModified' | translate }}</span\n\t\t\t\t\t><span>{{ 'pptx.backstage.columnSize' | translate }}</span>\n\t\t\t\t</header>\n\t\t\t\t@for (file of visibleRecent(); track file.key) {\n\t\t\t\t\t<button type=\"button\" (click)=\"openRecent(file.key)\">\n\t\t\t\t\t\t<span class=\"name\"\n\t\t\t\t\t\t\t><b>P</b\n\t\t\t\t\t\t\t><span\n\t\t\t\t\t\t\t\t><strong>{{ file.name }}</strong\n\t\t\t\t\t\t\t\t><small>{{ file.location }}</small></span\n\t\t\t\t\t\t\t></span\n\t\t\t\t\t\t><span>{{ date(file.timestamp) }}</span\n\t\t\t\t\t\t><span>{{ size(file.size) }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t} @empty {\n\t\t\t\t\t<p>{{ 'pptx.backstage.noRecent' | translate }}</p>\n\t\t\t\t}\n\t\t\t</div>\n\t\t}\n\t\t@if (actions().length) {\n\t\t\t<div class=\"actions\">\n\t\t\t\t@for (action of actions(); track action.titleKey) {\n\t\t\t\t\t<button type=\"button\" (click)=\"run(action.event)\">\n\t\t\t\t\t\t<b>{{ action.icon }}</b\n\t\t\t\t\t\t><span\n\t\t\t\t\t\t\t><strong>{{ action.titleKey | translate }}</strong\n\t\t\t\t\t\t\t><small>{{ action.bodyKey | translate }}</small></span\n\t\t\t\t\t\t>\n\t\t\t\t\t</button>\n\t\t\t\t}\n\t\t\t</div>\n\t\t}\n\t\t@if (page() === 'account') {\n\t\t\t<pptx-account-page [accountAuth]=\"accountAuth()\" />\n\t\t}\n\t\t@if (page() === 'options') {\n\t\t\t<section class=\"card\">\n\t\t\t\t<b class=\"avatar\">\u2699</b>\n\t\t\t\t<h2>{{ 'pptx.backstage.optionsTitle' | translate }}</h2>\n\t\t\t\t<p>{{ 'pptx.backstage.optionsBody' | translate }}</p>\n\t\t\t\t<button class=\"primary\" type=\"button\" (click)=\"run(options)\">\n\t\t\t\t\t{{ 'pptx.backstage.openOptions' | translate }}\n\t\t\t\t</button>\n\t\t\t</section>\n\t\t}\n\t\t<footer>\n\t\t\t{{ fileName() || ('pptx.backstage.untitled' | translate) }} \u00B7\n\t\t\t{{ 'pptx.backstage.savedToBrowser' | translate }}\n\t\t</footer>\n\t</main>\n</div>\n", styles: [":host{font-family:Aptos,Segoe UI,sans-serif}.bs{position:fixed;inset:0;z-index:200;display:flex;background:var(--pptx-background, #fafafa);color:var(--pptx-foreground, #242424)}.bs aside{display:flex;width:148px;flex:none;flex-direction:column;border-right:1px solid var(--pptx-border, #d7d7d7);background:var(--pptx-secondary, #f5eee9)}.back{height:48px;border:0;border-bottom:1px solid var(--pptx-border, #ddd);background:none;color:inherit;font-size:22px}.back:hover,nav button:hover,.recent>button:hover{background:var(--pptx-accent, #eadfd8)}nav{display:flex;min-height:0;flex:1;flex-direction:column;padding:8px 0}nav i{flex:1}nav button{display:flex;min-height:40px;align-items:center;gap:12px;padding:0 16px;border:0;border-left:2px solid transparent;background:none;color:inherit;font-size:12px;text-align:left}nav button span{width:16px;font-size:16px;text-align:center}nav button.active{border-left-color:var(--pptx-primary, #c43e1c);background:var(--pptx-card, #fff);color:var(--pptx-primary, #c43e1c)}main{min-width:0;flex:1;overflow:auto;padding:20px clamp(32px,4vw,72px)}h1{margin:0;font-size:24px;font-weight:600}h2{margin:28px 0 18px;font-size:17px}.templates{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:24px}.templates button{border:0;background:none;color:inherit;text-align:left}.templates button>b{display:block;aspect-ratio:16/9;border:1px solid var(--pptx-border, #ccc);box-shadow:0 1px 2px #0002;transition:.15s}.templates button:hover>b{transform:translateY(-2px);border-color:var(--pptx-primary, #c43e1c);box-shadow:0 7px 18px #0002}.templates strong,.templates small{display:block;overflow:hidden;margin-top:8px;font-size:12px;text-overflow:ellipsis;white-space:nowrap}.templates small{margin-top:2px;color:var(--pptx-muted-foreground, #777);font-size:10px}.search{display:block;width:min(540px,100%);height:40px;margin-top:32px;padding:0 14px;border:1px solid var(--pptx-input, #888);background:var(--pptx-card, #fff);color:var(--pptx-card-foreground, #242424)}.search:focus{border-color:var(--pptx-ring, #c43e1c);outline:none}.primary{margin-top:16px;padding:10px 20px;border:0;background:var(--pptx-primary, #c43e1c);color:var(--pptx-primary-foreground, #fff);font-weight:600}.recent{border-top:1px solid var(--pptx-border, #ddd)}.recent header,.recent>button{display:grid;grid-template-columns:1fr 120px 90px;align-items:center;padding:10px 12px}.recent header{color:var(--pptx-muted-foreground, #666);font-size:11px;font-weight:600}.recent>button{width:100%;border:0;border-top:1px solid var(--pptx-border, #e5e5e5);background:none;color:inherit;font-size:11px;text-align:left}.name{display:flex;min-width:0;align-items:center;gap:12px}.name>b{display:grid;width:32px;height:32px;place-items:center;background:var(--pptx-primary, #d24726);color:var(--pptx-primary-foreground, #fff)}.name strong,.name small{display:block;overflow:hidden;font-size:13px;font-weight:400;text-overflow:ellipsis;white-space:nowrap}.name small,.recent p{color:var(--pptx-muted-foreground, #666);font-size:11px}.recent p{text-align:center}.actions{display:grid;max-width:900px;grid-template-columns:1fr 1fr;gap:20px;margin-top:32px}.actions button{display:flex;min-height:112px;gap:16px;padding:20px;border:1px solid var(--pptx-border, #ddd);background:var(--pptx-card, #fff);color:var(--pptx-card-foreground, #242424);text-align:left}.actions button:hover{border-color:var(--pptx-primary, #c43e1c);box-shadow:0 5px 15px #0002}.actions button>b{display:grid;width:40px;height:40px;flex:none;place-items:center;background:var(--pptx-accent, #fbe9e3);color:var(--pptx-primary, #c43e1c)}.actions strong,.actions small{display:block;font-size:15px}.actions small{margin-top:7px;color:var(--pptx-muted-foreground, #666);font-size:12px;line-height:1.5}.card{max-width:760px;margin-top:32px;padding:28px;border:1px solid var(--pptx-border, #ddd);background:var(--pptx-card, #fff);color:var(--pptx-card-foreground, #242424)}.avatar{display:grid;width:56px;height:56px;place-items:center;border-radius:50%;background:var(--pptx-primary, #c43e1c);color:var(--pptx-primary-foreground, #fff);font-size:20px}.card p,footer{color:var(--pptx-muted-foreground, #666)}.card p{line-height:1.6}footer{margin-top:48px;font-size:11px}@media(max-width:640px),(pointer:coarse){.bs{flex-direction:column}.bs aside{width:100%;flex-direction:row;align-items:center;overflow-x:auto;border-right:0;border-bottom:1px solid var(--pptx-border, #d7d7d7)}.back{min-width:48px;flex-shrink:0;border-bottom:0;border-right:1px solid var(--pptx-border, #ddd)}nav{flex-direction:row;align-items:center;padding:0}nav i{display:none}nav button{flex-shrink:0;white-space:nowrap;border-left:0;border-bottom:2px solid transparent;padding:0 12px}nav button.active{border-left-color:transparent;border-bottom-color:var(--pptx-primary, #c43e1c)}main{padding:16px}.actions{grid-template-columns:1fr}.recent header,.recent>button{grid-template-columns:1fr 90px}.recent header span:last-child,.recent>button>span:last-child{display:none}}\n"] }]
104683
105304
  }], ctorParameters: () => [], propDecorators: { fileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileName", required: false }] }], slideCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideCount", required: false }] }], exporting: [{ type: i0.Input, args: [{ isSignal: true, alias: "exporting", required: false }] }], hasMacros: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasMacros", required: false }] }], hiddenActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "hiddenActions", required: false }] }], accountAuth: [{ type: i0.Input, args: [{ isSignal: true, alias: "accountAuth", required: false }] }], close: [{ type: i0.Output, args: ["close"] }], createPresentation: [{ type: i0.Output, args: ["createPresentation"] }], openFile: [{ type: i0.Output, args: ["openFile"] }], openRecentFile: [{ type: i0.Output, args: ["openRecentFile"] }], save: [{ type: i0.Output, args: ["save"] }], savePpsx: [{ type: i0.Output, args: ["savePpsx"] }], savePptm: [{ type: i0.Output, args: ["savePptm"] }], packageForSharing: [{ type: i0.Output, args: ["packageForSharing"] }], exportPng: [{ type: i0.Output, args: ["exportPng"] }], exportPdf: [{ type: i0.Output, args: ["exportPdf"] }], exportGif: [{ type: i0.Output, args: ["exportGif"] }], exportVideo: [{ type: i0.Output, args: ["exportVideo"] }], exportJson: [{ type: i0.Output, args: ["exportJson"] }], copySlideAsImage: [{ type: i0.Output, args: ["copySlideAsImage"] }], print: [{ type: i0.Output, args: ["print"] }], info: [{ type: i0.Output, args: ["info"] }], signatures: [{ type: i0.Output, args: ["signatures"] }], replace: [{ type: i0.Output, args: ["replace"] }], openPassword: [{ type: i0.Output, args: ["openPassword"] }], openFontEmbedding: [{ type: i0.Output, args: ["openFontEmbedding"] }], openVersionHistory: [{ type: i0.Output, args: ["openVersionHistory"] }], share: [{ type: i0.Output, args: ["share"] }], options: [{ type: i0.Output, args: ["options"] }] } });
104684
105305
 
104685
- /**
104686
- * ribbon-color-popover.component.ts: the hover-reveal swatch popover used for the
104687
- * Font-colour and Text-highlight pickers in {@link RibbonFontControlsComponent}.
104688
- * The trigger icon is projected via `<ng-content>`; the swatch grid + custom
104689
- * colour input are shared. Split out to keep the font controls under the 300-LOC
104690
- * cap; behaviour and markup are unchanged.
104691
- */
104692
- class RibbonColorPopoverComponent {
104693
- constructor() {
104694
- this.current = input('#000000', /* @ts-ignore */
104695
- ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
104696
- this.presets = input([], /* @ts-ignore */
104697
- ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
104698
- this.disabled = input(false, /* @ts-ignore */
104699
- ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
104700
- this.titleKey = input('', /* @ts-ignore */
104701
- ...(ngDevMode ? [{ debugName: "titleKey" }] : /* istanbul ignore next */ []));
104702
- this.swatchAriaKey = input('', /* @ts-ignore */
104703
- ...(ngDevMode ? [{ debugName: "swatchAriaKey" }] : /* istanbul ignore next */ []));
104704
- this.pick = output();
104705
- }
104706
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: RibbonColorPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
104707
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: RibbonColorPopoverComponent, isStandalone: true, selector: "pptx-ribbon-color-popover", inputs: { current: { classPropertyName: "current", publicName: "current", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, swatchAriaKey: { classPropertyName: "swatchAriaKey", publicName: "swatchAriaKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pick: "pick" }, ngImport: i0, template: `
104708
- <div class="group relative">
104709
- <button
104710
- type="button"
104711
- class="pptx-rb-pill"
104712
- [disabled]="disabled()"
104713
- [title]="titleKey() | translate"
104714
- (mousedown)="$event.preventDefault()"
104715
- >
104716
- <ng-content />
104717
- <span class="-mt-0.5 block h-1 w-4 rounded-sm" [style.background]="current()"></span>
104718
- </button>
104719
- <div class="absolute left-0 top-full z-50 hidden pt-1 group-hover:block">
104720
- <div class="w-36 rounded-lg border border-border bg-card p-2 shadow-2xl">
104721
- <div class="mb-2 grid grid-cols-5 gap-1.5">
104722
- @for (c of presets(); track c) {
104723
- <button
104724
- type="button"
104725
- data-pptx-compact
104726
- class="h-5 w-5 rounded-full border transition-transform hover:scale-125"
104727
- [ngClass]="
104728
- current().toLowerCase() === c
104729
- ? 'border-primary ring-1 ring-primary'
104730
- : 'border-border'
104731
- "
104732
- [style.background]="c"
104733
- [attr.aria-label]="swatchAriaKey() | translate: { color: c }"
104734
- (mousedown)="$event.preventDefault()"
104735
- (click)="pick.emit(c)"
104736
- ></button>
104737
- }
104738
- </div>
104739
- <label
104740
- class="block w-full cursor-pointer py-1 text-center text-[10px] text-muted-foreground transition-colors hover:text-foreground"
104741
- >
104742
- {{ 'pptx.ribbon.customColour' | translate }}
104743
- <input
104744
- type="color"
104745
- class="sr-only"
104746
- [value]="current()"
104747
- (change)="pick.emit($any($event.target).value)"
104748
- />
104749
- </label>
104750
- </div>
104751
- </div>
104752
- </div>
104753
- `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
104754
- }
104755
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: RibbonColorPopoverComponent, decorators: [{
104756
- type: Component,
104757
- args: [{
104758
- selector: 'pptx-ribbon-color-popover',
104759
- standalone: true,
104760
- changeDetection: ChangeDetectionStrategy.OnPush,
104761
- imports: [NgClass, TranslatePipe],
104762
- template: `
104763
- <div class="group relative">
104764
- <button
104765
- type="button"
104766
- class="pptx-rb-pill"
104767
- [disabled]="disabled()"
104768
- [title]="titleKey() | translate"
104769
- (mousedown)="$event.preventDefault()"
104770
- >
104771
- <ng-content />
104772
- <span class="-mt-0.5 block h-1 w-4 rounded-sm" [style.background]="current()"></span>
104773
- </button>
104774
- <div class="absolute left-0 top-full z-50 hidden pt-1 group-hover:block">
104775
- <div class="w-36 rounded-lg border border-border bg-card p-2 shadow-2xl">
104776
- <div class="mb-2 grid grid-cols-5 gap-1.5">
104777
- @for (c of presets(); track c) {
104778
- <button
104779
- type="button"
104780
- data-pptx-compact
104781
- class="h-5 w-5 rounded-full border transition-transform hover:scale-125"
104782
- [ngClass]="
104783
- current().toLowerCase() === c
104784
- ? 'border-primary ring-1 ring-primary'
104785
- : 'border-border'
104786
- "
104787
- [style.background]="c"
104788
- [attr.aria-label]="swatchAriaKey() | translate: { color: c }"
104789
- (mousedown)="$event.preventDefault()"
104790
- (click)="pick.emit(c)"
104791
- ></button>
104792
- }
104793
- </div>
104794
- <label
104795
- class="block w-full cursor-pointer py-1 text-center text-[10px] text-muted-foreground transition-colors hover:text-foreground"
104796
- >
104797
- {{ 'pptx.ribbon.customColour' | translate }}
104798
- <input
104799
- type="color"
104800
- class="sr-only"
104801
- [value]="current()"
104802
- (change)="pick.emit($any($event.target).value)"
104803
- />
104804
- </label>
104805
- </div>
104806
- </div>
104807
- </div>
104808
- `,
104809
- }]
104810
- }], propDecorators: { current: [{ type: i0.Input, args: [{ isSignal: true, alias: "current", required: false }] }], presets: [{ type: i0.Input, args: [{ isSignal: true, alias: "presets", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], swatchAriaKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "swatchAriaKey", required: false }] }], pick: [{ type: i0.Output, args: ["pick"] }] } });
104811
-
104812
105306
  /**
104813
105307
  * ribbon-font-controls.component.ts: the ribbon's reusable Font control group
104814
105308
  * (family/size dropdowns, grow/shrink, clear-formatting, bold/italic/underline/
@@ -106056,6 +106550,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
106056
106550
  * markup are unchanged. Font/Paragraph controls are the shared
106057
106551
  * {@link RibbonFontControlsComponent} / {@link RibbonParagraphControlsComponent}.
106058
106552
  */
106553
+ /**
106554
+ * Home > Reset: re-apply the active slide's own layout, restoring inherited
106555
+ * placeholder geometry and formatting (React/Vue parity via the shared
106556
+ * `resetSlideLayoutPath` decision function). A no-op when the slide records
106557
+ * no layout. Exported as a pure dispatch function (rather than inlined in the
106558
+ * component) so it is directly testable without constructing the component,
106559
+ * whose constructor runs an `effect()` that needs a full Angular
106560
+ * `ChangeDetectionScheduler` this package's TestBed-free unit tests don't
106561
+ * provide (see `action-settings-panel.component.test.ts`).
106562
+ */
106563
+ function performResetSlide(editor, slideIndex) {
106564
+ const path = resetSlideLayoutPath(editor.slides()[slideIndex]);
106565
+ if (path) {
106566
+ void editor.applyLayout(slideIndex, path);
106567
+ }
106568
+ }
106059
106569
  class RibbonHomeSectionComponent {
106060
106570
  constructor() {
106061
106571
  this.editor = inject(EditorStateService);
@@ -106091,6 +106601,7 @@ class RibbonHomeSectionComponent {
106091
106601
  this.openTemplateGallery = output();
106092
106602
  /** Emitted with the layout the user picked, after it has been applied. */
106093
106603
  this.applyLayout = output();
106604
+ /** Emitted after Home > Reset has re-applied the slide's layout. */
106094
106605
  this.resetSlide = output();
106095
106606
  // The menus open on hover with no event to hang a lazy load off, so the
106096
106607
  // fetch is kicked off once a deck is present. It is still deferred out of
@@ -106119,6 +106630,14 @@ class RibbonHomeSectionComponent {
106119
106630
  void this.editor.applyLayout(this.slideIndex(), layoutPath);
106120
106631
  this.applyLayout.emit(layoutPath);
106121
106632
  }
106633
+ /**
106634
+ * The button used to `resetSlide.emit()` to nobody, so clicking it did
106635
+ * nothing; {@link performResetSlide} now actually performs the reset.
106636
+ */
106637
+ onResetSlide() {
106638
+ performResetSlide(this.editor, this.slideIndex());
106639
+ this.resetSlide.emit();
106640
+ }
106122
106641
  copy() {
106123
106642
  this.editor.copySelected(this.slideIndex());
106124
106643
  }
@@ -106273,8 +106792,9 @@ class RibbonHomeSectionComponent {
106273
106792
  <button
106274
106793
  type="button"
106275
106794
  class="pptx-rb-gb whitespace-nowrap"
106795
+ [disabled]="!canEdit()"
106276
106796
  [title]="'pptx.sections.resetSlideTitle' | translate"
106277
- (click)="resetSlide.emit()"
106797
+ (click)="onResetSlide()"
106278
106798
  >
106279
106799
  <svg lucideRotateCcw class="h-4 w-4"></svg> {{ 'pptx.animations.reset' | translate }}
106280
106800
  </button>
@@ -106498,8 +107018,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
106498
107018
  <button
106499
107019
  type="button"
106500
107020
  class="pptx-rb-gb whitespace-nowrap"
107021
+ [disabled]="!canEdit()"
106501
107022
  [title]="'pptx.sections.resetSlideTitle' | translate"
106502
- (click)="resetSlide.emit()"
107023
+ (click)="onResetSlide()"
106503
107024
  >
106504
107025
  <svg lucideRotateCcw class="h-4 w-4"></svg> {{ 'pptx.animations.reset' | translate }}
106505
107026
  </button>
@@ -107696,7 +108217,11 @@ class RibbonContentComponent {
107696
108217
  (openTemplateGallery)="openTemplateGallery.emit()"
107697
108218
  />
107698
108219
  <span class="pptx-rb-sep"></span>
107699
- <pptx-ribbon-drawing-group [canEdit]="canEdit()" [slideIndex]="slideIndex()" />
108220
+ <pptx-ribbon-drawing-group
108221
+ [canEdit]="canEdit()"
108222
+ [slideIndex]="slideIndex()"
108223
+ [selectedElement]="selectedElement()"
108224
+ />
107700
108225
  <span class="pptx-rb-sep"></span>
107701
108226
  <!--
107702
108227
  React parity (Toolbar.tsx: sArr = sHome || toolbarSection === 'arrange'):
@@ -107746,7 +108271,7 @@ class RibbonContentComponent {
107746
108271
  />
107747
108272
  }
107748
108273
  }
107749
- `, isInline: true, dependencies: [{ kind: "component", type: RibbonFileSectionComponent, selector: "pptx-ribbon-file-section", inputs: ["fileName", "slideCount", "exporting", "hasMacros", "hiddenActions", "accountAuth"], outputs: ["close", "createPresentation", "openFile", "openRecentFile", "save", "savePpsx", "savePptm", "packageForSharing", "exportPng", "exportPdf", "exportGif", "exportVideo", "exportJson", "copySlideAsImage", "print", "info", "signatures", "replace", "openPassword", "openFontEmbedding", "openVersionHistory", "share", "options"] }, { kind: "component", type: RibbonHomeSectionComponent, selector: "pptx-ribbon-home-section", inputs: ["slideIndex", "selectedElement", "canEdit", "formatPainterActive", "canActivateFormatPainter"], outputs: ["toggleFormatPainter", "findReplace", "openTemplateGallery", "applyLayout", "resetSlide"] }, { kind: "component", type: RibbonInsertSectionComponent, selector: "pptx-ribbon-insert-section", inputs: ["slideIndex", "newChartType", "newShapeType"], outputs: ["openSmartArtDialog", "openEquationDialog", "openHyperlink", "chartTypeChange", "shapeTypeChange"] }, { kind: "component", type: RibbonFontControlsComponent, selector: "pptx-ribbon-font-controls", inputs: ["slideIndex", "selectedElement"] }, { kind: "component", type: RibbonParagraphControlsComponent, selector: "pptx-ribbon-paragraph-controls", inputs: ["slideIndex", "selectedElement"] }, { kind: "component", type: RibbonArrangeSectionComponent, selector: "pptx-ribbon-arrange-section", inputs: ["slideIndex", "selectedElement", "canEdit", "formatPainterActive", "canActivateFormatPainter"], outputs: ["toggleFormatPainter"] }, { kind: "component", type: RibbonDrawingGroupComponent, selector: "pptx-ribbon-drawing-group", inputs: ["canEdit", "slideIndex"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
108274
+ `, isInline: true, dependencies: [{ kind: "component", type: RibbonFileSectionComponent, selector: "pptx-ribbon-file-section", inputs: ["fileName", "slideCount", "exporting", "hasMacros", "hiddenActions", "accountAuth"], outputs: ["close", "createPresentation", "openFile", "openRecentFile", "save", "savePpsx", "savePptm", "packageForSharing", "exportPng", "exportPdf", "exportGif", "exportVideo", "exportJson", "copySlideAsImage", "print", "info", "signatures", "replace", "openPassword", "openFontEmbedding", "openVersionHistory", "share", "options"] }, { kind: "component", type: RibbonHomeSectionComponent, selector: "pptx-ribbon-home-section", inputs: ["slideIndex", "selectedElement", "canEdit", "formatPainterActive", "canActivateFormatPainter"], outputs: ["toggleFormatPainter", "findReplace", "openTemplateGallery", "applyLayout", "resetSlide"] }, { kind: "component", type: RibbonInsertSectionComponent, selector: "pptx-ribbon-insert-section", inputs: ["slideIndex", "newChartType", "newShapeType"], outputs: ["openSmartArtDialog", "openEquationDialog", "openHyperlink", "chartTypeChange", "shapeTypeChange"] }, { kind: "component", type: RibbonFontControlsComponent, selector: "pptx-ribbon-font-controls", inputs: ["slideIndex", "selectedElement"] }, { kind: "component", type: RibbonParagraphControlsComponent, selector: "pptx-ribbon-paragraph-controls", inputs: ["slideIndex", "selectedElement"] }, { kind: "component", type: RibbonArrangeSectionComponent, selector: "pptx-ribbon-arrange-section", inputs: ["slideIndex", "selectedElement", "canEdit", "formatPainterActive", "canActivateFormatPainter"], outputs: ["toggleFormatPainter"] }, { kind: "component", type: RibbonDrawingGroupComponent, selector: "pptx-ribbon-drawing-group", inputs: ["canEdit", "slideIndex", "selectedElement"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
107750
108275
  }
107751
108276
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: RibbonContentComponent, decorators: [{
107752
108277
  type: Component,
@@ -107810,7 +108335,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
107810
108335
  (openTemplateGallery)="openTemplateGallery.emit()"
107811
108336
  />
107812
108337
  <span class="pptx-rb-sep"></span>
107813
- <pptx-ribbon-drawing-group [canEdit]="canEdit()" [slideIndex]="slideIndex()" />
108338
+ <pptx-ribbon-drawing-group
108339
+ [canEdit]="canEdit()"
108340
+ [slideIndex]="slideIndex()"
108341
+ [selectedElement]="selectedElement()"
108342
+ />
107814
108343
  <span class="pptx-rb-sep"></span>
107815
108344
  <!--
107816
108345
  React parity (Toolbar.tsx: sArr = sHome || toolbarSection === 'arrange'):
@@ -110411,31 +110940,10 @@ function previewAngularAnimation(animation) {
110411
110940
  return true;
110412
110941
  }
110413
110942
 
110414
- function reorderAnimationTimeline(animations, sourceIndex, targetIndex) {
110415
- const sorted = [...animations].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
110416
- if (sourceIndex < 0 ||
110417
- targetIndex < 0 ||
110418
- sourceIndex >= sorted.length ||
110419
- targetIndex >= sorted.length ||
110420
- sourceIndex === targetIndex) {
110421
- return sorted.map((animation, order) => ({ ...animation, order }));
110422
- }
110423
- const [moved] = sorted.splice(sourceIndex, 1);
110424
- if (!moved) {
110425
- return sorted;
110426
- }
110427
- sorted.splice(targetIndex, 0, moved);
110428
- return sorted.map((animation, order) => ({ ...animation, order }));
110429
- }
110430
- function buildAnimationTimelineBars(animations) {
110431
- const sorted = [...animations].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
110432
- const total = Math.max(1, ...sorted.map((animation) => (animation.delayMs ?? 0) + (animation.durationMs ?? 500)));
110433
- return sorted.map((animation) => ({
110434
- elementId: animation.elementId,
110435
- leftPercent: ((animation.delayMs ?? 0) / total) * 100,
110436
- widthPercent: ((animation.durationMs ?? 500) / total) * 100,
110437
- }));
110438
- }
110943
+ /* oxlint-disable eslint/one-var -- the component's own field/method
110944
+ declarations below are independent, not adjacent initializations of
110945
+ related values; merging them would hurt readability far more than it
110946
+ helps. */
110439
110947
  class AnimationTimelineComponent {
110440
110948
  constructor() {
110441
110949
  this.animations = input.required(/* @ts-ignore */
@@ -110493,7 +111001,7 @@ class AnimationTimelineComponent {
110493
111001
  event.preventDefault();
110494
111002
  const source = this.dragIndex();
110495
111003
  if (source !== undefined) {
110496
- this.animationsChange.emit(reorderAnimationTimeline(this.animations(), source, index));
111004
+ this.animationsChange.emit(reorderAnimationTo(this.animations(), source, index));
110497
111005
  }
110498
111006
  this.clearDrag();
110499
111007
  }
@@ -117025,9 +117533,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
117025
117533
  * drawingShapes so the renderer reflows with the override). Reading the
117026
117534
  * current value off `node.style` drives the inspector controls.
117027
117535
  *
117028
- * 2. **Node-count bounds** - soft, per-layout min/max guards mirroring the
117029
- * React `smartart-node-limits.ts`, so the text-pane Add / Remove
117030
- * affordances can be disabled with an explanatory hint at the bounds.
117536
+ * 2. **Node-count bounds** - soft, per-layout min/max guards. The table,
117537
+ * `getSmartArtNodeBounds`, `canAddTopLevelNode` and `canRemoveTopLevelNode`
117538
+ * are re-exported from the single shared implementation in
117539
+ * `pptx-viewer-shared` (`packages/shared/src/render/smartart-node-limits.ts`),
117540
+ * which also backs the React and Vue bindings. `describeSmartArtBounds`
117541
+ * stays a thin Angular wrapper: it takes the shared function's neutral
117542
+ * English text and, when a `TranslateService` is supplied, swaps in the
117543
+ * translated string keyed off the same bounds, so the shared module never
117544
+ * takes an i18n dependency.
117031
117545
  *
117032
117546
  * @module angular-viewer/smart-art-node-style-helpers
117033
117547
  *
@@ -117074,61 +117588,39 @@ function toggleNodeBold(data, node) {
117074
117588
  function toggleNodeItalic(data, node) {
117075
117589
  return setNodeStyle(data, node.id, { italic: !nodeItalic(node) });
117076
117590
  }
117077
- /** Per-layout bounds table; layouts not listed fall back to {@link DEFAULT_BOUNDS}. */
117078
- const LAYOUT_BOUNDS = {
117079
- venn: { min: 2, max: 3 },
117080
- matrix: { min: 4, max: 4 },
117081
- pyramid: { min: 2, max: 5 },
117082
- funnel: { min: 2, max: 5 },
117083
- target: { min: 2, max: 5 },
117084
- gear: { min: 2, max: 3 },
117085
- cycle: { min: 3 },
117086
- };
117087
- /** Fallback bounds for any layout without an explicit entry. */
117088
- const DEFAULT_BOUNDS = { min: 1 };
117089
- /** Resolve the node-count bounds for a layout category. */
117090
- function getSmartArtNodeBounds(layout) {
117091
- if (!layout) {
117092
- return DEFAULT_BOUNDS;
117093
- }
117094
- return LAYOUT_BOUNDS[layout] ?? DEFAULT_BOUNDS;
117095
- }
117591
+ // ── Node-count bounds (re-exported from pptx-viewer-shared) ────────────────
117592
+ // See the module doc comment: the table, `getSmartArtNodeBounds`,
117593
+ // `canAddTopLevelNode` and `canRemoveTopLevelNode` are re-exported above,
117594
+ // unmodified, from the single shared implementation. Only
117595
+ // `describeSmartArtBounds` stays defined here, as a thin wrapper that applies
117596
+ // this binding's `TranslateService` on top of the shared neutral text.
117096
117597
  /** Count of top-level (parentless) nodes in a diagram. */
117097
117598
  function topLevelNodeCount(data) {
117098
117599
  return (data.nodes ?? []).filter((node) => !node.parentId).length;
117099
117600
  }
117100
- /** Whether adding another top-level node stays within the layout's max. */
117101
- function canAddTopLevelNode(layout, topLevelCount) {
117102
- const { max } = getSmartArtNodeBounds(layout);
117103
- return max === undefined || topLevelCount < max;
117104
- }
117105
- /** Whether removing a top-level node keeps the count at or above the min. */
117106
- function canRemoveTopLevelNode(layout, topLevelCount) {
117107
- const { min } = getSmartArtNodeBounds(layout);
117108
- return topLevelCount > min;
117109
- }
117110
117601
  /**
117111
117602
  * Short, human-readable explanation of a layout's bounds for a tooltip / hint,
117112
117603
  * or `undefined` when the layout imposes no meaningful limit.
117604
+ *
117605
+ * Delegates the neutral English text to the shared implementation; when a
117606
+ * `TranslateService` is supplied, swaps in the translated string for the same
117607
+ * bounds instead.
117113
117608
  */
117114
117609
  function describeSmartArtBounds(layout, translate) {
117115
117610
  const { min, max } = getSmartArtNodeBounds(layout);
117611
+ if (!translate) {
117612
+ return describeSmartArtBounds$1(layout);
117613
+ }
117116
117614
  if (min <= 1 && max === undefined) {
117117
117615
  return undefined;
117118
117616
  }
117119
117617
  if (max === undefined) {
117120
- return translate
117121
- ? translate.instant('pptx.smartArt.boundsHintMin', { min })
117122
- : `Works best with at least ${min} items.`;
117618
+ return translate.instant('pptx.smartArt.boundsHintMin', { min });
117123
117619
  }
117124
117620
  if (min === max) {
117125
- return translate
117126
- ? translate.instant('pptx.smartArt.boundsHintExact', { max })
117127
- : `This layout uses exactly ${max} items.`;
117621
+ return translate.instant('pptx.smartArt.boundsHintExact', { max });
117128
117622
  }
117129
- return translate
117130
- ? translate.instant('pptx.smartArt.boundsHintRange', { min, max })
117131
- : `Works best with ${min} to ${max} items.`;
117623
+ return translate.instant('pptx.smartArt.boundsHintRange', { min, max });
117132
117624
  }
117133
117625
 
117134
117626
  /**
@@ -117895,6 +118387,22 @@ function inputValue$1(event) {
117895
118387
  return target instanceof HTMLInputElement ? target.value : null;
117896
118388
  }
117897
118389
 
118390
+ /**
118391
+ * table-properties-helpers.ts: pure helpers for the table properties inspector.
118392
+ *
118393
+ * The small immutable transforms behind the Angular port of the React
118394
+ * `TablePropertiesPanel` / `TableCellAdvancedFill`: building a CSS gradient
118395
+ * string from structured stops so the renderer shows edited gradients live.
118396
+ * Table quick-style preset application (`applyTableStylePreset`) and
118397
+ * column-width redistribution / "distribute evenly" one-liners live in
118398
+ * `pptx-viewer-shared`'s `render/table-style-presets.ts` and
118399
+ * `render/table-resize.ts` (imported from `../internal/shared`) so every
118400
+ * binding shares one copy of that logic.
118401
+ *
118402
+ * No Angular imports, so they are unit-testable with plain vitest.
118403
+ */
118404
+ /* oxlint-disable eslint/one-var -- independent, unrelated locals across these
118405
+ helpers; merging them into one statement would hurt readability. */
117898
118406
  /** Default row height (px) used when a row has no explicit height. */
117899
118407
  const DEFAULT_TABLE_ROW_HEIGHT = 32;
117900
118408
  /** The structure / style toggle flags shown as checkboxes, with i18n dictionary keys. */
@@ -117906,69 +118414,6 @@ const TABLE_STRUCTURE_TOGGLES = [
117906
118414
  { key: 'lastCol', labelKey: 'pptx.table.lastColumn' },
117907
118415
  { key: 'lastRow', labelKey: 'pptx.table.lastRow' },
117908
118416
  ];
117909
- /**
117910
- * Apply a table quick-style preset to every cell's style, mirroring the React
117911
- * `TablePropertiesPanel` preset `onClick`. Header cells get the header fill /
117912
- * foreground + bold; banded body rows get the band background; every cell gets
117913
- * the preset border colour. Returns a new rows array.
117914
- */
117915
- function applyTableStylePreset(td, preset) {
117916
- return td.rows.map((row, ri) => ({
117917
- ...row,
117918
- cells: row.cells.map((cell) => {
117919
- const isHeader = ri === 0 && Boolean(td.firstRowHeader);
117920
- const isBand = Boolean(td.bandedRows) && (ri - (td.firstRowHeader ? 1 : 0)) % 2 === 0;
117921
- const style = {
117922
- ...cell.style,
117923
- backgroundColor: isHeader ? preset.headerBg : isBand ? preset.bandBg : undefined,
117924
- color: isHeader ? preset.headerFg : cell.style?.color,
117925
- bold: isHeader ? true : cell.style?.bold,
117926
- borderColor: preset.borderColor,
117927
- };
117928
- return { ...cell, style };
117929
- }),
117930
- }));
117931
- }
117932
- /**
117933
- * Redistribute column widths when a single column is set to `newFraction`,
117934
- * scaling the other columns proportionally so the array still sums to 1.
117935
- * Mirrors the React column-width slider `onChange`.
117936
- */
117937
- function redistributeColumnWidth(widths, index, newFraction) {
117938
- const oldFraction = widths[index];
117939
- if (oldFraction === undefined) {
117940
- return widths;
117941
- }
117942
- const diff = newFraction - oldFraction;
117943
- const next = [...widths];
117944
- next[index] = newFraction;
117945
- const othersTotal = 1 - oldFraction;
117946
- if (othersTotal > 0) {
117947
- for (let j = 0; j < next.length; j++) {
117948
- if (j !== index) {
117949
- next[j] = Math.max(0.05, widths[j] - diff * (widths[j] / othersTotal));
117950
- }
117951
- }
117952
- }
117953
- const sum = next.reduce((a, b) => a + b, 0);
117954
- return sum > 0 ? next.map((w) => w / sum) : next;
117955
- }
117956
- /** An equal-width column array for `count` columns. */
117957
- function evenColumnWidths(count) {
117958
- if (count <= 0) {
117959
- return [];
117960
- }
117961
- return Array.from({ length: count }, () => 1 / count);
117962
- }
117963
- /** Rows with a uniform (average) height applied. */
117964
- function evenRowHeights(td) {
117965
- const count = td.rows.length;
117966
- if (count === 0) {
117967
- return td.rows;
117968
- }
117969
- const avg = Math.round(td.rows.reduce((s, r) => s + (r.height ?? DEFAULT_TABLE_ROW_HEIGHT), 0) / count);
117970
- return td.rows.map((r) => ({ ...r, height: avg }));
117971
- }
117972
118417
  /**
117973
118418
  * Build a CSS gradient string from structured cell-style gradient fields, so
117974
118419
  * the renderer (which reads `gradientFillCss`) shows an edited gradient live.
@@ -119389,6 +119834,8 @@ function stringFromEvent$1(event) {
119389
119834
  * controls. Every edit emits a fully-updated element through `elementChange`,
119390
119835
  * committed by the inspector as one undoable history entry.
119391
119836
  */
119837
+ /* oxlint-disable eslint/one-var -- each handler declares its own independent
119838
+ locals; merging them into one statement would hurt readability. */
119392
119839
  class TablePropertiesComponent {
119393
119840
  constructor() {
119394
119841
  /** The table element being edited. */
@@ -119435,7 +119882,7 @@ class TablePropertiesComponent {
119435
119882
  onEvenRows() {
119436
119883
  const data = this.td();
119437
119884
  if (data) {
119438
- this.emit({ rows: evenRowHeights(data) });
119885
+ this.emit({ rows: evenRowHeights(data.rows) });
119439
119886
  }
119440
119887
  }
119441
119888
  onColWidth(index, event) {
@@ -120869,7 +121316,7 @@ class InspectorPanelComponent {
120869
121316
  height: cur.height,
120870
121317
  rotation: cur.rotation ?? 0,
120871
121318
  opacity: cur.opacity ?? 1,
120872
- fillColor: fillColorOf(cur),
121319
+ fillColor: fillColorOf$1(cur),
120873
121320
  strokeColor: strokeColorOf(cur),
120874
121321
  textColor: textColorOf(cur),
120875
121322
  fontSize: fontSizeOf(cur),
@@ -121000,7 +121447,7 @@ class InspectorPanelComponent {
121000
121447
  return;
121001
121448
  }
121002
121449
  const cur = this.el();
121003
- this.editor.updateElement(this.slideIndex(), cur.id, shapeStylePatch(cur, { fillColor: color }));
121450
+ this.editor.updateElement(this.slideIndex(), cur.id, shapeStylePatch$1(cur, { fillColor: color }));
121004
121451
  }
121005
121452
  onStrokeColorChange(event) {
121006
121453
  const color = stringFromEvent(event);
@@ -121008,7 +121455,7 @@ class InspectorPanelComponent {
121008
121455
  return;
121009
121456
  }
121010
121457
  const cur = this.el();
121011
- this.editor.updateElement(this.slideIndex(), cur.id, shapeStylePatch(cur, { strokeColor: color }));
121458
+ this.editor.updateElement(this.slideIndex(), cur.id, shapeStylePatch$1(cur, { strokeColor: color }));
121012
121459
  }
121013
121460
  // ── Text style ───────────────────────────────────────────────────────────
121014
121461
  onTextColorChange(event) {
@@ -137493,5 +137940,5 @@ function cn(...values) {
137493
137940
  * Generated bundle index. Do not edit.
137494
137941
  */
137495
137942
 
137496
- export { CommentsPanelComponent as $, ALIGN_OPTIONS as A, AnimationPlaybackService as B, AutosaveRecoveryDialogComponent as C, AutosaveService as D, BroadcastDialogComponent as E, CHART_EDITOR_STYLES as F, CURSOR_PALETTE as G, CanvasFitService as H, ChartAxisOptionsComponent as I, ChartAxisStyleOptionsComponent as J, ChartComboTypeOptionsComponent as K, ChartDataEditorComponent as L, ChartDataLabelOptionsComponent as M, ChartDatapointMarkerOptionsComponent as N, ChartDatapointOptionsComponent as O, ChartDisplayOptionsComponent as P, ChartElementViewComponent as Q, ChartErrorBarOptionsComponent as R, ChartMarkerOptionsComponent as S, ChartPartSelectionService as T, ChartPrimitivesComponent as U, ChartRendererComponent as V, ChartTrendlineOptionsComponent as W, CollaborationCursorsComponent as X, CollaborationService as Y, ColorChangedImageComponent as Z, CommentMarkersOverlayComponent as _, ANIMATION_PRESET_CATEGORIES as a, InspectorPanelComponent as a$, CommentsService as a0, ComparePanelComponent as a1, ConnectorRendererComponent as a2, ConnectorTextOverlayComponent as a3, CustomShowsComponent as a4, DATA_TABLE_HEADER_H as a5, DATA_TABLE_KEY_W as a6, DATA_TABLE_PADDING as a7, DATA_TABLE_ROW_H as a8, DEFAULT_BOUNDS as a9, EditorToolbarComponent as aA, EffectsPanelComponent as aB, ElementRendererComponent as aC, EmbeddedFontsService as aD, EncryptedFileDialogComponent as aE, EquationEditorDialogComponent as aF, EquationRendererComponent as aG, EquationTemplateGalleryComponent as aH, ExportProgressModalComponent as aI, ExportService as aJ, FieldContextService as aK, FindBarComponent as aL, FindReplaceBarComponent as aM, FollowModeBarComponent as aN, FontEmbeddingListComponent as aO, FontEmbeddingPanelComponent as aP, GALLERY_THEME_PRESETS as aQ, GRIDLINE_COLOR as aR, GradientPickerComponent as aS, HANDOUT_OPTIONS as aT, HeaderFooterDialogComponent as aU, HyperlinkDialogComponent as aV, ImagePropertiesPanelComponent as aW, InkDrawingService as aX, InkRendererComponent as aY, InsertSmartArtDialogComponent as aZ, InspectorPaneHeaderComponent 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 as ae, DEFAULT_LAYOUT as af, DEFAULT_PALETTE$1 as ag, DEFAULT_PATTERN_FILL_PRESET as ah, DEFAULT_PRINT_SETTINGS as ai, DEFAULT_SLIDE_BACKGROUND as aj, DEFAULT_STROKE_COLOR as ak, DEFAULT_STYLE as al, DEFAULT_TABLE_ROW_HEIGHT as am, DEFAULT_TEXT_COLOR$1 as an, DEFAULT_VIEWER_PROFILE as ao, DIRECTIONAL_PRESETS as ap, DIRECTION_OPTIONS as aq, DocumentPropertiesCardComponent as ar, EMBEDDED_FONTS_STYLE_ID as as, EMPHASIS_PRESETS as at, ENTRANCE_PRESETS as au, TEMPLATES as av, EXIT_PRESETS as aw, EditorContextMenuComponent as ax, EditorHistory as ay, EditorStateService as az, AUDIENCE_HASH as b, RibbonComponent as b$, IsMobileService as b0, KeepAnnotationsDialogComponent as b1, LOCALE_CATALOG as b2, LONG_PRESS_DURATION_MS as b3, LONG_PRESS_MOVE_TOLERANCE_PX as b4, LoadContentService as b5, LocalPresencePublisher as b6, MAX_ZOOM_SCALE as b7, MIN_ZOOM_SCALE as b8, MOTION_PATH_COLUMNS as b9, PasswordStrengthMeterComponent as bA, PowerPointViewerComponent as bB, PresentToolbarAutoHide as bC, PresentationAnnotationOverlayComponent as bD, PresentationAnnotationsService as bE, PresentationOverlayComponent as bF, PresentationPropertiesPanelComponent as bG, PresentationSettingsCardComponent as bH, PresentationSubtitleBarComponent as bI, PresentationToolbarComponent as bJ, PresentationTransitionOverlayComponent as bK, PresenterViewComponent as bL, PresenterWindowService as bM, PrintDialogComponent as bN, PrintService as bO, PrintSettingsPanelComponent as bP, PropertiesDialogComponent as bQ, REPEAT_MODE_OPTIONS as bR, RESIZE_HANDLES as bS, RULER_FONT_SIZE as bT, RULER_THICKNESS as bU, ReadingViewOverlayComponent as bV, RemoteSelectionOverlayComponent as bW, RibbonAnimationGalleryComponent as bX, RibbonAnimationsSectionComponent as bY, RibbonArrangeSectionComponent as bZ, RibbonColorPopoverComponent as b_, MediaPreviewComponent as ba, MediaPropertiesPanelComponent as bb, MediaRendererComponent as bc, MediaTrimTimelineComponent as bd, MobileBottomBarComponent as be, MobileMenuSheetComponent as bf, MobilePresenterViewComponent as bg, MobileSheetComponent as bh, MobileSlidesSheetComponent as bi, MobileToolbarComponent as bj, ModalDialogComponent as bk, Model3DRendererComponent as bl, NotesHandoutCardComponent as bm, NotesPanelComponent as bn, NotesToolbarComponent as bo, OleRendererComponent as bp, OutlineViewOverlayComponent as bq, POWER_POINT_VIEWER_PROVIDERS as br, PPTX_OPEN_ACCEPT as bs, PRESENTATION_OPEN_EXTENSIONS as bt, PRESENTER_CHANNEL_NAME as bu, PRESENTER_MSG_ORIGIN as bv, PRESENTER_TIMER_SEGMENT_MS as bw, PX_PER_CM as bx, PX_PER_INCH as by, PasswordProtectionDialogComponent as bz, AUDIENCE_NONCE_KEY as c, TEXT_3D_TOP_BEVEL_KEYS as c$, RibbonDesignSectionComponent as c0, RibbonDrawSectionComponent as c1, RibbonDrawingGroupComponent as c2, RibbonEditingSectionComponent as c3, RibbonFileSectionComponent as c4, RibbonFontControlsComponent as c5, RibbonHomeSectionComponent as c6, RibbonHyperlinkButtonComponent as c7, RibbonInsertFieldsComponent as c8, RibbonInsertSectionComponent as c9, SettingsLanguageTabComponent as cA, ShareDialogComponent as cB, ShortcutPanelComponent as cC, ShowOptionsFieldsetComponent as cD, ShowSlidesFieldsetComponent as cE, SignatureStrippedDialogComponent as cF, SignaturesPanelComponent as cG, SignaturesService as cH, SlideBackgroundCardComponent as cI, SlideCanvasComponent as cJ, SlideDefaultInspectorComponent as cK, SlideDiffChangesComponent as cL, SlideDiffRowComponent as cM, SlideDiffThumbnailsComponent as cN, SlideSizeCardComponent as cO, SlideSorterOverlayComponent as cP, SlideThemeOverridePanelComponent as cQ, SlideTransitionCardComponent as cR, SlidesPanelComponent as cS, SmartArt3DRendererComponent as cT, SmartArt3DService as cU, SmartArtPreviewComponent as cV, SmartArtPropertiesComponent as cW, SmartArtRendererComponent as cX, StatusBarComponent as cY, TABLE_STRUCTURE_TOGGLES as cZ, TEXT_3D_BOTTOM_BEVEL_KEYS as c_, RibbonMotionPathGalleryComponent as ca, RibbonParagraphControlsComponent as cb, RibbonPrimaryRowComponent as cc, RibbonReviewSectionComponent as cd, RibbonShapeExtrasComponent as ce, RibbonSlideshowSectionComponent as cf, RibbonTransitionsSectionComponent as cg, RibbonViewSectionComponent as ch, RulerGuidesService as ci, SEQUENCE_OPTIONS as cj, SEVERITY_GROUPS as ck, SEVERITY_LABELS as cl, SHORTCUT_REFERENCE_ITEMS as cm, SLIDE_TRANSITION_KEYFRAMES as cn, DEFAULT_PALETTE as co, PALETTES$1 as cp, SMART_ART_COLOR_SCHEMES as cq, SMART_ART_STYLE_OPTIONS as cr, SUB_ITEM_LABEL as cs, SVG_WARP_PRESETS as ct, SWIPE_MAX_VERTICAL_PX as cu, SWIPE_THRESHOLD_PX as cv, SelectionPaneComponent as cw, SetUpSlideShowDialogComponent as cx, SettingsAppearanceTabComponent as cy, SettingsDialogComponent as cz, AVATAR_COLOR_SWATCHES as d, animationPresetLabelKey as d$, TEXT_DIRECTION_OPTIONS$1 as d0, THEME_CATALOG as d1, TIMING_CURVE_OPTIONS as d2, TRIGGER_OPTIONS as d3, TYPE_LABELS as d4, TableCellAdvancedFillComponent as d5, TableCellFormattingComponent as d6, TableDataEditorComponent as d7, TablePropertiesComponent as d8, TableRendererComponent as d9, ViewerFileIOService as dA, ViewerFindReplaceService as dB, ViewerFormatPainterService as dC, ViewerInspectorPanelService as dD, ViewerKeyboardService as dE, ViewerMobileSheetService as dF, ViewerPresentationModeService as dG, ViewerThemeGalleryService as dH, ViewerTouchGesturesService as dI, ViewerZoomService as dJ, WEBM_MIME_CANDIDATES as dK, WriteBackScheduler as dL, ZERO_LINE_COLOR as dM, ZoomNavigationService as dN, ZoomRendererComponent as dO, ZoomTargetService as dP, addCategory as dQ, addCommentToList as dR, addGradientStopPatch as dS, addItem as dT, addSeries as dU, addSubItem as dV, advanceStep as dW, affordanceElements as dX, aiToggleVisible as dY, alignPatch as dZ, animationFor as d_, TableResizeOverlayComponent as da, TableSelectionService as db, TagsCardComponent as dc, Text3DBevelSectionComponent as dd, Text3DPanelComponent as de, TextAdvancedPanelComponent as df, ThemeEditorFieldsComponent as dg, ThemeGalleryComponent as dh, ThemeSelectorCardComponent as di, TitleBarComponent as dj, TitleBarSearchComponent as dk, TransitionDirectionPickerComponent as dl, TransitionPreviewComponent as dm, VALIGN_OPTIONS as dn, VIEWER_THEME as dp, VersionHistoryPanelComponent as dq, ViewerCanvasEditingService as dr, ViewerCollabCursorService as ds, ViewerCollaborationSessionService as dt, ViewerCompareService as du, ViewerCustomShowsService as dv, ViewerDialogsService as dw, ViewerDocumentPropertiesService as dx, ViewerExportService as dy, ViewerExtraDialogsComponent as dz, AXIS_LABEL_COLOR as e, buildWaterfallViewModel as e$, annotationMapToInkInserts as e0, applyAcceptedDiff as e1, applyAnimationPreset as e2, applyFindReplacements as e3, applyFormatToElement as e4, applyMove as e5, applyResize as e6, applyTableStylePreset as e7, asMediaElement as e8, assignUserColor as e9, buildEquationElement as eA, buildEquationSegment as eB, buildFallbackViewModel as eC, buildFontFaceRule as eD, buildGradientFillCss as eE, buildGridlinesAndLabels as eF, buildHyperlinkPatch as eG, buildInkContainerStyle as eH, buildInkStrokes as eI, buildLegend as eJ, buildModel3DContainerStyle as eK, buildModel3DViewModel as eL, buildOleActionModel as eM, buildOleInfoRows as eN, buildPatternFillCss as eO, buildPrintHtmlDocument as eP, buildPropertiesPatch as eQ, buildRegionMapViewModel as eR, buildSaveSlides as eS, buildShareUrl as eT, buildSmartArtInsertElement as eU, buildSmartArtNodes as eV, buildStockViewModel as eW, buildSurfaceViewModel as eX, buildTableViewModel as eY, buildTreemapViewModel as eZ, buildTrimFragment as e_, attachShowVisibilityPause as ea, attachTouchGestures as eb, axisTickValues as ec, beginNodeEdit as ed, bevelSizePatch as ee, boolFromEvent as ef, bringForward as eg, bringToFront as eh, buildBarActions as ei, buildBroadcastConfig as ej, buildBroadcastViewerUrl as ek, buildCategoryLabels as el, buildCellParagraphs as em, buildChartViewModel as en, buildChatLogExport as eo, buildChatLogMarkdown as ep, buildChromeStyle as eq, buildClearHyperlinkPatch as er, buildClickGroups as es, buildColStyles as et, buildCollaborationConfig as eu, buildComboViewModel as ev, buildCssGradientFromShapeStyle as ew, buildDuotoneFilter as ex, buildDuotoneFilterId as ey, buildEmbeddedFontStyles as ez, AccessibilityPanelComponent as f, computeScatterXDomain as f$, buildZeroLine as f0, buildZoomContainerStyle as f1, buildZoomViewModel as f2, bulletIndentPx as f3, canAddTopLevelNode as f4, canGroupSelection as f5, canRemoveTopLevelNode as f6, canSetStrokeWidth as f7, canStartBroadcast as f8, canStartShare as f9, commitNodeText as fA, computeAlign as fB, computeAxisTitlePrimitives as fC, computeBarRects as fD, computeBubbleRadius as fE, computeCornerHandle as fF, computeDataTablePrimitives as fG, computeDistribute as fH, computeDrawingViewBox as fI, computeErrorBarPrimitives as fJ, computeFocusTargets as fK, computeHandleBoxes as fL, computeHandoutLayout as fM, computeIsMobile as fN, computeIsTablet as fO, computeLinePoints as fP, computeLinearRegression as fQ, computePageCount as fR, computePieLayout as fS, computePieSlicePath as fT, computePieSlices as fU, computePlotLayout as fV, computeRSquared as fW, computeRadarPoints as fX, computeResizeHandleBoxes as fY, computeRotateHandleBox as fZ, computeScatterDots as f_, canUngroupSelection as fa, canUseClipboard as fb, captionDisplayText as fc, cellRunStyle as fd, cellStyleToStyleMap as fe, cellTdStyle as ff, changeCountLabel as fg, changeIcon as fh, characterSpacingPatch as fi, chartPreserveAspectRatio as fj, checkFontAvailable as fk, clampCursorPosition as fl, clampGifDimensions as fm, clampIndex as fn, clampNotesFontSize as fo, clampScale as fp, clampStep as fq, clearAllLocalViewerData as fr, clearAudienceContent as fs, cn as ft, collectAccessibilityIssues as fu, collectElementText as fv, collectSlideText as fw, collectStoredChats as fx, collectUsedFontFamilies as fy, columnWidthStyle as fz, AccessibilityService as g, formatAxisValue as g$, computeSelectionBoxes as g0, computeSingleSelected as g1, computeSlideIndices as g2, computeSnap as g3, computeStackedBarRects as g4, computeStackedValueRange as g5, computeTrendlinePrimitives as g6, computeValueRange as g7, convertOmmlToMathMl as g8, copyFormatFromElement as g9, durationOf as gA, effectsStateOf as gB, enableGlowPatch as gC, enableInnerShadowPatch as gD, enableOuterShadowPatch as gE, enableReflectionPatch as gF, enableSoftEdgePatch as gG, encodeGif as gH, endShowMediaCleanup as gI, estimatePageCount as gJ, evenColumnWidths as gK, evenRowHeights as gL, exitPresentationFullscreen as gM, exportAiChatLogs as gN, extractPathPoints as gO, eyedropperAvailable as gP, fillColorOf as gQ, findInSlides as gR, findOwningSlideIndex as gS, findSlideIndexByElementId as gT, firstVisibleIndex as gU, fitPolynomial as gV, fitZoom as gW, focusTargetChips as gX, fontMimeForFormat as gY, fontSizeOf as gZ, forgetSessionDeck as g_, countAccessibilityIssues as ga, countAnnotationStrokes as gb, createAngularAiBridge as gc, createCustomShow as gd, createSwipeDismissDrag as ge, createWebrtcBundle as gf, createWebsocketBundle as gg, cssObjectToStyleMap as gh, currentColorScheme as gi, currentLayout as gj, currentStyle as gk, defaultCssVars as gl, defaultRadius as gm, defaultThemeColors as gn, deleteElementsByIds as go, deleteVersion as gp, demoteNode as gq, deriveModel3DBlobUrl as gr, derivePresenceList as gs, describeSmartArtBounds as gt, disableGlowPatch as gu, disableInnerShadowPatch as gv, disableOuterShadowPatch as gw, disableReflectionPatch as gx, disableSoftEdgePatch as gy, duplicateElementById as gz, AccountPageComponent as h, isInjectableUrl as h$, formatBytes as h0, formatCursorLabel as h1, formatElapsed as h2, formatFileSize as h3, formatPropertyDate as h4, formatTime as h5, fpsToFrameIntervalMs as h6, generateBroadcastRoomId as h7, generateCommentId as h8, generateCustomShowId as h9, getTextWarp as hA, getTouchDistance as hB, getWarpCategory as hC, getWarpPath as hD, gradientStateFromStyle as hE, gradientStateOf as hF, gradientStatePatch as hG, gridColumns as hH, groupIssuesBySeverity as hI, hasAnimation as hJ, hasCopyableFormat as hK, hasExistingLink as hL, hasExitedFullscreen as hM, hasGradientFill as hN, hasPressureVariation as hO, hasVisibleSlideAfter as hP, headerLabel as hQ, imageDimensions as hR, inkViewBox as hS, insertTableElementColumn as hT, insertTableElementRow as hU, interpolateWidth as hV, isAudienceTab as hW, isBold as hX, isBrowserOpenableMime as hY, isChildNode as hZ, isElementInteractive as h_, generatePressureCircles as ha, generateTicks as hb, getClrChangeParams as hc, getContainerStyle as hd, getDuotoneFilterDef as he, getImageSrc as hf, getLocalStorageUsageSummary as hg, getOleAriaLabel as hh, getOleBadgeLabel as hi, getOleDisplayName as hj, getOleDownloadFileName as hk, getOleTypeColor as hl, getOleTypeLabel as hm, getPasswordStrength as hn, getPatternSvg as ho, getPlaceholderStyle as hp, getVersions as hq, getResolvedShapeClipPath as hr, getResolvedShapeClipPathFor as hs, getSessionTabId as ht, getShapeFillStrokeStyle as hu, getSlideBackgroundStyle as hv, getSlideTransitionAnimations as hw, getSmartArtNodeBounds as hx, getSpeechRecognitionCtor as hy, getTextBlockStyle as hz, ActionSettingsPanelComponent as i, parseAudienceNonce as i$, isItalic as i0, isLegacyBinaryPresentation as i1, isPpactionUrl as i2, isPresenterMessage as i3, isSigned as i4, isSupportedPresentationFile as i5, isTextElement as i6, isTwoTableFocus as i7, isUnderline as i8, isUrlSafe as i9, narrowToCircle as iA, narrowToPolygon as iB, narrowToRect as iC, newChartElement as iD, newEquationElement as iE, newPresetShapeElement as iF, newShapeElement as iG, newSmartArtElement as iH, newTableElement as iI, newTextElement as iJ, nextVisibleIndex as iK, nodeBold as iL, nodeEditBox as iM, nodeFillColor as iN, nodeFontColor as iO, nodeIdFromKey as iP, nodeItalic as iQ, nodeStyle as iR, normalizeFontFormat as iS, normalizeSlidesPerPage as iT, normalizeValue as iU, numFromEvent as iV, ommlToMathml as iW, ooxmlDashToCssBorderStyle as iX, openNativeEyeDropper as iY, overallStatus as iZ, paletteColor as i_, isValidRoomId as ia, isViewportBackgroundPressTarget as ib, isZoomActivationKey as ic, issueTrackKey as id, issueTypeLabel as ie, keyToLabel as ig, lastVisibleIndex as ih, latexToMathml as ii, layoutConnectorPaints as ij, layoutNodeLabels as ik, linePointsToSvgString as il, lineSpacingPatch as im, loadAudienceContent as io, loadSessionDeck as ip, mediaFallbackFor as iq, mediaSurfaceFor as ir, mergeCaptionResults as is, mergeDown as it, mergeRight as iu, mergeSelection as iv, moveElementBy as iw, moveNodeDown as ix, moveNodeUp as iy, msToFrameDelayCs as iz, AdvancedChartEditorComponent as j, restoreSessionDeck as j$, parseNodeTextarea as j0, partitionSlides as j1, patchChartData as j2, patchChartStyle as j3, patchTableData as j4, patchTextStyle as j5, patternPresetOptions as j6, pendingElementStyles as j7, pickColorByClickFallback as j8, pickFile as j9, removeElementAnimation as jA, removeGradientStopPatch as jB, removeNode as jC, removeTableElementRow as jD, removeSeries as jE, renderToCanvas as jF, reorderAnimationDown as jG, reorderAnimationUp as jH, replaceInSlides as jI, replaceMatch as jJ, requestPresentationFullscreen as jK, resizeElement as jL, resolveCaptionTracks as jM, resolveChartKind as jN, resolveFontVariant as jO, resolveHyperlinkHref as jP, resolveInteractiveElementId as jQ, resolveMediaSrc as jR, resolveOleType as jS, resolveParagraphBullet as jT, resolvePresenterNotes as jU, resolveProfileInitial as jV, resolveRegionCode as jW, resolveSlideAutoAdvanceMs as jX, resolvePalette as jY, resolveThemeCatalogEntry as jZ, resolveTransitionDuration as j_, pickSupportedMimeType as ja, planGifFrames as jb, planVideoSegments 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, redistributeColumnWidth as jt, registerCrossSlideAudio as ju, rememberSessionDeck as jv, removeAnimation as jw, removeCategory as jx, removeTableElementColumn as jy, removeCommentFromList as jz, AiChangeOverlayComponent as k, shapeStylePatch as k$, revealedElementStyles as k0, routeOrthogonalConnector as k1, rowStyle as k2, rulerDragToGuidePosition as k3, rulerHighlight as k4, rulerStripTicks as k5, sampleColorFromSlide as k6, sanitizeColor as k7, sanitizeSlideIndex as k8, sanitizeUserName as k9, setDataPointFill as kA, setDataPointLabel as kB, setDataPointMarker as kC, setDelay as kD, setDirection as kE, setDuration as kF, setElementPosition as kG, setGridlineStyle as kH, setLayout as kI, setLegend as kJ, setNodeStyle as kK, setNodeText as kL, setRepeatCount as kM, setRepeatMode as kN, setSequence as kO, setSeriesChartType as kP, setSeriesColor as kQ, setSeriesErrorBars as kR, setSeriesMarker as kS, setSeriesName as kT, setSeriesTrendline as kU, setSeriesValue as kV, setStyle as kW, setTimingCurve as kX, setTitle as kY, setTrigger as kZ, setTriggerShapeId as k_, saveViewerProfile as ka, savedPresentationFileName as kb, scanAvailableFonts as kc, searchSlides as kd, seedBroadcastFields as ke, seedHyperlinkDraft as kf, seedPropertiesDraft as kg, seedShareFields as kh, segmentFrameCount as ki, selectValue$2 as kj, sendBackward as kk, sendToBack as kl, sequentialColorScale as km, serializeWriteBack as kn, seriesColor as ko, setAnimationEmphasis as kp, setAnimationEntrance as kq, setAnimationExit as kr, setAxis as ks, setAxisLogScale as kt, setAxisTitleStyle as ku, setCategoryLabel as kv, setCellText as kw, setColorScheme as kx, setDataLabels as ky, setDataPointExplosion as kz, AiChatPanelComponent as l, sheetAfterNavigate as l0, shouldBlockClickAdvance as l1, shouldUseSvgWarp as l2, showDirectionPicker as l3, showsTemplateAffordance as l4, signatureCountLabel as l5, signatureKey as l6, signatureTimestamp as l7, signerName as l8, statusLabel as l9, toggleCommentResolvedInList as lA, toggleNodeBold as lB, toggleNodeItalic as lC, toggleSheet as lD, topLevelNodeCount as lE, transformSelectedTextCase as lF, translationsEn as lG, updateElementById as lH, updateGlowPatch as lI, updateGradientStopPatch as lJ, updateInnerShadowPatch as lK, updateOuterShadowPatch as lL, updateReflectionPatch as lM, vAlignPatch as lN, validatePassword as lO, validatePrintSettings as lP, validateRoomId as lQ, valueToY as lR, vermilionDarkColors as lS, vermilionDarkTheme as lT, vermilionLightColors as lU, vermilionLightTheme as lV, vermilionRadius as lW, waypointsToPathD as lX, worstStatus as lY, zoomTargetSlideIndex as lZ, slideNumberOf as la, slidesWithReappliedLayout as lb, smartArtNodes as lc, paletteColour as ld, snapToGridStep as le, splitCursorCell as lf, splitMergedCell as lg, statusKind as lh, statusLabel$1 as li, storeAudienceContent as lj, stringFromEvent$5 as lk, strokeColorOf as ll, strokeToInkElement as lm, strokeWidthOf as ln, styleShadowFilter as lo, textAdvancedPatch as lp, textAdvancedStateFromStyle as lq, textAdvancedStateOf as lr, textColorOf as ls, textDirectionPatch as lt, textStyleOf as lu, textStylePatch as lv, themeStyle as lw, themeToCssVars as lx, thumbnailHeight as ly, thumbnailZoom as lz, AiChatService as m, AiComposerComponent as n, AiFocusBarComponent as o, AiFocusHighlightOverlayComponent as p, AiHistoryMenuComponent as q, AiHistoryService as r, AiMessageListComponent as s, toChatSummary as t, AiPanelStore as u, AiProposalCardComponent as v, AiSettingsSectionComponent as w, AiToolCallCardComponent as x, AnimationAuthorPanelComponent as y, AnimationPanelComponent as z };
137497
- //# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-CgQt2SkW.mjs.map
137943
+ export { CommentsPanelComponent as $, ALIGN_OPTIONS as A, AnimationPlaybackService as B, AutosaveRecoveryDialogComponent as C, AutosaveService as D, BroadcastDialogComponent as E, CHART_EDITOR_STYLES as F, CURSOR_PALETTE as G, CanvasFitService as H, ChartAxisOptionsComponent as I, ChartAxisStyleOptionsComponent as J, ChartComboTypeOptionsComponent as K, ChartDataEditorComponent as L, ChartDataLabelOptionsComponent as M, ChartDatapointMarkerOptionsComponent as N, ChartDatapointOptionsComponent as O, ChartDisplayOptionsComponent as P, ChartElementViewComponent as Q, ChartErrorBarOptionsComponent as R, ChartMarkerOptionsComponent as S, ChartPartSelectionService as T, ChartPrimitivesComponent as U, ChartRendererComponent as V, ChartTrendlineOptionsComponent as W, CollaborationCursorsComponent as X, CollaborationService as Y, ColorChangedImageComponent as Z, CommentMarkersOverlayComponent as _, ANIMATION_PRESET_CATEGORIES as a, InspectorPanelComponent as a$, CommentsService as a0, ComparePanelComponent as a1, ConnectorRendererComponent as a2, ConnectorTextOverlayComponent as a3, CustomShowsComponent as a4, DATA_TABLE_HEADER_H as a5, DATA_TABLE_KEY_W as a6, DATA_TABLE_PADDING as a7, DATA_TABLE_ROW_H as a8, DEFAULT_BOUNDS as a9, EditorToolbarComponent as aA, EffectsPanelComponent as aB, ElementRendererComponent as aC, EmbeddedFontsService as aD, EncryptedFileDialogComponent as aE, EquationEditorDialogComponent as aF, EquationRendererComponent as aG, EquationTemplateGalleryComponent as aH, ExportProgressModalComponent as aI, ExportService as aJ, FieldContextService as aK, FindBarComponent as aL, FindReplaceBarComponent as aM, FollowModeBarComponent as aN, FontEmbeddingListComponent as aO, FontEmbeddingPanelComponent as aP, GALLERY_THEME_PRESETS as aQ, GRIDLINE_COLOR as aR, GradientPickerComponent as aS, HANDOUT_OPTIONS as aT, HeaderFooterDialogComponent as aU, HyperlinkDialogComponent as aV, ImagePropertiesPanelComponent as aW, InkDrawingService as aX, InkRendererComponent as aY, InsertSmartArtDialogComponent as aZ, InspectorPaneHeaderComponent 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_PALETTE$1 as ag, DEFAULT_PATTERN_FILL_PRESET as ah, DEFAULT_PRINT_SETTINGS as ai, DEFAULT_SLIDE_BACKGROUND as aj, DEFAULT_STROKE_COLOR as ak, DEFAULT_STYLE as al, DEFAULT_TABLE_ROW_HEIGHT as am, DEFAULT_TEXT_COLOR$1 as an, DEFAULT_VIEWER_PROFILE as ao, DIRECTIONAL_PRESETS as ap, DIRECTION_OPTIONS as aq, DocumentPropertiesCardComponent as ar, EMBEDDED_FONTS_STYLE_ID as as, EMPHASIS_PRESETS as at, ENTRANCE_PRESETS as au, TEMPLATES as av, EXIT_PRESETS as aw, EditorContextMenuComponent as ax, EditorHistory as ay, EditorStateService as az, AUDIENCE_HASH as b, RibbonComponent as b$, IsMobileService as b0, KeepAnnotationsDialogComponent as b1, LOCALE_CATALOG as b2, LONG_PRESS_DURATION_MS as b3, LONG_PRESS_MOVE_TOLERANCE_PX as b4, LoadContentService as b5, LocalPresencePublisher as b6, MAX_ZOOM_SCALE as b7, MIN_ZOOM_SCALE as b8, MOTION_PATH_COLUMNS as b9, PasswordStrengthMeterComponent as bA, PowerPointViewerComponent as bB, PresentToolbarAutoHide as bC, PresentationAnnotationOverlayComponent as bD, PresentationAnnotationsService as bE, PresentationOverlayComponent as bF, PresentationPropertiesPanelComponent as bG, PresentationSettingsCardComponent as bH, PresentationSubtitleBarComponent as bI, PresentationToolbarComponent as bJ, PresentationTransitionOverlayComponent as bK, PresenterViewComponent as bL, PresenterWindowService as bM, PrintDialogComponent as bN, PrintService as bO, PrintSettingsPanelComponent as bP, PropertiesDialogComponent as bQ, REPEAT_MODE_OPTIONS as bR, RESIZE_HANDLES as bS, RULER_FONT_SIZE as bT, RULER_THICKNESS as bU, ReadingViewOverlayComponent as bV, RemoteSelectionOverlayComponent as bW, RibbonAnimationGalleryComponent as bX, RibbonAnimationsSectionComponent as bY, RibbonArrangeSectionComponent as bZ, RibbonColorPopoverComponent as b_, MediaPreviewComponent as ba, MediaPropertiesPanelComponent as bb, MediaRendererComponent as bc, MediaTrimTimelineComponent as bd, MobileBottomBarComponent as be, MobileMenuSheetComponent as bf, MobilePresenterViewComponent as bg, MobileSheetComponent as bh, MobileSlidesSheetComponent as bi, MobileToolbarComponent as bj, ModalDialogComponent as bk, Model3DRendererComponent as bl, NotesHandoutCardComponent as bm, NotesPanelComponent as bn, NotesToolbarComponent as bo, OleRendererComponent as bp, OutlineViewOverlayComponent as bq, POWER_POINT_VIEWER_PROVIDERS as br, PPTX_OPEN_ACCEPT as bs, PRESENTATION_OPEN_EXTENSIONS as bt, PRESENTER_CHANNEL_NAME as bu, PRESENTER_MSG_ORIGIN as bv, PRESENTER_TIMER_SEGMENT_MS as bw, PX_PER_CM as bx, PX_PER_INCH as by, PasswordProtectionDialogComponent as bz, AUDIENCE_NONCE_KEY as c, TEXT_3D_TOP_BEVEL_KEYS as c$, RibbonDesignSectionComponent as c0, RibbonDrawSectionComponent as c1, RibbonDrawingGroupComponent as c2, RibbonEditingSectionComponent as c3, RibbonFileSectionComponent as c4, RibbonFontControlsComponent as c5, RibbonHomeSectionComponent as c6, RibbonHyperlinkButtonComponent as c7, RibbonInsertFieldsComponent as c8, RibbonInsertSectionComponent as c9, SettingsLanguageTabComponent as cA, ShareDialogComponent as cB, ShortcutPanelComponent as cC, ShowOptionsFieldsetComponent as cD, ShowSlidesFieldsetComponent as cE, SignatureStrippedDialogComponent as cF, SignaturesPanelComponent as cG, SignaturesService as cH, SlideBackgroundCardComponent as cI, SlideCanvasComponent as cJ, SlideDefaultInspectorComponent as cK, SlideDiffChangesComponent as cL, SlideDiffRowComponent as cM, SlideDiffThumbnailsComponent as cN, SlideSizeCardComponent as cO, SlideSorterOverlayComponent as cP, SlideThemeOverridePanelComponent as cQ, SlideTransitionCardComponent as cR, SlidesPanelComponent as cS, SmartArt3DRendererComponent as cT, SmartArt3DService as cU, SmartArtPreviewComponent as cV, SmartArtPropertiesComponent as cW, SmartArtRendererComponent as cX, StatusBarComponent as cY, TABLE_STRUCTURE_TOGGLES as cZ, TEXT_3D_BOTTOM_BEVEL_KEYS as c_, RibbonMotionPathGalleryComponent as ca, RibbonParagraphControlsComponent as cb, RibbonPrimaryRowComponent as cc, RibbonReviewSectionComponent as cd, RibbonShapeExtrasComponent as ce, RibbonSlideshowSectionComponent as cf, RibbonTransitionsSectionComponent as cg, RibbonViewSectionComponent as ch, RulerGuidesService as ci, SEQUENCE_OPTIONS as cj, SEVERITY_GROUPS as ck, SEVERITY_LABELS as cl, SHORTCUT_REFERENCE_ITEMS as cm, SLIDE_TRANSITION_KEYFRAMES as cn, DEFAULT_PALETTE as co, PALETTES$1 as cp, SMART_ART_COLOR_SCHEMES as cq, SMART_ART_STYLE_OPTIONS as cr, SUB_ITEM_LABEL as cs, SVG_WARP_PRESETS as ct, SWIPE_MAX_VERTICAL_PX as cu, SWIPE_THRESHOLD_PX as cv, SelectionPaneComponent as cw, SetUpSlideShowDialogComponent as cx, SettingsAppearanceTabComponent as cy, SettingsDialogComponent as cz, AVATAR_COLOR_SWATCHES as d, animationPresetLabelKey as d$, TEXT_DIRECTION_OPTIONS$1 as d0, THEME_CATALOG as d1, TIMING_CURVE_OPTIONS as d2, TRIGGER_OPTIONS as d3, TYPE_LABELS as d4, TableCellAdvancedFillComponent as d5, TableCellFormattingComponent as d6, TableDataEditorComponent as d7, TablePropertiesComponent as d8, TableRendererComponent as d9, ViewerFileIOService as dA, ViewerFindReplaceService as dB, ViewerFormatPainterService as dC, ViewerInspectorPanelService as dD, ViewerKeyboardService as dE, ViewerMobileSheetService as dF, ViewerPresentationModeService as dG, ViewerThemeGalleryService as dH, ViewerTouchGesturesService as dI, ViewerZoomService as dJ, WEBM_MIME_CANDIDATES as dK, WriteBackScheduler as dL, ZERO_LINE_COLOR as dM, ZoomNavigationService as dN, ZoomRendererComponent as dO, ZoomTargetService as dP, addCategory as dQ, addCommentToList as dR, addGradientStopPatch as dS, addItem as dT, addSeries as dU, addSubItem as dV, advanceStep as dW, affordanceElements as dX, aiToggleVisible as dY, alignPatch as dZ, animationFor as d_, TableResizeOverlayComponent as da, TableSelectionService as db, TagsCardComponent as dc, Text3DBevelSectionComponent as dd, Text3DPanelComponent as de, TextAdvancedPanelComponent as df, ThemeEditorFieldsComponent as dg, ThemeGalleryComponent as dh, ThemeSelectorCardComponent as di, TitleBarComponent as dj, TitleBarSearchComponent as dk, TransitionDirectionPickerComponent as dl, TransitionPreviewComponent as dm, VALIGN_OPTIONS as dn, VIEWER_THEME as dp, VersionHistoryPanelComponent as dq, ViewerCanvasEditingService as dr, ViewerCollabCursorService as ds, ViewerCollaborationSessionService as dt, ViewerCompareService as du, ViewerCustomShowsService as dv, ViewerDialogsService as dw, ViewerDocumentPropertiesService as dx, ViewerExportService as dy, ViewerExtraDialogsComponent as dz, AXIS_LABEL_COLOR as e, buildWaterfallViewModel as e$, annotationMapToInkInserts as e0, applyAcceptedDiff as e1, applyAnimationPreset as e2, applyFindReplacements as e3, applyFormatToElement as e4, applyMove as e5, applyResize as e6, asMediaElement as e7, assignUserColor as e8, attachShowVisibilityPause as e9, buildEquationSegment as eA, buildFallbackViewModel as eB, buildFontFaceRule as eC, buildGradientFillCss as eD, buildGridlinesAndLabels as eE, buildHyperlinkPatch as eF, buildInkContainerStyle as eG, buildInkStrokes as eH, buildLegend as eI, buildMarkTooltip as eJ, buildModel3DContainerStyle as eK, buildModel3DViewModel as eL, buildOleActionModel as eM, buildOleInfoRows as eN, buildPatternFillCss as eO, buildPrintHtmlDocument as eP, buildPropertiesPatch as eQ, buildRegionMapViewModel as eR, buildSaveSlides as eS, buildShareUrl as eT, buildSmartArtInsertElement as eU, buildSmartArtNodes as eV, buildStockViewModel as eW, buildSurfaceViewModel as eX, buildTableViewModel as eY, buildTreemapViewModel as eZ, buildTrimFragment as e_, attachTouchGestures as ea, axisTickValues as eb, beginNodeEdit as ec, bevelSizePatch as ed, boolFromEvent as ee, bringForward as ef, bringToFront as eg, buildBarActions as eh, buildBroadcastConfig as ei, buildBroadcastViewerUrl as ej, buildCategoryLabels as ek, buildCellParagraphs as el, buildChartViewModel as em, buildChatLogExport as en, buildChatLogMarkdown as eo, buildChromeStyle as ep, buildClearHyperlinkPatch as eq, buildClickGroups as er, buildColStyles as es, buildCollaborationConfig as et, buildComboViewModel as eu, buildCssGradientFromShapeStyle as ev, buildDuotoneFilter as ew, buildDuotoneFilterId as ex, buildEmbeddedFontStyles as ey, buildEquationElement as ez, AccessibilityPanelComponent as f, computeScatterXDomain as f$, buildZeroLine as f0, buildZoomContainerStyle as f1, buildZoomViewModel as f2, bulletIndentPx as f3, canAddTopLevelNode as f4, canGroupSelection as f5, canRemoveTopLevelNode as f6, canSetStrokeWidth as f7, canStartBroadcast as f8, canStartShare as f9, commitNodeText as fA, computeAlign as fB, computeAxisTitlePrimitives as fC, computeBarRects as fD, computeBubbleRadius as fE, computeCornerHandle as fF, computeDataTablePrimitives as fG, computeDistribute as fH, computeDrawingViewBox as fI, computeErrorBarPrimitives as fJ, computeFocusTargets as fK, computeHandleBoxes as fL, computeHandoutLayout as fM, computeIsMobile as fN, computeIsTablet as fO, computeLinePoints as fP, computeLinearRegression as fQ, computePageCount as fR, computePieLayout as fS, computePieSlicePath as fT, computePieSlices as fU, computePlotLayout as fV, computeRSquared as fW, computeRadarPoints as fX, computeResizeHandleBoxes as fY, computeRotateHandleBox as fZ, computeScatterDots as f_, canUngroupSelection as fa, canUseClipboard as fb, captionDisplayText as fc, cellRunStyle as fd, cellStyleToStyleMap as fe, cellTdStyle as ff, changeCountLabel as fg, changeIcon as fh, characterSpacingPatch as fi, chartPreserveAspectRatio as fj, checkFontAvailable as fk, clampCursorPosition as fl, clampGifDimensions as fm, clampIndex as fn, clampNotesFontSize as fo, clampScale as fp, clampStep as fq, clearAllLocalViewerData as fr, clearAudienceContent as fs, cn as ft, collectAccessibilityIssues as fu, collectElementText as fv, collectSlideText as fw, collectStoredChats as fx, collectUsedFontFamilies as fy, columnWidthStyle as fz, AccessibilityService as g, formatCursorLabel as g$, computeSelectionBoxes as g0, computeSingleSelected as g1, computeSlideIndices as g2, computeSnap as g3, computeStackedBarRects as g4, computeStackedValueRange as g5, computeTrendlinePrimitives as g6, computeValueRange as g7, convertOmmlToMathMl as g8, copyFormatFromElement as g9, durationOf as gA, effectsStateOf as gB, enableGlowPatch as gC, enableInnerShadowPatch as gD, enableOuterShadowPatch as gE, enableReflectionPatch as gF, enableSoftEdgePatch as gG, encodeGif as gH, endShowMediaCleanup as gI, estimatePageCount as gJ, exitPresentationFullscreen as gK, exportAiChatLogs as gL, extractPathPoints as gM, eyedropperAvailable as gN, fillColorOf$1 as gO, findInSlides as gP, findOwningSlideIndex as gQ, findSlideIndexByElementId as gR, firstVisibleIndex as gS, fitPolynomial as gT, fitZoom as gU, focusTargetChips as gV, fontMimeForFormat as gW, fontSizeOf as gX, forgetSessionDeck as gY, formatAxisValue as gZ, formatBytes as g_, countAccessibilityIssues as ga, countAnnotationStrokes as gb, createAngularAiBridge as gc, createCustomShow as gd, createSwipeDismissDrag as ge, createWebrtcBundle as gf, createWebsocketBundle as gg, cssObjectToStyleMap as gh, currentColorScheme as gi, currentLayout as gj, currentStyle as gk, defaultCssVars as gl, defaultRadius as gm, defaultThemeColors as gn, deleteElementsByIds as go, deleteVersion as gp, demoteNode as gq, deriveModel3DBlobUrl as gr, derivePresenceList as gs, describeSmartArtBounds as gt, disableGlowPatch as gu, disableInnerShadowPatch as gv, disableOuterShadowPatch as gw, disableReflectionPatch as gx, disableSoftEdgePatch as gy, duplicateElementById as gz, AccountPageComponent as h, isLegacyBinaryPresentation as h$, formatElapsed as h0, formatFileSize as h1, formatPropertyDate as h2, formatTime as h3, fpsToFrameIntervalMs as h4, generateBroadcastRoomId as h5, generateCommentId as h6, generateCustomShowId as h7, generatePressureCircles as h8, generateTicks as h9, getWarpCategory as hA, getWarpPath as hB, gradientStateFromStyle as hC, gradientStateOf as hD, gradientStatePatch as hE, gridColumns as hF, groupIssuesBySeverity as hG, hasAnimation as hH, hasCopyableFormat as hI, hasExistingLink as hJ, hasExitedFullscreen as hK, hasGradientFill as hL, hasPressureVariation as hM, hasVisibleSlideAfter as hN, headerLabel as hO, imageDimensions as hP, inkViewBox as hQ, insertTableElementColumn as hR, insertTableElementRow as hS, interpolateWidth as hT, isAudienceTab as hU, isBold as hV, isBrowserOpenableMime as hW, isChildNode as hX, isElementInteractive as hY, isInjectableUrl as hZ, isItalic as h_, getClrChangeParams as ha, getContainerStyle as hb, getDuotoneFilterDef as hc, getImageSrc as hd, getLocalStorageUsageSummary as he, getOleAriaLabel as hf, getOleBadgeLabel as hg, getOleDisplayName as hh, getOleDownloadFileName as hi, getOleTypeColor as hj, getOleTypeLabel as hk, getPasswordStrength as hl, getPatternSvg as hm, getPlaceholderStyle as hn, getVersions as ho, getResolvedShapeClipPath as hp, getResolvedShapeClipPathFor as hq, getSessionTabId as hr, getShapeFillStrokeStyle as hs, getSlideBackgroundStyle as ht, getSlideTransitionAnimations as hu, getSmartArtNodeBounds as hv, getSpeechRecognitionCtor as hw, getTextBlockStyle as hx, getTextWarp as hy, getTouchDistance as hz, ActionSettingsPanelComponent as i, partitionSlides as i$, isPpactionUrl as i0, isPresenterMessage as i1, isSigned as i2, isSupportedPresentationFile as i3, isTextElement as i4, isTwoTableFocus as i5, isUnderline as i6, isUrlSafe as i7, isValidRoomId as i8, isViewportBackgroundPressTarget as i9, narrowToRect as iA, newChartElement as iB, newEquationElement as iC, newPresetShapeElement as iD, newShapeElement as iE, newSmartArtElement as iF, newTableElement as iG, newTextElement as iH, nextVisibleIndex as iI, nodeBold as iJ, nodeEditBox as iK, nodeFillColor as iL, nodeFontColor as iM, nodeIdFromKey as iN, nodeItalic as iO, nodeStyle as iP, normalizeFontFormat as iQ, normalizeSlidesPerPage as iR, normalizeValue as iS, numFromEvent as iT, ommlToMathml as iU, ooxmlDashToCssBorderStyle as iV, openNativeEyeDropper as iW, overallStatus as iX, paletteColor as iY, parseAudienceNonce as iZ, parseNodeTextarea as i_, isZoomActivationKey as ia, issueTrackKey as ib, issueTypeLabel as ic, keyToLabel as id, lastVisibleIndex as ie, latexToMathml as ig, layoutConnectorPaints as ih, layoutNodeLabels as ii, linePointsToSvgString as ij, lineSpacingPatch as ik, loadAudienceContent as il, loadSessionDeck as im, mediaFallbackFor as io, mediaSurfaceFor as ip, mergeCaptionResults as iq, mergeDown as ir, mergeRight as is, mergeSelection as it, moveElementBy as iu, moveNodeDown as iv, moveNodeUp as iw, msToFrameDelayCs as ix, narrowToCircle as iy, narrowToPolygon as iz, AdvancedChartEditorComponent as j, rowStyle as j$, patchChartData as j0, patchChartStyle as j1, patchTableData as j2, patchTextStyle as j3, patternPresetOptions as j4, pendingElementStyles as j5, pickColorByClickFallback as j6, pickFile as j7, pickSupportedMimeType as j8, planGifFrames as j9, removeTableElementRow as jA, removeSeries as jB, renderToCanvas as jC, reorderAnimationDown as jD, reorderAnimationUp as jE, replaceInSlides as jF, replaceMatch as jG, requestPresentationFullscreen as jH, resizeElement as jI, resolveCaptionTracks as jJ, resolveChartKind as jK, resolveFontVariant as jL, resolveHyperlinkHref as jM, resolveInteractiveElementId as jN, resolveMediaSrc as jO, resolveOleType as jP, resolveParagraphBullet as jQ, resolvePresenterNotes as jR, resolveProfileInitial as jS, resolveRegionCode as jT, resolveSlideAutoAdvanceMs as jU, resolvePalette as jV, resolveThemeCatalogEntry as jW, resolveTransitionDuration as jX, restoreSessionDeck as jY, revealedElementStyles as jZ, routeOrthogonalConnector as j_, planVideoSegments as ja, pointsToSvgPathD as jb, presenceToCursors as jc, presentationBaseName as jd, presentationStageStyle as je, presenterTimerProgress as jf, presetByLayout as jg, presetsForCategory as jh, pressuresToWidths as ji, prevVisibleIndex as jj, projectDrawingShapes as jk, promoteNode as jl, provideViewerTheme as jm, radarAngle as jn, radarRingPoints as jo, readAsDataUrl as jp, recordWebm as jq, registerCrossSlideAudio as jr, rememberSessionDeck as js, removeAnimation as jt, removeCategory as ju, removeTableElementColumn as jv, removeCommentFromList as jw, removeElementAnimation as jx, removeGradientStopPatch as jy, removeNode as jz, AiChangeOverlayComponent as k, shouldUseSvgWarp as k$, rulerDragToGuidePosition as k0, rulerHighlight as k1, rulerStripTicks as k2, sampleColorFromSlide as k3, sanitizeColor as k4, sanitizeSlideIndex as k5, sanitizeUserName as k6, saveViewerProfile as k7, savedPresentationFileName as k8, scanAvailableFonts as k9, setDelay as kA, setDirection as kB, setDuration as kC, setElementPosition as kD, setGridlineStyle as kE, setLayout as kF, setLegend as kG, setNodeStyle as kH, setNodeText as kI, setRepeatCount as kJ, setRepeatMode as kK, setSequence as kL, setSeriesChartType as kM, setSeriesColor as kN, setSeriesErrorBars as kO, setSeriesMarker as kP, setSeriesName as kQ, setSeriesTrendline as kR, setSeriesValue as kS, setStyle as kT, setTimingCurve as kU, setTitle as kV, setTrigger as kW, setTriggerShapeId as kX, shapeStylePatch$1 as kY, sheetAfterNavigate as kZ, shouldBlockClickAdvance as k_, searchSlides as ka, seedBroadcastFields as kb, seedHyperlinkDraft as kc, seedPropertiesDraft as kd, seedShareFields as ke, segmentFrameCount as kf, selectValue$2 as kg, sendBackward as kh, sendToBack as ki, sequentialColorScale as kj, serializeWriteBack as kk, seriesColor as kl, setAnimationEmphasis as km, setAnimationEntrance as kn, setAnimationExit as ko, setAxis as kp, setAxisLogScale as kq, setAxisTitleStyle as kr, setCategoryLabel as ks, setCellText as kt, setColorScheme as ku, setDataLabels as kv, setDataPointExplosion as kw, setDataPointFill as kx, setDataPointLabel as ky, setDataPointMarker as kz, AiChatPanelComponent as l, showDirectionPicker as l0, showsTemplateAffordance as l1, signatureCountLabel as l2, signatureKey as l3, signatureTimestamp as l4, signerName as l5, statusLabel as l6, slideNumberOf as l7, slidesWithReappliedLayout as l8, smartArtNodes as l9, toggleSheet as lA, topLevelNodeCount as lB, transformSelectedTextCase as lC, translationsEn as lD, updateElementById as lE, updateGlowPatch as lF, updateGradientStopPatch as lG, updateInnerShadowPatch as lH, updateOuterShadowPatch as lI, updateReflectionPatch as lJ, vAlignPatch as lK, validatePassword as lL, validatePrintSettings as lM, validateRoomId as lN, valueToY as lO, vermilionDarkColors as lP, vermilionDarkTheme as lQ, vermilionLightColors as lR, vermilionLightTheme as lS, vermilionRadius as lT, waypointsToPathD as lU, worstStatus as lV, zoomTargetSlideIndex as lW, paletteColour as la, snapToGridStep as lb, splitCursorCell as lc, splitMergedCell as ld, statusKind as le, statusLabel$1 as lf, storeAudienceContent as lg, stringFromEvent$5 as lh, strokeColorOf as li, strokeToInkElement as lj, strokeWidthOf as lk, styleShadowFilter as ll, textAdvancedPatch as lm, textAdvancedStateFromStyle as ln, textAdvancedStateOf as lo, textColorOf as lp, textDirectionPatch as lq, textStyleOf as lr, textStylePatch as ls, themeStyle as lt, themeToCssVars as lu, thumbnailHeight as lv, thumbnailZoom as lw, toggleCommentResolvedInList as lx, toggleNodeBold as ly, toggleNodeItalic as lz, AiChatService as m, AiComposerComponent as n, AiFocusBarComponent as o, AiFocusHighlightOverlayComponent as p, AiHistoryMenuComponent as q, AiHistoryService as r, AiMessageListComponent as s, toChatSummary as t, AiPanelStore as u, AiProposalCardComponent as v, AiSettingsSectionComponent as w, AiToolCallCardComponent as x, AnimationAuthorPanelComponent as y, AnimationPanelComponent as z };
137944
+ //# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-BLdLZTxr.mjs.map