pptx-angular-viewer 2.18.7 → 2.19.1

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';
10358
10377
  for (let si = 0; si < chartData.series.length; si++) {
10359
- const series = chartData.series[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;
10436
+ for (let si = 0; si < chartData.series.length; 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,
@@ -16309,7 +16358,7 @@ function withChartTitle(chartData, title) {
16309
16358
  * Without a threshold every click on a bar would commit a (tiny) value change,
16310
16359
  * so a user could not select a mark without editing it.
16311
16360
  */
16312
- const CHART_DRAG_THRESHOLD_PX$1 = 3;
16361
+ const CHART_DRAG_THRESHOLD_PX = 3;
16313
16362
  const STYLE_ELEMENT_ID$1 = 'pptx-chart-interaction-styles';
16314
16363
  const INTERACTION_CSS$1 = `
16315
16364
  .pptx-chart-interactive svg [data-chart-part] { pointer-events: auto; cursor: pointer; }
@@ -16345,19 +16394,19 @@ function ensureChartInteractionStyles$1() {
16345
16394
  /** Class a chart root carries while its marks are grabbable. */
16346
16395
  const CHART_INTERACTIVE_CLASS = 'pptx-chart-interactive';
16347
16396
  /** Class marking the currently selected mark. */
16348
- const CHART_PART_SELECTED_CLASS$1 = 'pptx-chart-part-selected';
16397
+ const CHART_PART_SELECTED_CLASS = 'pptx-chart-part-selected';
16349
16398
  /**
16350
16399
  * Re-apply the selected-part highlight class inside `root`.
16351
16400
  *
16352
16401
  * Called after every render: the projectors re-create the SVG marks on each
16353
16402
  * chart change, which drops DOM-only classes. A null `part` only clears.
16354
16403
  */
16355
- function applyChartPartHighlight$1(root, part) {
16404
+ function applyChartPartHighlight(root, part) {
16356
16405
  if (!root) {
16357
16406
  return;
16358
16407
  }
16359
- for (const node of root.querySelectorAll(`.${CHART_PART_SELECTED_CLASS$1}`)) {
16360
- node.classList.remove(CHART_PART_SELECTED_CLASS$1);
16408
+ for (const node of root.querySelectorAll(`.${CHART_PART_SELECTED_CLASS}`)) {
16409
+ node.classList.remove(CHART_PART_SELECTED_CLASS);
16361
16410
  }
16362
16411
  if (!part) {
16363
16412
  return;
@@ -16367,7 +16416,7 @@ function applyChartPartHighlight$1(root, part) {
16367
16416
  : ':not([data-chart-point])';
16368
16417
  const selector = `[data-chart-part='${part.role}'][data-chart-series='${part.seriesIndex}']${pointSelector}`;
16369
16418
  for (const node of root.querySelectorAll(selector)) {
16370
- node.classList.add(CHART_PART_SELECTED_CLASS$1);
16419
+ node.classList.add(CHART_PART_SELECTED_CLASS);
16371
16420
  }
16372
16421
  }
16373
16422
  /**
@@ -16380,7 +16429,7 @@ function applyChartPartHighlight$1(root, part) {
16380
16429
  * preview, or the axis rescales under the pointer mid-drag and the mark runs
16381
16430
  * away from the cursor.
16382
16431
  */
16383
- function beginChartValueDrag$1(params) {
16432
+ function beginChartValueDrag(params) {
16384
16433
  const { part, viewModel, chartData, clientY } = params;
16385
16434
  if (part.role !== 'dataPoint' || part.pointIndex === undefined || !viewModel.valueDrag) {
16386
16435
  return null;
@@ -16410,7 +16459,7 @@ function beginChartValueDrag$1(params) {
16410
16459
  * itself inside the slide's zoom transform.
16411
16460
  */
16412
16461
  function advanceChartValueDrag(state, clientY, svgHeight) {
16413
- if (!state.moved && Math.abs(clientY - state.startClientY) < CHART_DRAG_THRESHOLD_PX$1) {
16462
+ if (!state.moved && Math.abs(clientY - state.startClientY) < CHART_DRAG_THRESHOLD_PX) {
16414
16463
  return null;
16415
16464
  }
16416
16465
  if (svgHeight === 0 || state.part.pointIndex === undefined) {
@@ -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: {
@@ -60339,6 +60660,9 @@ function applyUniformCellPaddingPatch(el, padding) {
60339
60660
  };
60340
60661
  }
60341
60662
 
60663
+ /* oxlint-disable eslint/one-var -- pervasive pre-existing pattern in this file
60664
+ (each mutator is a short sequence of independent `const`s); merging them
60665
+ isn't a style choice here. */
60342
60666
  /**
60343
60667
  * Pure, framework-agnostic comment-array transforms, shared by every binding.
60344
60668
  *
@@ -60362,7 +60686,7 @@ function generateCommentId() {
60362
60686
  * Append a new comment to a comment list.
60363
60687
  * @returns the NEW full comment array, or `null` when `text` is blank.
60364
60688
  */
60365
- function addCommentToList(comments, text, authorName, x, y) {
60689
+ function addCommentToList(comments, text, authorName, x, y, elementId) {
60366
60690
  const trimmed = text.trim();
60367
60691
  if (trimmed.length === 0) {
60368
60692
  return null;
@@ -60375,19 +60699,83 @@ function addCommentToList(comments, text, authorName, x, y) {
60375
60699
  resolved: false,
60376
60700
  ...(typeof x === 'number' ? { x } : {}),
60377
60701
  ...(typeof y === 'number' ? { y } : {}),
60702
+ ...(elementId ? { elementId } : {}),
60378
60703
  };
60379
60704
  return [...comments, comment];
60380
60705
  }
60381
60706
  /**
60382
- * Remove a comment (by id) from a comment list.
60707
+ * Immutably map the comment matching `id` anywhere in the tree (top-level
60708
+ * rows and nested `replies`, at any depth), applying `fn` to it.
60709
+ * @returns a tuple of the new tree and whether a match was found/changed.
60710
+ */
60711
+ function mapCommentTree(comments, id, fn) {
60712
+ let changed = false;
60713
+ const next = comments.map((comment) => {
60714
+ if (comment.id === id) {
60715
+ changed = true;
60716
+ return fn(comment);
60717
+ }
60718
+ if (comment.replies && comment.replies.length > 0) {
60719
+ const [replies, repliesChanged] = mapCommentTree(comment.replies, id, fn);
60720
+ if (repliesChanged) {
60721
+ changed = true;
60722
+ return { ...comment, replies };
60723
+ }
60724
+ }
60725
+ return comment;
60726
+ });
60727
+ return [next, changed];
60728
+ }
60729
+ /**
60730
+ * Immutably drop the comment matching `id` anywhere in the tree (top-level
60731
+ * rows and nested `replies`, at any depth).
60732
+ * @returns a tuple of the new tree and whether anything was removed.
60733
+ */
60734
+ function filterCommentTree(comments, id) {
60735
+ let changed = false;
60736
+ const next = [];
60737
+ for (const comment of comments) {
60738
+ if (comment.id === id) {
60739
+ changed = true;
60740
+ continue;
60741
+ }
60742
+ if (comment.replies && comment.replies.length > 0) {
60743
+ const [replies, repliesChanged] = filterCommentTree(comment.replies, id);
60744
+ if (repliesChanged) {
60745
+ changed = true;
60746
+ next.push({ ...comment, replies });
60747
+ continue;
60748
+ }
60749
+ }
60750
+ next.push(comment);
60751
+ }
60752
+ return [next, changed];
60753
+ }
60754
+ /**
60755
+ * Remove a comment (by id) from a comment list. Searches the whole tree, so
60756
+ * a nested reply (at any depth) is removed just like a top-level comment.
60383
60757
  * @returns the NEW full comment array, or `null` when nothing changed.
60384
60758
  */
60385
60759
  function removeCommentFromList(comments, id) {
60386
- const next = comments.filter((comment) => comment.id !== id);
60387
- if (next.length === comments.length) {
60760
+ const [next, changed] = filterCommentTree(comments, id);
60761
+ return changed ? next : null;
60762
+ }
60763
+ /**
60764
+ * Update the text of a comment (by id) anywhere in the tree (top-level rows
60765
+ * and nested `replies`, at any depth).
60766
+ * @returns the NEW full comment array, or `null` when `text` is blank or the
60767
+ * comment is not found.
60768
+ */
60769
+ function editCommentInList(comments, id, text) {
60770
+ const trimmed = text.trim();
60771
+ if (trimmed.length === 0) {
60388
60772
  return null;
60389
60773
  }
60390
- return next;
60774
+ const [next, changed] = mapCommentTree(comments, id, (comment) => ({
60775
+ ...comment,
60776
+ text: trimmed,
60777
+ }));
60778
+ return changed ? next : null;
60391
60779
  }
60392
60780
  /**
60393
60781
  * Append a threaded reply under the top-level comment `parentId`.
@@ -60421,22 +60809,17 @@ function replyToCommentInList(comments, parentId, text, authorName) {
60421
60809
  : comment);
60422
60810
  }
60423
60811
  /**
60424
- * Toggle the `resolved` flag of a comment (by id) in a comment list.
60812
+ * Toggle the `resolved` flag of a comment (by id) in a comment list. Searches
60813
+ * the whole tree, so a nested reply (at any depth) can be resolved just like
60814
+ * a top-level comment.
60425
60815
  * @returns the NEW full comment array, or `null` when nothing changed.
60426
60816
  */
60427
60817
  function toggleCommentResolvedInList(comments, id) {
60428
- let changed = false;
60429
- const next = comments.map((comment) => {
60430
- if (comment.id !== id) {
60431
- return comment;
60432
- }
60433
- changed = true;
60434
- return { ...comment, resolved: !comment.resolved };
60435
- });
60436
- if (!changed) {
60437
- return null;
60438
- }
60439
- return next;
60818
+ const [next, changed] = mapCommentTree(comments, id, (comment) => ({
60819
+ ...comment,
60820
+ resolved: !comment.resolved,
60821
+ }));
60822
+ return changed ? next : null;
60440
60823
  }
60441
60824
 
60442
60825
  /**
@@ -65679,6 +66062,20 @@ function select(group, key, labelKey, choiceList) {
65679
66062
  function numberControl(group, key, labelKey, min, max, unitKey) {
65680
66063
  return { kind: 'number', group, key, labelKey, min, max, unitKey };
65681
66064
  }
66065
+ /**
66066
+ * Clamp a File > Options number-control edit into its schema range.
66067
+ *
66068
+ * Returns `undefined` when `raw` does not parse to a finite number, so the
66069
+ * caller can skip the commit and leave the field's prior value in place
66070
+ * (matches the behaviour every binding except one already had).
66071
+ */
66072
+ function clampOptionNumber(raw, min, max) {
66073
+ const parsed = Number(raw);
66074
+ if (!Number.isFinite(parsed)) {
66075
+ return undefined;
66076
+ }
66077
+ return Math.min(max, Math.max(min, parsed));
66078
+ }
65682
66079
  function textControl(group, key, labelKey) {
65683
66080
  return { kind: 'text', group, key, labelKey, maxLength: 64 };
65684
66081
  }
@@ -66163,16 +66560,24 @@ function overlayStored(base, stored) {
66163
66560
  * download, short enough not to leak the object URL.
66164
66561
  */
66165
66562
  /** Default fallback name used when a download filename is empty or unusable. */
66166
- const FALLBACK_DOWNLOAD_NAME = 'presentation.pptx';
66563
+ const FALLBACK_DOWNLOAD_NAME = 'presentation.pptx',
66564
+ /** Fallback stem used by {@link resolveExportBaseName} when no usable name remains. */
66565
+ EXPORT_BASE_NAME_FALLBACK = 'presentation',
66566
+ /**
66567
+ * Extensions {@link resolveExportBaseName} strips by default: every format the
66568
+ * export surface (PNG/PDF/GIF/WebM) or the original source deck (pptx) can be
66569
+ * named after. Bare, lower-case, no leading dot.
66570
+ */
66571
+ DEFAULT_EXPORT_STRIP_EXTENSIONS = ['pptx', 'pdf', 'png', 'gif', 'webm'],
66167
66572
  /** Revoke delay (ms) after triggering a download click. */
66168
- const REVOKE_DELAY_MS = 200;
66573
+ REVOKE_DELAY_MS = 200,
66169
66574
  /**
66170
66575
  * Revoke delay (ms) after opening a payload in a new tab. Longer than the
66171
66576
  * download delay: the new document may still be fetching the object URL (a PDF
66172
66577
  * viewer, an image) well after the current task finishes, so we keep the URL
66173
66578
  * alive for a minute before releasing it.
66174
66579
  */
66175
- const OPEN_REVOKE_DELAY_MS = 60_000;
66580
+ OPEN_REVOKE_DELAY_MS = 60_000;
66176
66581
  /**
66177
66582
  * Strip control characters, filesystem-reserved characters, and path-traversal
66178
66583
  * sequences from a user-supplied download filename. CR/LF in particular can
@@ -66213,6 +66618,28 @@ function sanitizeDownloadFilename(input) {
66213
66618
  }
66214
66619
  return cleaned;
66215
66620
  }
66621
+ /**
66622
+ * Derive the base (extensionless) file name for an export download from a
66623
+ * user-supplied source file name: trims whitespace, strips one trailing
66624
+ * extension from `extensions` (case-insensitive), and falls back to
66625
+ * `presentation` when `sourceName` is `undefined` or the result is empty.
66626
+ *
66627
+ * Used by every binding's export controller to turn a loaded deck's source
66628
+ * name (e.g. `Deck.pptx`) into the stem for its own export downloads (e.g.
66629
+ * `Deck-slide-1.png`, `Deck.gif`) without doubling up the extension.
66630
+ *
66631
+ * @param sourceName - The raw source file name, or `undefined` when none is
66632
+ * known yet.
66633
+ * @param extensions - Extensions to strip, without the leading dot. Defaults
66634
+ * to the full export-surface set (`pptx`, `pdf`, `png`, `gif`, `webm`).
66635
+ */
66636
+ function resolveExportBaseName(sourceName, extensions = DEFAULT_EXPORT_STRIP_EXTENSIONS) {
66637
+ if (sourceName === undefined) {
66638
+ return EXPORT_BASE_NAME_FALLBACK;
66639
+ }
66640
+ const pattern = new RegExp(`\\.(?:${extensions.join('|')})$`, 'iu'), trimmed = sourceName.trim().replace(pattern, '');
66641
+ return trimmed === '' ? EXPORT_BASE_NAME_FALLBACK : trimmed;
66642
+ }
66216
66643
  /**
66217
66644
  * Trigger a browser download for a Blob. The `filename` is used verbatim: pass a
66218
66645
  * name through {@link sanitizeDownloadFilename} first if it may be hostile.
@@ -66221,8 +66648,7 @@ function sanitizeDownloadFilename(input) {
66221
66648
  * @param filename - The suggested download name.
66222
66649
  */
66223
66650
  function downloadBlob(blob, filename) {
66224
- const url = URL.createObjectURL(blob);
66225
- const anchor = document.createElement('a');
66651
+ const url = URL.createObjectURL(blob), anchor = document.createElement('a');
66226
66652
  anchor.href = url;
66227
66653
  anchor.download = filename;
66228
66654
  document.body.appendChild(anchor);
@@ -66262,13 +66688,11 @@ function dataUrlToBlob(dataUrl) {
66262
66688
  if (!match?.groups) {
66263
66689
  return undefined;
66264
66690
  }
66265
- const mime = match.groups.mime || 'application/octet-stream';
66266
- const isBase64 = Boolean(match.groups.base64);
66267
- const payload = match.groups.payload ?? '';
66691
+ // eslint-disable-next-line one-var -- separated from `match` above by a guard clause
66692
+ const mime = match.groups.mime || 'application/octet-stream', isBase64 = Boolean(match.groups.base64), payload = match.groups.payload ?? '';
66268
66693
  try {
66269
66694
  if (isBase64) {
66270
- const binary = atob(payload);
66271
- const bytes = new Uint8Array(binary.length);
66695
+ const binary = atob(payload), bytes = new Uint8Array(binary.length);
66272
66696
  for (let i = 0; i < binary.length; i++) {
66273
66697
  bytes[i] = binary.charCodeAt(i);
66274
66698
  }
@@ -66291,14 +66715,13 @@ function dataUrlToBlob(dataUrl) {
66291
66715
  * @param url - The payload URL (typically a recovered `data:` URL).
66292
66716
  */
66293
66717
  function openUrlInNewTab(url) {
66294
- const blob = url.startsWith('data:') ? dataUrlToBlob(url) : undefined;
66295
- const target = blob ? URL.createObjectURL(blob) : url;
66718
+ const blob = url.startsWith('data:') ? dataUrlToBlob(url) : undefined, target = blob ? URL.createObjectURL(blob) : url,
66296
66719
  // NB: no `noopener` here. A `blob:` object URL is resolved from the opener's
66297
66720
  // origin-partitioned blob store, and Chromium refuses to resolve it in the
66298
66721
  // disconnected browsing context `noopener` creates (the new tab lands on an
66299
66722
  // empty document). We instead sever the child's back-reference to us
66300
66723
  // afterwards, which mitigates reverse-tabnabbing without breaking the blob.
66301
- const opened = window.open(target, '_blank');
66724
+ opened = window.open(target, '_blank');
66302
66725
  if (opened) {
66303
66726
  try {
66304
66727
  opened.opener = null;
@@ -70857,7 +71280,7 @@ function createLocalStorageBackend(namespace) {
70857
71280
  /** Try IndexedDB first; fall back to localStorage on any failure. */
70858
71281
  async function resolveBackend(dbName, namespace) {
70859
71282
  try {
70860
- const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-IYWfGg1_.mjs');
71283
+ const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-DG82Zq8L.mjs');
70861
71284
  const db = await openChatDb(dbName);
70862
71285
  return createIdbBackend(db);
70863
71286
  }
@@ -80413,53 +80836,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
80413
80836
  type: Injectable
80414
80837
  }], ctorParameters: () => [] });
80415
80838
 
80416
- /** Minimum pointer travel (px) before a mark press becomes a value drag. */
80417
- const CHART_DRAG_THRESHOLD_PX = 3;
80418
- /** Class toggled onto the SVG marks matching the selected chart part. */
80419
- const CHART_PART_SELECTED_CLASS = 'pptx-chart-part-selected';
80420
- /**
80421
- * Begin a value drag for a pressed part, or return null when the part is not
80422
- * a draggable data point (series lines, non-cartesian charts, missing drag
80423
- * context).
80424
- */
80425
- function beginChartValueDrag(part, vm, chartData, startClientY) {
80426
- if (part.role !== 'dataPoint' || part.pointIndex === undefined || !vm.valueDrag) {
80427
- return null;
80428
- }
80429
- const startValue = chartData.series[part.seriesIndex]?.values[part.pointIndex] ?? 0;
80430
- return {
80431
- part,
80432
- drag: vm.valueDrag,
80433
- svgHeight: vm.svgHeight,
80434
- startClientY,
80435
- anchorViewY: dragAnchorViewY(startValue, vm.valueDrag, part.seriesIndex),
80436
- baseChartData: chartData,
80437
- moved: false,
80438
- lastData: null,
80439
- lastValue: null,
80440
- };
80441
- }
80442
- /**
80443
- * Advance a drag session for a pointer move. Returns the preview chart data +
80444
- * live value once the pointer has travelled past the threshold, or null while
80445
- * the press still counts as a click (or geometry is unusable).
80446
- */
80447
- function moveChartValueDrag(session, clientY, renderedSvgHeight) {
80448
- if (!session.moved && Math.abs(clientY - session.startClientY) < CHART_DRAG_THRESHOLD_PX) {
80449
- return null;
80450
- }
80451
- if (session.part.pointIndex === undefined || renderedSvgHeight === 0) {
80452
- return null;
80453
- }
80454
- session.moved = true;
80455
- const deltaViewY = ((clientY - session.startClientY) / renderedSvgHeight) * session.svgHeight;
80456
- const viewY = session.anchorViewY + deltaViewY;
80457
- const value = dragValueForPart(viewY, session.drag, session.part.seriesIndex);
80458
- const data = withChartPointValue(session.baseChartData, session.part.seriesIndex, session.part.pointIndex, value);
80459
- session.lastData = data;
80460
- session.lastValue = value;
80461
- return { data, value };
80462
- }
80839
+ // ─────────────────────────────────────────────────────────────────────────────
80840
+ // Value-drag commit gate
80841
+ // ─────────────────────────────────────────────────────────────────────────────
80463
80842
  /**
80464
80843
  * The chart data a finished drag should commit, or null when nothing should
80465
80844
  * be committed (cancelled, or the press never became a drag).
@@ -80490,57 +80869,29 @@ function commitChartElementData(editor, elementId, chartData, templateSlideId) {
80490
80869
  editor.updateElement(slideIndex, elementId, { chartData });
80491
80870
  }
80492
80871
  // ─────────────────────────────────────────────────────────────────────────────
80493
- // Selected-part highlight
80494
- // ─────────────────────────────────────────────────────────────────────────────
80495
- /**
80496
- * CSS selector matching the SVG marks tagged with `part`. A part without a
80497
- * `pointIndex` must NOT match point-level marks of the same series (and vice
80498
- * versa), so the point clause is always present in one form or the other.
80499
- */
80500
- function chartPartSelector(part) {
80501
- const pointSel = part.pointIndex !== undefined
80502
- ? `[data-chart-point='${part.pointIndex}']`
80503
- : ':not([data-chart-point])';
80504
- return `[data-chart-part='${part.role}'][data-chart-series='${part.seriesIndex}']${pointSel}`;
80505
- }
80506
- /**
80507
- * Re-apply the selected-part highlight class inside `root`: clears every
80508
- * existing highlight, then tags the marks matching `part` (no-op for null).
80509
- * Runs after each render because re-created SVG marks drop DOM-only classes.
80510
- */
80511
- function applyChartPartHighlight(root, part) {
80512
- for (const node of root.querySelectorAll(`.${CHART_PART_SELECTED_CLASS}`)) {
80513
- node.classList.remove(CHART_PART_SELECTED_CLASS);
80514
- }
80515
- if (!part) {
80516
- return;
80517
- }
80518
- for (const node of root.querySelectorAll(chartPartSelector(part))) {
80519
- node.classList.add(CHART_PART_SELECTED_CLASS);
80520
- }
80521
- }
80522
- // ─────────────────────────────────────────────────────────────────────────────
80523
- // Singleton interaction stylesheet
80872
+ // Interaction stylesheet (shared base rules + Angular's own badge/editor CSS)
80524
80873
  // ─────────────────────────────────────────────────────────────────────────────
80525
80874
  const STYLE_ELEMENT_ID = 'pptx-ng-chart-interaction-styles';
80526
80875
  /**
80527
- * Interaction CSS, injected once into `document.head` (component styles are
80528
- * view-encapsulated in Angular, so they could not reach into the chart
80529
- * renderer's SVG). The `[data-chart-part]` rules match React's stylesheet;
80530
- * the `pptx-ng-*` rules style this binding's badge / inline title editor.
80876
+ * Angular-only interaction CSS, injected once into `document.head` alongside
80877
+ * the shared `[data-chart-part]` / selected-mark rules from
80878
+ * `ensureChartInteractionStyles` (`pptx-viewer-shared`). Component styles are
80879
+ * view-encapsulated in Angular, so they cannot reach into the chart
80880
+ * renderer's SVG or style the badge/title-input this component projects
80881
+ * next to it.
80531
80882
  */
80532
80883
  const INTERACTION_CSS = `
80533
- .pptx-chart-interactive svg [data-chart-part] { pointer-events: auto; cursor: pointer; }
80534
- .pptx-chart-interactive svg [data-chart-part]:hover { filter: brightness(1.12); }
80535
- .pptx-chart-interactive svg [data-chart-part='title'] { cursor: text; }
80536
- .pptx-chart-interactive svg .${CHART_PART_SELECTED_CLASS} { filter: drop-shadow(0 0 2.5px #3b82f6); }
80537
- .pptx-chart-interactive svg .${CHART_PART_SELECTED_CLASS}:hover { filter: drop-shadow(0 0 2.5px #3b82f6) brightness(1.12); }
80538
80884
  .pptx-ng-chart-view { position: relative; width: 100%; height: 100%; }
80539
80885
  .pptx-ng-chart-drag-badge { position: absolute; top: 4px; right: 4px; z-index: 10; border-radius: 4px; background: rgba(37, 99, 235, 0.9); padding: 2px 6px; font-size: 10px; font-weight: 500; color: #fff; pointer-events: none; }
80540
80886
  .pptx-ng-chart-title-input { position: absolute; left: 50%; top: 2px; z-index: 10; width: 60%; transform: translateX(-50%); border: 1px solid #94a3b8; border-radius: 4px; background: #fff; padding: 2px 4px; text-align: center; font-size: 11px; color: #0f172a; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); }
80541
80887
  `;
80542
- /** Inject the (singleton) interaction stylesheet for chart part hit targets. */
80888
+ /**
80889
+ * Inject the interaction stylesheets for chart part hit targets: the shared
80890
+ * base rules (singleton, shared across all five bindings) plus Angular's own
80891
+ * badge/title-input CSS (singleton, this binding only).
80892
+ */
80543
80893
  function ensureChartInteractionStyles() {
80894
+ ensureChartInteractionStyles$1();
80544
80895
  if (typeof document === 'undefined' || document.getElementById(STYLE_ELEMENT_ID)) {
80545
80896
  return;
80546
80897
  }
@@ -80675,7 +81026,11 @@ class ChartPrimitivesComponent {
80675
81026
  [attr.data-chart-part]="partRole(prim)"
80676
81027
  [attr.data-chart-series]="partSeries(prim)"
80677
81028
  [attr.data-chart-point]="partPoint(prim)"
80678
- />
81029
+ >
81030
+ @if (asRect(prim).title !== undefined) {
81031
+ <svg:title>{{ asRect(prim).title }}</svg:title>
81032
+ }
81033
+ </svg:rect>
80679
81034
  }
80680
81035
  @case ('path') {
80681
81036
  <svg:path
@@ -80709,7 +81064,11 @@ class ChartPrimitivesComponent {
80709
81064
  [attr.data-chart-part]="partRole(prim)"
80710
81065
  [attr.data-chart-series]="partSeries(prim)"
80711
81066
  [attr.data-chart-point]="partPoint(prim)"
80712
- />
81067
+ >
81068
+ @if (asPolyline(prim).title !== undefined) {
81069
+ <svg:title>{{ asPolyline(prim).title }}</svg:title>
81070
+ }
81071
+ </svg:polyline>
80713
81072
  }
80714
81073
  @case ('circle') {
80715
81074
  <svg:circle
@@ -80721,7 +81080,11 @@ class ChartPrimitivesComponent {
80721
81080
  [attr.data-chart-part]="partRole(prim)"
80722
81081
  [attr.data-chart-series]="partSeries(prim)"
80723
81082
  [attr.data-chart-point]="partPoint(prim)"
80724
- />
81083
+ >
81084
+ @if (asCircle(prim).title !== undefined) {
81085
+ <svg:title>{{ asCircle(prim).title }}</svg:title>
81086
+ }
81087
+ </svg:circle>
80725
81088
  }
80726
81089
  @case ('line') {
80727
81090
  <svg:line
@@ -80731,7 +81094,11 @@ class ChartPrimitivesComponent {
80731
81094
  [attr.y2]="asLine(prim).y2"
80732
81095
  [attr.stroke]="asLine(prim).stroke"
80733
81096
  [attr.stroke-width]="asLine(prim).strokeWidth"
80734
- />
81097
+ >
81098
+ @if (asLine(prim).title !== undefined) {
81099
+ <svg:title>{{ asLine(prim).title }}</svg:title>
81100
+ }
81101
+ </svg:line>
80735
81102
  }
80736
81103
  @case ('polygon') {
80737
81104
  <svg:polygon
@@ -80744,7 +81111,11 @@ class ChartPrimitivesComponent {
80744
81111
  [attr.data-chart-part]="partRole(prim)"
80745
81112
  [attr.data-chart-series]="partSeries(prim)"
80746
81113
  [attr.data-chart-point]="partPoint(prim)"
80747
- />
81114
+ >
81115
+ @if (asPolygon(prim).title !== undefined) {
81116
+ <svg:title>{{ asPolygon(prim).title }}</svg:title>
81117
+ }
81118
+ </svg:polygon>
80748
81119
  }
80749
81120
  @case ('text') {
80750
81121
  <svg:text
@@ -80786,7 +81157,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
80786
81157
  [attr.data-chart-part]="partRole(prim)"
80787
81158
  [attr.data-chart-series]="partSeries(prim)"
80788
81159
  [attr.data-chart-point]="partPoint(prim)"
80789
- />
81160
+ >
81161
+ @if (asRect(prim).title !== undefined) {
81162
+ <svg:title>{{ asRect(prim).title }}</svg:title>
81163
+ }
81164
+ </svg:rect>
80790
81165
  }
80791
81166
  @case ('path') {
80792
81167
  <svg:path
@@ -80820,7 +81195,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
80820
81195
  [attr.data-chart-part]="partRole(prim)"
80821
81196
  [attr.data-chart-series]="partSeries(prim)"
80822
81197
  [attr.data-chart-point]="partPoint(prim)"
80823
- />
81198
+ >
81199
+ @if (asPolyline(prim).title !== undefined) {
81200
+ <svg:title>{{ asPolyline(prim).title }}</svg:title>
81201
+ }
81202
+ </svg:polyline>
80824
81203
  }
80825
81204
  @case ('circle') {
80826
81205
  <svg:circle
@@ -80832,7 +81211,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
80832
81211
  [attr.data-chart-part]="partRole(prim)"
80833
81212
  [attr.data-chart-series]="partSeries(prim)"
80834
81213
  [attr.data-chart-point]="partPoint(prim)"
80835
- />
81214
+ >
81215
+ @if (asCircle(prim).title !== undefined) {
81216
+ <svg:title>{{ asCircle(prim).title }}</svg:title>
81217
+ }
81218
+ </svg:circle>
80836
81219
  }
80837
81220
  @case ('line') {
80838
81221
  <svg:line
@@ -80842,7 +81225,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
80842
81225
  [attr.y2]="asLine(prim).y2"
80843
81226
  [attr.stroke]="asLine(prim).stroke"
80844
81227
  [attr.stroke-width]="asLine(prim).strokeWidth"
80845
- />
81228
+ >
81229
+ @if (asLine(prim).title !== undefined) {
81230
+ <svg:title>{{ asLine(prim).title }}</svg:title>
81231
+ }
81232
+ </svg:line>
80846
81233
  }
80847
81234
  @case ('polygon') {
80848
81235
  <svg:polygon
@@ -80855,7 +81242,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
80855
81242
  [attr.data-chart-part]="partRole(prim)"
80856
81243
  [attr.data-chart-series]="partSeries(prim)"
80857
81244
  [attr.data-chart-point]="partPoint(prim)"
80858
- />
81245
+ >
81246
+ @if (asPolygon(prim).title !== undefined) {
81247
+ <svg:title>{{ asPolygon(prim).title }}</svg:title>
81248
+ }
81249
+ </svg:polygon>
80859
81250
  }
80860
81251
  @case ('text') {
80861
81252
  <svg:text
@@ -80904,7 +81295,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
80904
81295
  * Input: `element` - required `PptxElement` narrowed to `type === 'chart'`
80905
81296
  */
80906
81297
  const LEGEND_SWATCH_SIZE = 10;
80907
- const LEGEND_ITEM_WIDTH = 80;
80908
81298
  class ChartRendererComponent {
80909
81299
  constructor() {
80910
81300
  this.element = input.required(/* @ts-ignore */
@@ -80914,15 +81304,11 @@ class ChartRendererComponent {
80914
81304
  this.viewBox = computed(() => `0 0 ${this.vm().svgWidth} ${this.vm().svgHeight}`, /* @ts-ignore */
80915
81305
  ...(ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []));
80916
81306
  this.swatchSize = LEGEND_SWATCH_SIZE;
81307
+ this.legendItems = computed(() => computeChartLegendLayout(this.vm()), /* @ts-ignore */
81308
+ ...(ngDevMode ? [{ debugName: "legendItems" }] : /* istanbul ignore next */ []));
80917
81309
  }
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)})`;
81310
+ legendTransform(item) {
81311
+ return `translate(${item.x.toFixed(1)},${item.y.toFixed(1)})`;
80926
81312
  }
80927
81313
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ChartRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
80928
81314
  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 +81447,19 @@ class ChartRendererComponent {
81061
81447
  }
81062
81448
 
81063
81449
  <!-- Legend -->
81064
- @if (vm().legend.length > 0) {
81065
- @for (entry of vm().legend; track $index) {
81066
- <g [attr.transform]="legendTransform($index)">
81450
+ @if (legendItems().length > 0) {
81451
+ @for (item of legendItems(); track $index) {
81452
+ <g [attr.transform]="legendTransform(item)">
81067
81453
  <rect
81068
81454
  x="0"
81069
81455
  y="-7"
81070
81456
  [attr.width]="swatchSize"
81071
81457
  [attr.height]="swatchSize"
81072
81458
  rx="2"
81073
- [attr.fill]="entry.color"
81459
+ [attr.fill]="item.color"
81074
81460
  />
81075
81461
  <text [attr.x]="swatchSize + 3" y="3" font-size="9" fill="#475569">
81076
- {{ entry.label }}
81462
+ {{ item.label }}
81077
81463
  </text>
81078
81464
  </g>
81079
81465
  }
@@ -81224,19 +81610,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
81224
81610
  }
81225
81611
 
81226
81612
  <!-- Legend -->
81227
- @if (vm().legend.length > 0) {
81228
- @for (entry of vm().legend; track $index) {
81229
- <g [attr.transform]="legendTransform($index)">
81613
+ @if (legendItems().length > 0) {
81614
+ @for (item of legendItems(); track $index) {
81615
+ <g [attr.transform]="legendTransform(item)">
81230
81616
  <rect
81231
81617
  x="0"
81232
81618
  y="-7"
81233
81619
  [attr.width]="swatchSize"
81234
81620
  [attr.height]="swatchSize"
81235
81621
  rx="2"
81236
- [attr.fill]="entry.color"
81622
+ [attr.fill]="item.color"
81237
81623
  />
81238
81624
  <text [attr.x]="swatchSize + 3" y="3" font-size="9" fill="#475569">
81239
- {{ entry.label }}
81625
+ {{ item.label }}
81240
81626
  </text>
81241
81627
  </g>
81242
81628
  }
@@ -81246,6 +81632,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
81246
81632
  }]
81247
81633
  }], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }] } });
81248
81634
 
81635
+ /* oxlint-disable eslint/one-var -- pervasive pre-existing pattern in this file:
81636
+ independent handler-local `const`s, not one statement */
81249
81637
  /**
81250
81638
  * ChartElementViewComponent (Angular port of React's `ChartElementView.tsx`):
81251
81639
  * renders a chart and, while it is selected + editable, makes its data marks
@@ -81409,7 +81797,12 @@ class ChartElementViewComponent {
81409
81797
  if (!chartData || !vm) {
81410
81798
  return;
81411
81799
  }
81412
- const session = beginChartValueDrag(part, vm, chartData, event.clientY);
81800
+ const session = beginChartValueDrag({
81801
+ part,
81802
+ viewModel: vm,
81803
+ chartData,
81804
+ clientY: event.clientY,
81805
+ });
81413
81806
  if (!session) {
81414
81807
  return;
81415
81808
  }
@@ -81430,14 +81823,11 @@ class ChartElementViewComponent {
81430
81823
  return;
81431
81824
  }
81432
81825
  const svg = this.wrapper()?.nativeElement.querySelector('svg');
81433
- const rect = svg?.getBoundingClientRect();
81434
- if (!rect || rect.height === 0) {
81435
- return;
81436
- }
81437
- const result = moveChartValueDrag(session, event.clientY, rect.height);
81438
- if (result) {
81439
- this.previewData.set(result.data);
81440
- this.dragValue.set(result.value);
81826
+ const height = svg?.getBoundingClientRect().height ?? 0;
81827
+ const step = advanceChartValueDrag(session, event.clientY, height);
81828
+ if (step) {
81829
+ this.previewData.set(step.chartData);
81830
+ this.dragValue.set(step.value);
81441
81831
  }
81442
81832
  }
81443
81833
  onPointerUp() {
@@ -87075,6 +87465,9 @@ function affordanceElements(elements, editTemplateMode, isTemplate) {
87075
87465
  * rendering, clearing on pointerup) stays in `SlideCanvasComponent`.
87076
87466
  */
87077
87467
 
87468
+ /* oxlint-disable eslint/one-var -- pervasive pre-existing pattern in this file
87469
+ (many independent short-lived `const`s per handler); merging them isn't a
87470
+ style choice here. */
87078
87471
  /** Pixels (screen-space) a pointer must move before a click becomes a drag. */
87079
87472
  const DRAG_THRESHOLD = 3;
87080
87473
  /** Handle size in screen pixels (fine pointer: mouse/trackpad). */
@@ -87965,7 +88358,12 @@ class SlideCanvasComponent {
87965
88358
  return;
87966
88359
  }
87967
88360
  event.preventDefault();
87968
- const id = this.interactiveElementIdAt(event.target);
88361
+ // The inline text editor renders as an overlay beside the elements, not a
88362
+ // child of the one it edits, so a right-click inside it hit-tests to
88363
+ // nothing via interactiveElementIdAt. Fall back to the element being
88364
+ // edited rather than swallowing the menu on the element the user just
88365
+ // clicked (matches Vue's and Svelte's useContextMenu/onStageContextMenu).
88366
+ const hitId = this.interactiveElementIdAt(event.target), id = resolveContextMenuElementId(hitId, event.target, this.editingId());
87969
88367
  this.contextMenu.emit({ id, x: event.clientX, y: event.clientY });
87970
88368
  }
87971
88369
  onHandlePointerDown(event, handle) {
@@ -88694,6 +89092,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
88694
89092
  `, styles: [".master-sidebar{display:flex;width:224px;min-height:0;flex-direction:column;border-right:1px solid var(--pptx-border, #33334d);background:var(--pptx-card, #1e1e2e)}header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px}header strong{color:var(--pptx-muted-foreground, #a5a5b5);font-size:11px;text-transform:uppercase}header button{border:0;background:transparent;color:inherit;font-size:20px;cursor:pointer}.tabs{display:flex;padding:0 4px;border-bottom:1px solid var(--pptx-border, #33334d)}.tabs button{flex:1;padding:6px 3px;border:0;border-bottom:2px solid transparent;background:transparent;color:var(--pptx-muted-foreground, #a5a5b5);font-size:10px;cursor:pointer}.tabs button[aria-selected=true]{border-bottom-color:#f59e0b;color:#f59e0b}.body{flex:1;min-height:0;overflow:auto;padding:8px}.master-item{display:block;width:100%;margin-bottom:6px;padding:8px;border:1px solid transparent;border-radius:5px;background:transparent;color:inherit;text-align:left}.master-item.layout{width:calc(100% - 14px);margin-left:14px}.master-item[aria-pressed=true]{border-color:var(--pptx-primary, #6366f1)}section,.background-editor{display:flex;flex-direction:column;gap:8px;margin-bottom:12px;padding:10px;border:1px solid var(--pptx-border, #33334d);border-radius:6px}.counts{display:grid;grid-template-columns:repeat(3,1fr);gap:4px}.counts button[aria-pressed=true]{background:var(--pptx-primary, #6366f1);color:#fff}.background-editor input{width:100%;height:34px}\n"] }]
88695
89093
  }], propDecorators: { tab: [{ type: i0.Input, args: [{ isSignal: true, alias: "tab", required: true }] }], slideMasters: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideMasters", required: true }] }], notesMaster: [{ type: i0.Input, args: [{ isSignal: true, alias: "notesMaster", required: false }] }], handoutMaster: [{ type: i0.Input, args: [{ isSignal: true, alias: "handoutMaster", required: false }] }], activeMasterIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeMasterIndex", required: false }] }], activeLayoutIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeLayoutIndex", required: false }] }], handoutSlidesPerPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "handoutSlidesPerPage", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], tabChange: [{ type: i0.Output, args: ["tabChange"] }], selectMaster: [{ type: i0.Output, args: ["selectMaster"] }], selectLayout: [{ type: i0.Output, args: ["selectLayout"] }], slidesPerPageChange: [{ type: i0.Output, args: ["slidesPerPageChange"] }], backgroundChange: [{ type: i0.Output, args: ["backgroundChange"] }], close: [{ type: i0.Output, args: ["close"] }] } });
88696
89094
 
89095
+ /**
89096
+ * Apply a bottom-bar tap: decide the next sheet with shared's `toggleSheet`
89097
+ * (tapping the open sheet closes it, tapping a different one switches to
89098
+ * it), then close everything and open whatever `toggleSheet` decided, if
89099
+ * anything.
89100
+ */
89101
+ function applyMobileBarSheetTap(tapped, current, actions) {
89102
+ const next = toggleSheet(current, tapped);
89103
+ actions.closeAll();
89104
+ switch (next) {
89105
+ case 'slides':
89106
+ actions.openSlides();
89107
+ break;
89108
+ case 'inspector':
89109
+ actions.openInspector();
89110
+ break;
89111
+ case 'comments':
89112
+ actions.openComments();
89113
+ break;
89114
+ case 'notes':
89115
+ actions.openNotes();
89116
+ break;
89117
+ }
89118
+ }
89119
+
88697
89120
  /**
88698
89121
  * mobile-bottom-bar.component.ts: Persistent mobile bottom action bar.
88699
89122
  *
@@ -88739,36 +89162,40 @@ class MobileBottomBarComponent {
88739
89162
  /** User tapped the Notes button. */
88740
89163
  this.notes = output();
88741
89164
  // ── Derived action list ───────────────────────────────────────────────────
89165
+ // Shared `buildBarActions` decides which slots are disabled (no slides
89166
+ // loaded); this binding only maps the resulting descriptor's `key` and
89167
+ // `disabled` onto its own labels, icons and outputs.
88742
89168
  this.actions = computed(() => {
88743
- const count = this.slideCount();
88744
- const noSlides = count === 0;
88745
- const active = this.activeSheet();
89169
+ const active = this.activeSheet(), disabledByKey = new Map(buildBarActions({ slideCount: this.slideCount() }).map((descriptor) => [
89170
+ descriptor.key,
89171
+ descriptor.disabled,
89172
+ ]));
88746
89173
  return [
88747
89174
  {
88748
89175
  key: 'slides',
88749
89176
  labelKey: 'pptx.sections.slides',
88750
- disabled: noSlides,
89177
+ disabled: disabledByKey.get('slides') ?? false,
88751
89178
  active: active === 'slides',
88752
89179
  emit: () => this.openSlides.emit(),
88753
89180
  },
88754
89181
  {
88755
89182
  key: 'insert',
88756
89183
  labelKey: 'pptx.editorToolbar.insert',
88757
- disabled: noSlides,
89184
+ disabled: disabledByKey.get('insert') ?? false,
88758
89185
  active: false,
88759
89186
  emit: () => this.insert.emit(),
88760
89187
  },
88761
89188
  {
88762
89189
  key: 'inspector',
88763
89190
  labelKey: 'pptx.arrange.format',
88764
- disabled: noSlides,
89191
+ disabled: disabledByKey.get('inspector') ?? false,
88765
89192
  active: active === 'inspector',
88766
89193
  emit: () => this.openFormat.emit(),
88767
89194
  },
88768
89195
  {
88769
89196
  key: 'comments',
88770
89197
  labelKey: 'pptx.toolbar.comments',
88771
- disabled: noSlides,
89198
+ disabled: disabledByKey.get('comments') ?? false,
88772
89199
  active: active === 'comments',
88773
89200
  badge: this.commentCount(),
88774
89201
  emit: () => this.openComments.emit(),
@@ -88777,7 +89204,7 @@ class MobileBottomBarComponent {
88777
89204
  key: 'notes',
88778
89205
  labelKey: 'pptx.notes.title',
88779
89206
  ariaLabelKey: 'pptx.statusBar.toggleNotes',
88780
- disabled: noSlides,
89207
+ disabled: disabledByKey.get('notes') ?? false,
88781
89208
  active: active === 'notes',
88782
89209
  emit: () => this.notes.emit(),
88783
89210
  },
@@ -104025,9 +104452,138 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
104025
104452
  }]
104026
104453
  }], 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
104454
 
104455
+ /**
104456
+ * ribbon-color-popover.component.ts: the hover-reveal swatch popover used for the
104457
+ * Font-colour and Text-highlight pickers in {@link RibbonFontControlsComponent}.
104458
+ * The trigger icon is projected via `<ng-content>`; the swatch grid + custom
104459
+ * colour input are shared. Split out to keep the font controls under the 300-LOC
104460
+ * cap; behaviour and markup are unchanged.
104461
+ */
104462
+ class RibbonColorPopoverComponent {
104463
+ constructor() {
104464
+ this.current = input('#000000', /* @ts-ignore */
104465
+ ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
104466
+ this.presets = input([], /* @ts-ignore */
104467
+ ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
104468
+ this.disabled = input(false, /* @ts-ignore */
104469
+ ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
104470
+ this.titleKey = input('', /* @ts-ignore */
104471
+ ...(ngDevMode ? [{ debugName: "titleKey" }] : /* istanbul ignore next */ []));
104472
+ this.swatchAriaKey = input('', /* @ts-ignore */
104473
+ ...(ngDevMode ? [{ debugName: "swatchAriaKey" }] : /* istanbul ignore next */ []));
104474
+ this.pick = output();
104475
+ }
104476
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: RibbonColorPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
104477
+ 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: `
104478
+ <div class="group relative">
104479
+ <button
104480
+ type="button"
104481
+ class="pptx-rb-pill"
104482
+ [disabled]="disabled()"
104483
+ [title]="titleKey() | translate"
104484
+ (mousedown)="$event.preventDefault()"
104485
+ >
104486
+ <ng-content />
104487
+ <span class="-mt-0.5 block h-1 w-4 rounded-sm" [style.background]="current()"></span>
104488
+ </button>
104489
+ <div class="absolute left-0 top-full z-50 hidden pt-1 group-hover:block">
104490
+ <div class="w-36 rounded-lg border border-border bg-card p-2 shadow-2xl">
104491
+ <div class="mb-2 grid grid-cols-5 gap-1.5">
104492
+ @for (c of presets(); track c) {
104493
+ <button
104494
+ type="button"
104495
+ data-pptx-compact
104496
+ class="h-5 w-5 rounded-full border transition-transform hover:scale-125"
104497
+ [ngClass]="
104498
+ current().toLowerCase() === c
104499
+ ? 'border-primary ring-1 ring-primary'
104500
+ : 'border-border'
104501
+ "
104502
+ [style.background]="c"
104503
+ [attr.aria-label]="swatchAriaKey() | translate: { color: c }"
104504
+ (mousedown)="$event.preventDefault()"
104505
+ (click)="pick.emit(c)"
104506
+ ></button>
104507
+ }
104508
+ </div>
104509
+ <label
104510
+ class="block w-full cursor-pointer py-1 text-center text-[10px] text-muted-foreground transition-colors hover:text-foreground"
104511
+ >
104512
+ {{ 'pptx.ribbon.customColour' | translate }}
104513
+ <input
104514
+ type="color"
104515
+ class="sr-only"
104516
+ [value]="current()"
104517
+ (change)="pick.emit($any($event.target).value)"
104518
+ />
104519
+ </label>
104520
+ </div>
104521
+ </div>
104522
+ </div>
104523
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
104524
+ }
104525
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: RibbonColorPopoverComponent, decorators: [{
104526
+ type: Component,
104527
+ args: [{
104528
+ selector: 'pptx-ribbon-color-popover',
104529
+ standalone: true,
104530
+ changeDetection: ChangeDetectionStrategy.OnPush,
104531
+ imports: [NgClass, TranslatePipe],
104532
+ template: `
104533
+ <div class="group relative">
104534
+ <button
104535
+ type="button"
104536
+ class="pptx-rb-pill"
104537
+ [disabled]="disabled()"
104538
+ [title]="titleKey() | translate"
104539
+ (mousedown)="$event.preventDefault()"
104540
+ >
104541
+ <ng-content />
104542
+ <span class="-mt-0.5 block h-1 w-4 rounded-sm" [style.background]="current()"></span>
104543
+ </button>
104544
+ <div class="absolute left-0 top-full z-50 hidden pt-1 group-hover:block">
104545
+ <div class="w-36 rounded-lg border border-border bg-card p-2 shadow-2xl">
104546
+ <div class="mb-2 grid grid-cols-5 gap-1.5">
104547
+ @for (c of presets(); track c) {
104548
+ <button
104549
+ type="button"
104550
+ data-pptx-compact
104551
+ class="h-5 w-5 rounded-full border transition-transform hover:scale-125"
104552
+ [ngClass]="
104553
+ current().toLowerCase() === c
104554
+ ? 'border-primary ring-1 ring-primary'
104555
+ : 'border-border'
104556
+ "
104557
+ [style.background]="c"
104558
+ [attr.aria-label]="swatchAriaKey() | translate: { color: c }"
104559
+ (mousedown)="$event.preventDefault()"
104560
+ (click)="pick.emit(c)"
104561
+ ></button>
104562
+ }
104563
+ </div>
104564
+ <label
104565
+ class="block w-full cursor-pointer py-1 text-center text-[10px] text-muted-foreground transition-colors hover:text-foreground"
104566
+ >
104567
+ {{ 'pptx.ribbon.customColour' | translate }}
104568
+ <input
104569
+ type="color"
104570
+ class="sr-only"
104571
+ [value]="current()"
104572
+ (change)="pick.emit($any($event.target).value)"
104573
+ />
104574
+ </label>
104575
+ </div>
104576
+ </div>
104577
+ </div>
104578
+ `,
104579
+ }]
104580
+ }], 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"] }] } });
104581
+
104028
104582
  /**
104029
104583
  * ribbon-drawing-group.component.ts: Drawing group for the Home tab ribbon.
104030
- * Provides shape insertion, layer arrangement, and shape formatting placeholders.
104584
+ * Provides shape insertion, layer arrangement, and shape Fill/Outline colour
104585
+ * pickers (Shape Effects stays a disabled placeholder: it is genuinely
104586
+ * unimplemented in every binding).
104031
104587
  *
104032
104588
  * The Arrange menu also carries Group / Ungroup, which is where PowerPoint
104033
104589
  * puts them (Home > Drawing > Arrange) and where the other bindings expose
@@ -104040,9 +104596,50 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
104040
104596
  * The picker lists the first 12 entries of the shared preset catalogue
104041
104597
  * ({@link SHAPE_PRESET_DEFS}), so the geometry ids are valid OOXML
104042
104598
  * `a:prstGeom` values shared with every binding.
104599
+ *
104600
+ * Fill/Outline commit through `pptx-viewer-shared`'s `shapeFillChange` /
104601
+ * `shapeOutlineChange` decision functions (the same ones React's and Vue's
104602
+ * DrawingGroup use), so the two keys written (`fillColor`+`fillMode` /
104603
+ * `strokeColor`) can't drift from the other bindings a third time. The
104604
+ * buttons used to be `disabled` placeholders that did nothing when clicked.
104605
+ */
104606
+ /**
104607
+ * The quick "top shapes" row shared with React's toolbar (first 12 presets),
104608
+ * and the fallbacks the Fill/Outline swatch dots show when the selection has
104609
+ * none set (declared together: oxlint's `one-var` wants one `const` statement
104610
+ * per top-level scope).
104611
+ */
104612
+ const TOP_SHAPES = SHAPE_PRESET_DEFS.slice(0, 12), DEFAULT_FILL_COLOR = '#ffffff', DEFAULT_OUTLINE_COLOR = '#000000';
104613
+ /** Fill/Outline only apply to an editable, selected shape-like element. */
104614
+ function canFormatShapeSelection(canEdit, element) {
104615
+ return canEdit && element !== null && hasShapeProperties(element);
104616
+ }
104617
+ /** The colour the Fill swatch dot shows for the current selection. */
104618
+ function fillColorOf(element) {
104619
+ if (element === null || !hasShapeProperties(element)) {
104620
+ return DEFAULT_FILL_COLOR;
104621
+ }
104622
+ return element.shapeStyle?.fillColor ?? DEFAULT_FILL_COLOR;
104623
+ }
104624
+ /** The colour the Outline swatch dot shows for the current selection. */
104625
+ function outlineColorOf(element) {
104626
+ if (element === null || !hasShapeProperties(element)) {
104627
+ return DEFAULT_OUTLINE_COLOR;
104628
+ }
104629
+ return element.shapeStyle?.strokeColor ?? DEFAULT_OUTLINE_COLOR;
104630
+ }
104631
+ /**
104632
+ * The element patch a Fill/Outline swatch pick commits, or `undefined` when
104633
+ * the selection has no shape style to patch. Merges into the EXISTING
104634
+ * `shapeStyle` (not a replace) so picking a fill colour never clears an
104635
+ * already-set outline, and vice versa.
104043
104636
  */
104044
- /** The quick "top shapes" row shared with React's toolbar (first 12 presets). */
104045
- const TOP_SHAPES = SHAPE_PRESET_DEFS.slice(0, 12);
104637
+ function shapeStylePatch(element, style) {
104638
+ if (element === null || !hasShapeProperties(element)) {
104639
+ return undefined;
104640
+ }
104641
+ return { shapeStyle: { ...element.shapeStyle, ...style } };
104642
+ }
104046
104643
  class RibbonDrawingGroupComponent {
104047
104644
  constructor() {
104048
104645
  this.editor = inject(EditorStateService);
@@ -104051,11 +104648,36 @@ class RibbonDrawingGroupComponent {
104051
104648
  /** Index of the active slide (insertion target). */
104052
104649
  this.slideIndex = input(0, /* @ts-ignore */
104053
104650
  ...(ngDevMode ? [{ debugName: "slideIndex" }] : /* istanbul ignore next */ []));
104651
+ this.selectedElement = input(null, /* @ts-ignore */
104652
+ ...(ngDevMode ? [{ debugName: "selectedElement" }] : /* istanbul ignore next */ []));
104054
104653
  this.shapes = TOP_SHAPES;
104055
104654
  this.shapesOpen = signal(false, /* @ts-ignore */
104056
104655
  ...(ngDevMode ? [{ debugName: "shapesOpen" }] : /* istanbul ignore next */ []));
104057
104656
  this.arrangeOpen = signal(false, /* @ts-ignore */
104058
104657
  ...(ngDevMode ? [{ debugName: "arrangeOpen" }] : /* istanbul ignore next */ []));
104658
+ this.swatches = RIBBON_SHAPE_SWATCHES;
104659
+ this.canFormatShape = computed(() => canFormatShapeSelection(this.canEdit(), this.selectedElement()), /* @ts-ignore */
104660
+ ...(ngDevMode ? [{ debugName: "canFormatShape" }] : /* istanbul ignore next */ []));
104661
+ this.fillColor = computed(() => fillColorOf(this.selectedElement()), /* @ts-ignore */
104662
+ ...(ngDevMode ? [{ debugName: "fillColor" }] : /* istanbul ignore next */ []));
104663
+ this.outlineColor = computed(() => outlineColorOf(this.selectedElement()), /* @ts-ignore */
104664
+ ...(ngDevMode ? [{ debugName: "outlineColor" }] : /* istanbul ignore next */ []));
104665
+ }
104666
+ /** Commit a picked Fill swatch through the shared decision function. */
104667
+ onFill(color) {
104668
+ this.patchShapeStyle(shapeFillChange(color));
104669
+ }
104670
+ /** Commit a picked Outline swatch through the shared decision function. */
104671
+ onOutline(color) {
104672
+ this.patchShapeStyle(shapeOutlineChange(color));
104673
+ }
104674
+ /** Merge a Fill/Outline patch into the selection's shape style, if it has one. */
104675
+ patchShapeStyle(style) {
104676
+ const el = this.selectedElement(), patch = shapeStylePatch(el, style);
104677
+ if (!this.canFormatShape() || el === null || !patch) {
104678
+ return;
104679
+ }
104680
+ this.editor.updateElement(this.slideIndex(), el.id, patch);
104059
104681
  }
104060
104682
  /** Insert the picked preset immediately (selects it and records history). */
104061
104683
  onShapeSelect(shape) {
@@ -104088,7 +104710,7 @@ class RibbonDrawingGroupComponent {
104088
104710
  this.editor.sendSelectedToBack(this.slideIndex());
104089
104711
  }
104090
104712
  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: `
104713
+ 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
104714
  <!-- Shapes -->
104093
104715
  <div class="flex flex-col items-center gap-0.5">
104094
104716
  <div class="pptx-rb-grp">
@@ -104185,16 +104807,36 @@ class RibbonDrawingGroupComponent {
104185
104807
  </span>
104186
104808
  </div>
104187
104809
  <span class="pptx-rb-sep"></span>
104188
- <!-- Shape formatting (placeholders) -->
104810
+ <!-- Shape formatting -->
104189
104811
  <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>
104812
+ <div class="flex items-center gap-1">
104813
+ <pptx-ribbon-color-popover
104814
+ [current]="fillColor()"
104815
+ [presets]="swatches"
104816
+ [disabled]="!canFormatShape()"
104817
+ titleKey="pptx.drawing.shapeFill"
104818
+ swatchAriaKey="pptx.ribbon.fillColourValue"
104819
+ (pick)="onFill($event)"
104820
+ >
104192
104821
  {{ 'pptx.drawing.shapeFill' | translate }}
104193
- </button>
104194
- <button type="button" class="pptx-rb-gb" disabled>
104822
+ </pptx-ribbon-color-popover>
104823
+ <pptx-ribbon-color-popover
104824
+ [current]="outlineColor()"
104825
+ [presets]="swatches"
104826
+ [disabled]="!canFormatShape()"
104827
+ titleKey="pptx.drawing.shapeOutline"
104828
+ swatchAriaKey="pptx.ribbon.outlineColourValue"
104829
+ (pick)="onOutline($event)"
104830
+ >
104195
104831
  {{ 'pptx.drawing.shapeOutline' | translate }}
104196
- </button>
104197
- <button type="button" class="pptx-rb-gb" disabled>
104832
+ </pptx-ribbon-color-popover>
104833
+ <!-- Shape Effects: genuinely unimplemented (React/Vue parity), left as a placeholder. -->
104834
+ <button
104835
+ type="button"
104836
+ class="pptx-rb-gb"
104837
+ disabled
104838
+ [title]="'pptx.drawing.shapeEffectsUnavailable' | translate"
104839
+ >
104198
104840
  {{ 'pptx.drawing.shapeEffectsUnavailable' | translate }}
104199
104841
  </button>
104200
104842
  </div>
@@ -104202,7 +104844,7 @@ class RibbonDrawingGroupComponent {
104202
104844
  {{ 'pptx.ribbon.groupShapeStyles' | translate }}
104203
104845
  </span>
104204
104846
  </div>
104205
- `, isInline: true, dependencies: [{ kind: "component", type: LucideChevronDown, selector: "svg[lucideChevronDown]" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
104847
+ `, 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
104848
  }
104207
104849
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: RibbonDrawingGroupComponent, decorators: [{
104208
104850
  type: Component,
@@ -104211,7 +104853,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
104211
104853
  standalone: true,
104212
104854
  changeDetection: ChangeDetectionStrategy.OnPush,
104213
104855
  host: { class: 'contents' },
104214
- imports: [TranslatePipe, LucideChevronDown],
104856
+ imports: [TranslatePipe, LucideChevronDown, RibbonColorPopoverComponent],
104215
104857
  template: `
104216
104858
  <!-- Shapes -->
104217
104859
  <div class="flex flex-col items-center gap-0.5">
@@ -104309,16 +104951,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
104309
104951
  </span>
104310
104952
  </div>
104311
104953
  <span class="pptx-rb-sep"></span>
104312
- <!-- Shape formatting (placeholders) -->
104954
+ <!-- Shape formatting -->
104313
104955
  <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>
104956
+ <div class="flex items-center gap-1">
104957
+ <pptx-ribbon-color-popover
104958
+ [current]="fillColor()"
104959
+ [presets]="swatches"
104960
+ [disabled]="!canFormatShape()"
104961
+ titleKey="pptx.drawing.shapeFill"
104962
+ swatchAriaKey="pptx.ribbon.fillColourValue"
104963
+ (pick)="onFill($event)"
104964
+ >
104316
104965
  {{ 'pptx.drawing.shapeFill' | translate }}
104317
- </button>
104318
- <button type="button" class="pptx-rb-gb" disabled>
104966
+ </pptx-ribbon-color-popover>
104967
+ <pptx-ribbon-color-popover
104968
+ [current]="outlineColor()"
104969
+ [presets]="swatches"
104970
+ [disabled]="!canFormatShape()"
104971
+ titleKey="pptx.drawing.shapeOutline"
104972
+ swatchAriaKey="pptx.ribbon.outlineColourValue"
104973
+ (pick)="onOutline($event)"
104974
+ >
104319
104975
  {{ 'pptx.drawing.shapeOutline' | translate }}
104320
- </button>
104321
- <button type="button" class="pptx-rb-gb" disabled>
104976
+ </pptx-ribbon-color-popover>
104977
+ <!-- Shape Effects: genuinely unimplemented (React/Vue parity), left as a placeholder. -->
104978
+ <button
104979
+ type="button"
104980
+ class="pptx-rb-gb"
104981
+ disabled
104982
+ [title]="'pptx.drawing.shapeEffectsUnavailable' | translate"
104983
+ >
104322
104984
  {{ 'pptx.drawing.shapeEffectsUnavailable' | translate }}
104323
104985
  </button>
104324
104986
  </div>
@@ -104328,10 +104990,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
104328
104990
  </div>
104329
104991
  `,
104330
104992
  }]
104331
- }], propDecorators: { canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], slideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideIndex", required: false }] }] } });
104993
+ }], 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
104994
 
104333
104995
  // Generated by scripts/inline-shared.mjs from package.json. Do not edit.
104334
- const PPTX_ANGULAR_VIEWER_VERSION = "2.18.6";
104996
+ const PPTX_ANGULAR_VIEWER_VERSION = "2.19.0";
104335
104997
 
104336
104998
  /**
104337
104999
  * account-page.component.ts: File > Account content.
@@ -104682,133 +105344,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
104682
105344
  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
105345
  }], 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
105346
 
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
105347
  /**
104813
105348
  * ribbon-font-controls.component.ts: the ribbon's reusable Font control group
104814
105349
  * (family/size dropdowns, grow/shrink, clear-formatting, bold/italic/underline/
@@ -106056,6 +106591,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
106056
106591
  * markup are unchanged. Font/Paragraph controls are the shared
106057
106592
  * {@link RibbonFontControlsComponent} / {@link RibbonParagraphControlsComponent}.
106058
106593
  */
106594
+ /**
106595
+ * Home > Reset: re-apply the active slide's own layout, restoring inherited
106596
+ * placeholder geometry and formatting (React/Vue parity via the shared
106597
+ * `resetSlideLayoutPath` decision function). A no-op when the slide records
106598
+ * no layout. Exported as a pure dispatch function (rather than inlined in the
106599
+ * component) so it is directly testable without constructing the component,
106600
+ * whose constructor runs an `effect()` that needs a full Angular
106601
+ * `ChangeDetectionScheduler` this package's TestBed-free unit tests don't
106602
+ * provide (see `action-settings-panel.component.test.ts`).
106603
+ */
106604
+ function performResetSlide(editor, slideIndex) {
106605
+ const path = resetSlideLayoutPath(editor.slides()[slideIndex]);
106606
+ if (path) {
106607
+ void editor.applyLayout(slideIndex, path);
106608
+ }
106609
+ }
106059
106610
  class RibbonHomeSectionComponent {
106060
106611
  constructor() {
106061
106612
  this.editor = inject(EditorStateService);
@@ -106091,6 +106642,7 @@ class RibbonHomeSectionComponent {
106091
106642
  this.openTemplateGallery = output();
106092
106643
  /** Emitted with the layout the user picked, after it has been applied. */
106093
106644
  this.applyLayout = output();
106645
+ /** Emitted after Home > Reset has re-applied the slide's layout. */
106094
106646
  this.resetSlide = output();
106095
106647
  // The menus open on hover with no event to hang a lazy load off, so the
106096
106648
  // fetch is kicked off once a deck is present. It is still deferred out of
@@ -106119,6 +106671,14 @@ class RibbonHomeSectionComponent {
106119
106671
  void this.editor.applyLayout(this.slideIndex(), layoutPath);
106120
106672
  this.applyLayout.emit(layoutPath);
106121
106673
  }
106674
+ /**
106675
+ * The button used to `resetSlide.emit()` to nobody, so clicking it did
106676
+ * nothing; {@link performResetSlide} now actually performs the reset.
106677
+ */
106678
+ onResetSlide() {
106679
+ performResetSlide(this.editor, this.slideIndex());
106680
+ this.resetSlide.emit();
106681
+ }
106122
106682
  copy() {
106123
106683
  this.editor.copySelected(this.slideIndex());
106124
106684
  }
@@ -106273,8 +106833,9 @@ class RibbonHomeSectionComponent {
106273
106833
  <button
106274
106834
  type="button"
106275
106835
  class="pptx-rb-gb whitespace-nowrap"
106836
+ [disabled]="!canEdit()"
106276
106837
  [title]="'pptx.sections.resetSlideTitle' | translate"
106277
- (click)="resetSlide.emit()"
106838
+ (click)="onResetSlide()"
106278
106839
  >
106279
106840
  <svg lucideRotateCcw class="h-4 w-4"></svg> {{ 'pptx.animations.reset' | translate }}
106280
106841
  </button>
@@ -106498,8 +107059,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
106498
107059
  <button
106499
107060
  type="button"
106500
107061
  class="pptx-rb-gb whitespace-nowrap"
107062
+ [disabled]="!canEdit()"
106501
107063
  [title]="'pptx.sections.resetSlideTitle' | translate"
106502
- (click)="resetSlide.emit()"
107064
+ (click)="onResetSlide()"
106503
107065
  >
106504
107066
  <svg lucideRotateCcw class="h-4 w-4"></svg> {{ 'pptx.animations.reset' | translate }}
106505
107067
  </button>
@@ -107696,7 +108258,11 @@ class RibbonContentComponent {
107696
108258
  (openTemplateGallery)="openTemplateGallery.emit()"
107697
108259
  />
107698
108260
  <span class="pptx-rb-sep"></span>
107699
- <pptx-ribbon-drawing-group [canEdit]="canEdit()" [slideIndex]="slideIndex()" />
108261
+ <pptx-ribbon-drawing-group
108262
+ [canEdit]="canEdit()"
108263
+ [slideIndex]="slideIndex()"
108264
+ [selectedElement]="selectedElement()"
108265
+ />
107700
108266
  <span class="pptx-rb-sep"></span>
107701
108267
  <!--
107702
108268
  React parity (Toolbar.tsx: sArr = sHome || toolbarSection === 'arrange'):
@@ -107746,7 +108312,7 @@ class RibbonContentComponent {
107746
108312
  />
107747
108313
  }
107748
108314
  }
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 }); }
108315
+ `, 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
108316
  }
107751
108317
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: RibbonContentComponent, decorators: [{
107752
108318
  type: Component,
@@ -107810,7 +108376,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
107810
108376
  (openTemplateGallery)="openTemplateGallery.emit()"
107811
108377
  />
107812
108378
  <span class="pptx-rb-sep"></span>
107813
- <pptx-ribbon-drawing-group [canEdit]="canEdit()" [slideIndex]="slideIndex()" />
108379
+ <pptx-ribbon-drawing-group
108380
+ [canEdit]="canEdit()"
108381
+ [slideIndex]="slideIndex()"
108382
+ [selectedElement]="selectedElement()"
108383
+ />
107814
108384
  <span class="pptx-rb-sep"></span>
107815
108385
  <!--
107816
108386
  React parity (Toolbar.tsx: sArr = sHome || toolbarSection === 'arrange'):
@@ -110411,31 +110981,10 @@ function previewAngularAnimation(animation) {
110411
110981
  return true;
110412
110982
  }
110413
110983
 
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
- }
110984
+ /* oxlint-disable eslint/one-var -- the component's own field/method
110985
+ declarations below are independent, not adjacent initializations of
110986
+ related values; merging them would hurt readability far more than it
110987
+ helps. */
110439
110988
  class AnimationTimelineComponent {
110440
110989
  constructor() {
110441
110990
  this.animations = input.required(/* @ts-ignore */
@@ -110493,7 +111042,7 @@ class AnimationTimelineComponent {
110493
111042
  event.preventDefault();
110494
111043
  const source = this.dragIndex();
110495
111044
  if (source !== undefined) {
110496
- this.animationsChange.emit(reorderAnimationTimeline(this.animations(), source, index));
111045
+ this.animationsChange.emit(reorderAnimationTo(this.animations(), source, index));
110497
111046
  }
110498
111047
  this.clearDrag();
110499
111048
  }
@@ -117025,9 +117574,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
117025
117574
  * drawingShapes so the renderer reflows with the override). Reading the
117026
117575
  * current value off `node.style` drives the inspector controls.
117027
117576
  *
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.
117577
+ * 2. **Node-count bounds** - soft, per-layout min/max guards. The table,
117578
+ * `getSmartArtNodeBounds`, `canAddTopLevelNode` and `canRemoveTopLevelNode`
117579
+ * are re-exported from the single shared implementation in
117580
+ * `pptx-viewer-shared` (`packages/shared/src/render/smartart-node-limits.ts`),
117581
+ * which also backs the React and Vue bindings. `describeSmartArtBounds`
117582
+ * stays a thin Angular wrapper: it takes the shared function's neutral
117583
+ * English text and, when a `TranslateService` is supplied, swaps in the
117584
+ * translated string keyed off the same bounds, so the shared module never
117585
+ * takes an i18n dependency.
117031
117586
  *
117032
117587
  * @module angular-viewer/smart-art-node-style-helpers
117033
117588
  *
@@ -117074,61 +117629,39 @@ function toggleNodeBold(data, node) {
117074
117629
  function toggleNodeItalic(data, node) {
117075
117630
  return setNodeStyle(data, node.id, { italic: !nodeItalic(node) });
117076
117631
  }
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
- }
117632
+ // ── Node-count bounds (re-exported from pptx-viewer-shared) ────────────────
117633
+ // See the module doc comment: the table, `getSmartArtNodeBounds`,
117634
+ // `canAddTopLevelNode` and `canRemoveTopLevelNode` are re-exported above,
117635
+ // unmodified, from the single shared implementation. Only
117636
+ // `describeSmartArtBounds` stays defined here, as a thin wrapper that applies
117637
+ // this binding's `TranslateService` on top of the shared neutral text.
117096
117638
  /** Count of top-level (parentless) nodes in a diagram. */
117097
117639
  function topLevelNodeCount(data) {
117098
117640
  return (data.nodes ?? []).filter((node) => !node.parentId).length;
117099
117641
  }
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
117642
  /**
117111
117643
  * Short, human-readable explanation of a layout's bounds for a tooltip / hint,
117112
117644
  * or `undefined` when the layout imposes no meaningful limit.
117645
+ *
117646
+ * Delegates the neutral English text to the shared implementation; when a
117647
+ * `TranslateService` is supplied, swaps in the translated string for the same
117648
+ * bounds instead.
117113
117649
  */
117114
117650
  function describeSmartArtBounds(layout, translate) {
117115
117651
  const { min, max } = getSmartArtNodeBounds(layout);
117652
+ if (!translate) {
117653
+ return describeSmartArtBounds$1(layout);
117654
+ }
117116
117655
  if (min <= 1 && max === undefined) {
117117
117656
  return undefined;
117118
117657
  }
117119
117658
  if (max === undefined) {
117120
- return translate
117121
- ? translate.instant('pptx.smartArt.boundsHintMin', { min })
117122
- : `Works best with at least ${min} items.`;
117659
+ return translate.instant('pptx.smartArt.boundsHintMin', { min });
117123
117660
  }
117124
117661
  if (min === max) {
117125
- return translate
117126
- ? translate.instant('pptx.smartArt.boundsHintExact', { max })
117127
- : `This layout uses exactly ${max} items.`;
117662
+ return translate.instant('pptx.smartArt.boundsHintExact', { max });
117128
117663
  }
117129
- return translate
117130
- ? translate.instant('pptx.smartArt.boundsHintRange', { min, max })
117131
- : `Works best with ${min} to ${max} items.`;
117664
+ return translate.instant('pptx.smartArt.boundsHintRange', { min, max });
117132
117665
  }
117133
117666
 
117134
117667
  /**
@@ -117895,6 +118428,22 @@ function inputValue$1(event) {
117895
118428
  return target instanceof HTMLInputElement ? target.value : null;
117896
118429
  }
117897
118430
 
118431
+ /**
118432
+ * table-properties-helpers.ts: pure helpers for the table properties inspector.
118433
+ *
118434
+ * The small immutable transforms behind the Angular port of the React
118435
+ * `TablePropertiesPanel` / `TableCellAdvancedFill`: building a CSS gradient
118436
+ * string from structured stops so the renderer shows edited gradients live.
118437
+ * Table quick-style preset application (`applyTableStylePreset`) and
118438
+ * column-width redistribution / "distribute evenly" one-liners live in
118439
+ * `pptx-viewer-shared`'s `render/table-style-presets.ts` and
118440
+ * `render/table-resize.ts` (imported from `../internal/shared`) so every
118441
+ * binding shares one copy of that logic.
118442
+ *
118443
+ * No Angular imports, so they are unit-testable with plain vitest.
118444
+ */
118445
+ /* oxlint-disable eslint/one-var -- independent, unrelated locals across these
118446
+ helpers; merging them into one statement would hurt readability. */
117898
118447
  /** Default row height (px) used when a row has no explicit height. */
117899
118448
  const DEFAULT_TABLE_ROW_HEIGHT = 32;
117900
118449
  /** The structure / style toggle flags shown as checkboxes, with i18n dictionary keys. */
@@ -117906,69 +118455,6 @@ const TABLE_STRUCTURE_TOGGLES = [
117906
118455
  { key: 'lastCol', labelKey: 'pptx.table.lastColumn' },
117907
118456
  { key: 'lastRow', labelKey: 'pptx.table.lastRow' },
117908
118457
  ];
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
118458
  /**
117973
118459
  * Build a CSS gradient string from structured cell-style gradient fields, so
117974
118460
  * the renderer (which reads `gradientFillCss`) shows an edited gradient live.
@@ -119389,6 +119875,8 @@ function stringFromEvent$1(event) {
119389
119875
  * controls. Every edit emits a fully-updated element through `elementChange`,
119390
119876
  * committed by the inspector as one undoable history entry.
119391
119877
  */
119878
+ /* oxlint-disable eslint/one-var -- each handler declares its own independent
119879
+ locals; merging them into one statement would hurt readability. */
119392
119880
  class TablePropertiesComponent {
119393
119881
  constructor() {
119394
119882
  /** The table element being edited. */
@@ -119435,7 +119923,7 @@ class TablePropertiesComponent {
119435
119923
  onEvenRows() {
119436
119924
  const data = this.td();
119437
119925
  if (data) {
119438
- this.emit({ rows: evenRowHeights(data) });
119926
+ this.emit({ rows: evenRowHeights(data.rows) });
119439
119927
  }
119440
119928
  }
119441
119929
  onColWidth(index, event) {
@@ -120869,7 +121357,7 @@ class InspectorPanelComponent {
120869
121357
  height: cur.height,
120870
121358
  rotation: cur.rotation ?? 0,
120871
121359
  opacity: cur.opacity ?? 1,
120872
- fillColor: fillColorOf(cur),
121360
+ fillColor: fillColorOf$1(cur),
120873
121361
  strokeColor: strokeColorOf(cur),
120874
121362
  textColor: textColorOf(cur),
120875
121363
  fontSize: fontSizeOf(cur),
@@ -121000,7 +121488,7 @@ class InspectorPanelComponent {
121000
121488
  return;
121001
121489
  }
121002
121490
  const cur = this.el();
121003
- this.editor.updateElement(this.slideIndex(), cur.id, shapeStylePatch(cur, { fillColor: color }));
121491
+ this.editor.updateElement(this.slideIndex(), cur.id, shapeStylePatch$1(cur, { fillColor: color }));
121004
121492
  }
121005
121493
  onStrokeColorChange(event) {
121006
121494
  const color = stringFromEvent(event);
@@ -121008,7 +121496,7 @@ class InspectorPanelComponent {
121008
121496
  return;
121009
121497
  }
121010
121498
  const cur = this.el();
121011
- this.editor.updateElement(this.slideIndex(), cur.id, shapeStylePatch(cur, { strokeColor: color }));
121499
+ this.editor.updateElement(this.slideIndex(), cur.id, shapeStylePatch$1(cur, { strokeColor: color }));
121012
121500
  }
121013
121501
  // ── Text style ───────────────────────────────────────────────────────────
121014
121502
  onTextColorChange(event) {
@@ -129590,19 +130078,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
129590
130078
  /** Read a control's current primitive value off the options snapshot. */
129591
130079
  function readOptionValue(options, control) {
129592
130080
  const group = options[control.group];
130081
+ // oxlint-disable-next-line eslint/one-var -- distinct concern from the lookup above, forcing one statement hurts readability
129593
130082
  const value = group[control.key];
129594
130083
  return typeof value === 'boolean' || typeof value === 'number' || typeof value === 'string'
129595
130084
  ? value
129596
130085
  : undefined;
129597
130086
  }
129598
- /** Clamp a number-control edit into its schema range (NaN falls to min). */
129599
- function clampOptionNumber(raw, min, max) {
129600
- const parsed = Number(raw);
129601
- if (!Number.isFinite(parsed)) {
129602
- return min;
129603
- }
129604
- return Math.min(max, Math.max(min, parsed));
129605
- }
129606
130087
  class OptionsPaneComponent {
129607
130088
  constructor() {
129608
130089
  this.tab = input.required(/* @ts-ignore */
@@ -129639,8 +130120,10 @@ class OptionsPaneComponent {
129639
130120
  if (control.kind !== 'number') {
129640
130121
  return;
129641
130122
  }
129642
- const raw = event.target.value;
129643
- this.emit(control, clampOptionNumber(raw, control.min, control.max));
130123
+ const clamped = clampOptionNumber(event.target.value, control.min, control.max);
130124
+ if (clamped !== undefined) {
130125
+ this.emit(control, clamped);
130126
+ }
129644
130127
  }
129645
130128
  emitText(control, event) {
129646
130129
  this.emit(control, event.target.value);
@@ -133297,16 +133780,33 @@ class PowerPointViewerComponent {
133297
133780
  }
133298
133781
  }
133299
133782
  /**
133300
- * Mobile "Format" slot: surface the inspector for the current selection.
133301
- * On mobile the format pane starts closed (React parity: the canvas owns
133302
- * the first paint), so this explicitly opens it; with an element selected
133303
- * it shows the element inspector, otherwise the slide-properties view.
133783
+ * Mobile bottom-bar tap: decide the next sheet with shared's `toggleSheet`
133784
+ * (via `applyMobileBarSheetTap`), same priority every binding follows -
133785
+ * tapping the open sheet closes it, tapping a different one switches to it.
133786
+ * `mobileSheetSvc` and `inspectorPanel` back different bar slots (slides/
133787
+ * notes vs. format/comments), so this is the one place that coordinates
133788
+ * both from the shared decision.
133304
133789
  */
133305
- onMobileFormat() {
133306
- this.mobileSheetSvc.mobileSheet.set(null);
133307
- // Close any tool panel, clear the mobile-closed default, and undo a
133308
- // prior swipe-down dismissal so the format pane surfaces.
133309
- this.inspectorPanel.openFormatPanel();
133790
+ applyMobileSheetTap(tapped) {
133791
+ applyMobileBarSheetTap(tapped, this.mobileBarSheet(), {
133792
+ openSlides: () => this.mobileSheetSvc.mobileSheet.set('slides'),
133793
+ // Close any tool panel, clear the mobile-closed default, and undo a
133794
+ // prior swipe-down dismissal so the format pane surfaces (with an
133795
+ // element selected it shows the element inspector, otherwise slide
133796
+ // properties).
133797
+ openInspector: () => this.inspectorPanel.openFormatPanel(),
133798
+ openComments: () => {
133799
+ this.inspectorPanel.activePanel.set('comments');
133800
+ this.inspectorPanel.mobileInspectorHidden.set(false);
133801
+ },
133802
+ openNotes: () => this.mobileSheetSvc.showNotes.set(true),
133803
+ closeAll: () => {
133804
+ this.mobileSheetSvc.mobileSheet.set(null);
133805
+ this.mobileSheetSvc.showNotes.set(false);
133806
+ this.inspectorPanel.activePanel.set(null);
133807
+ this.inspectorPanel.formatPanelClosed.set(true);
133808
+ },
133809
+ });
133310
133810
  }
133311
133811
  /** Receive draw-tool state changes from the ribbon Draw tab. */
133312
133812
  onDrawToolChange(state) {
@@ -134241,15 +134741,11 @@ class PowerPointViewerComponent {
134241
134741
  [slideCount]="slideCount()"
134242
134742
  [commentCount]="activeComments().length"
134243
134743
  [activeSheet]="mobileBarSheet()"
134244
- (openSlides)="
134245
- mobileSheetSvc.mobileSheet.set(
134246
- mobileSheetSvc.mobileSheet() === 'slides' ? null : 'slides'
134247
- )
134248
- "
134744
+ (openSlides)="applyMobileSheetTap('slides')"
134249
134745
  (insert)="mobileSheetSvc.onMobileInsert()"
134250
- (openFormat)="onMobileFormat()"
134251
- (openComments)="inspectorPanel.togglePanel('comments')"
134252
- (notes)="mobileSheetSvc.toggleNotes()"
134746
+ (openFormat)="applyMobileSheetTap('inspector')"
134747
+ (openComments)="applyMobileSheetTap('comments')"
134748
+ (notes)="applyMobileSheetTap('notes')"
134253
134749
  />
134254
134750
  }
134255
134751
  </div>
@@ -135179,15 +135675,11 @@ i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "22.1.2", ng
135179
135675
  [slideCount]="slideCount()"
135180
135676
  [commentCount]="activeComments().length"
135181
135677
  [activeSheet]="mobileBarSheet()"
135182
- (openSlides)="
135183
- mobileSheetSvc.mobileSheet.set(
135184
- mobileSheetSvc.mobileSheet() === 'slides' ? null : 'slides'
135185
- )
135186
- "
135678
+ (openSlides)="applyMobileSheetTap('slides')"
135187
135679
  (insert)="mobileSheetSvc.onMobileInsert()"
135188
- (openFormat)="onMobileFormat()"
135189
- (openComments)="inspectorPanel.togglePanel('comments')"
135190
- (notes)="mobileSheetSvc.toggleNotes()"
135680
+ (openFormat)="applyMobileSheetTap('inspector')"
135681
+ (openComments)="applyMobileSheetTap('comments')"
135682
+ (notes)="applyMobileSheetTap('notes')"
135191
135683
  />
135192
135684
  }
135193
135685
  </div>
@@ -137493,5 +137985,5 @@ function cn(...values) {
137493
137985
  * Generated bundle index. Do not edit.
137494
137986
  */
137495
137987
 
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
137988
+ 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 };
137989
+ //# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-DrfIMkrX.mjs.map