pptx-angular-viewer 1.1.39 → 1.1.41

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.
@@ -1,7 +1,7 @@
1
1
  import { NgStyle, NgClass, NgTemplateOutlet } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
3
  import { InjectionToken, input, output, computed, ChangeDetectionStrategy, Component, signal, Injectable, HostListener, effect, inject, DestroyRef, ElementRef, viewChild, afterNextRender, Injector } from '@angular/core';
4
- import { guideEmuToPx, getShapeClipPath, getAdjustmentAwareShapeClipPath, getShapeClipPathFromPreset, getCloudPathForRendering, hasShapeProperties, applyDrawingColorTransforms as applyDrawingColorTransforms$1, hslToRgb as hslToRgb$1, PRESET_COLOR_MAP, isImageLikeElement, hasTextProperties, MIN_ELEMENT_SIZE as MIN_ELEMENT_SIZE$2, THEME_COLOR_SCHEME_KEYS, getLinkedTextBoxSegments, deobfuscateFont, detectFontFormat, getSubstituteFontFamily, checkMissingAltText, checkMissingSlideTitle, checkLowContrast, checkComplexTables, checkBlankSlide, checkDuplicateTitles, createChartElement, formatCommentTimestamp, setChartAxisLogScale, setChartAxisTitleStyle, setChartAxisGridlineStyle, setChartSeriesMarker, setChartSeriesChartType, setChartDataPointFill, setChartDataPointExplosion, chartDataAddSeries, chartDataRemoveSeries, chartDataAddCategory, chartDataRemoveCategory, chartDataUpdatePoint, chartDataChangeType, updateSmartArtNodeText, addSmartArtNodeAsChild, removeSmartArtNode, promoteSmartArtNode, demoteSmartArtNode, reorderSmartArtNode, switchSmartArtLayout, SWITCHABLE_LAYOUT_TYPES, PptxHandler, EncryptedFileError, parseSignatureXml, isInkElement, isZoomElement, THEME_PRESETS, applyThemeToData, getAnimationPresetInfo } from 'pptx-viewer-core';
4
+ import { guideEmuToPx, getShapeClipPath, getAdjustmentAwareShapeClipPath, getShapeClipPathFromPreset, getCloudPathForRendering, hasShapeProperties, applyDrawingColorTransforms as applyDrawingColorTransforms$1, hslToRgb as hslToRgb$1, PRESET_COLOR_MAP, isImageLikeElement, hasTextProperties, MIN_ELEMENT_SIZE as MIN_ELEMENT_SIZE$2, THEME_COLOR_SCHEME_KEYS, getLinkedTextBoxSegments, svgPathToPolygons, deobfuscateFont, detectFontFormat, getSubstituteFontFamily, checkMissingAltText, checkMissingSlideTitle, checkLowContrast, checkComplexTables, checkBlankSlide, checkDuplicateTitles, createChartElement, formatCommentTimestamp, setChartTitle, setChartLegend, setChartDataLabels, setChartAxis, setChartSeriesTrendline, setChartSeriesErrorBars, setChartDataPointLabel, setChartAxisLogScale, setChartAxisTitleStyle, setChartAxisGridlineStyle, setChartSeriesMarker, setChartSeriesChartType, setChartDataPointFill, setChartDataPointExplosion, chartDataAddSeries, chartDataRemoveSeries, chartDataAddCategory, chartDataRemoveCategory, chartDataUpdatePoint, chartDataChangeType, updateSmartArtNodeText, addSmartArtNodeAsChild, removeSmartArtNode, promoteSmartArtNode, demoteSmartArtNode, reorderSmartArtNode, switchSmartArtLayout, SWITCHABLE_LAYOUT_TYPES, PptxHandler, EncryptedFileError, parseSignatureXml, isInkElement, isZoomElement, THEME_PRESETS, applyThemeToData, getAnimationPresetInfo } from 'pptx-viewer-core';
5
5
  import { jsPDF } from 'jspdf';
6
6
  import html2canvasPro from 'html2canvas-pro';
7
7
  import { DomSanitizer } from '@angular/platform-browser';
@@ -5837,6 +5837,168 @@ function renderWinLossSparkline(data) {
5837
5837
  ].join('');
5838
5838
  }
5839
5839
 
5840
+ const CHART_TYPE_OPTIONS = [
5841
+ { value: 'bar', label: 'Bar' },
5842
+ { value: 'line', label: 'Line' },
5843
+ { value: 'pie', label: 'Pie' },
5844
+ { value: 'doughnut', label: 'Doughnut' },
5845
+ { value: 'area', label: 'Area' },
5846
+ { value: 'scatter', label: 'Scatter' },
5847
+ { value: 'bubble', label: 'Bubble' },
5848
+ { value: 'radar', label: 'Radar' },
5849
+ { value: 'stock', label: 'Stock' },
5850
+ { value: 'waterfall', label: 'Waterfall' },
5851
+ { value: 'combo', label: 'Combo' },
5852
+ ];
5853
+ const GROUPING_OPTIONS = [
5854
+ { value: 'clustered', label: 'Clustered' },
5855
+ { value: 'stacked', label: 'Stacked' },
5856
+ { value: 'percentStacked', label: '100% Stacked' },
5857
+ ];
5858
+ const LEGEND_POSITION_OPTIONS = [
5859
+ { value: 't', label: 'Top' },
5860
+ { value: 'b', label: 'Bottom' },
5861
+ { value: 'l', label: 'Left' },
5862
+ { value: 'r', label: 'Right' },
5863
+ ];
5864
+ const TICK_LABEL_POSITION_OPTIONS = [
5865
+ { value: 'nextTo', label: 'Next to axis' },
5866
+ { value: 'high', label: 'High' },
5867
+ { value: 'low', label: 'Low' },
5868
+ { value: 'none', label: 'None' },
5869
+ ];
5870
+ const DISPLAY_UNITS_OPTIONS = [
5871
+ { value: '', label: 'None' },
5872
+ { value: 'hundreds', label: 'Hundreds' },
5873
+ { value: 'thousands', label: 'Thousands' },
5874
+ { value: 'tenThousands', label: 'Ten Thousands' },
5875
+ { value: 'hundredThousands', label: 'Hundred Thousands' },
5876
+ { value: 'millions', label: 'Millions' },
5877
+ { value: 'tenMillions', label: 'Ten Millions' },
5878
+ { value: 'hundredMillions', label: 'Hundred Millions' },
5879
+ { value: 'billions', label: 'Billions' },
5880
+ { value: 'trillions', label: 'Trillions' },
5881
+ ];
5882
+ const DATA_LABEL_CONTENT_OPTIONS = [
5883
+ { key: 'showValue', label: 'Value' },
5884
+ { key: 'showCategory', label: 'Category name' },
5885
+ { key: 'showSeriesName', label: 'Series name' },
5886
+ { key: 'showPercent', label: 'Percentage' },
5887
+ { key: 'showLegendKey', label: 'Legend key' },
5888
+ ];
5889
+ const DATA_LABEL_POSITION_OPTIONS = [
5890
+ { value: '', label: 'Default' },
5891
+ { value: 'ctr', label: 'Center' },
5892
+ { value: 'inEnd', label: 'Inside End' },
5893
+ { value: 'inBase', label: 'Inside Base' },
5894
+ { value: 'outEnd', label: 'Outside End' },
5895
+ { value: 'bestFit', label: 'Best Fit' },
5896
+ ];
5897
+ const TRENDLINE_TYPE_OPTIONS = [
5898
+ { value: '', label: 'None' },
5899
+ { value: 'linear', label: 'Linear' },
5900
+ { value: 'exponential', label: 'Exponential' },
5901
+ { value: 'logarithmic', label: 'Logarithmic' },
5902
+ { value: 'polynomial', label: 'Polynomial' },
5903
+ { value: 'power', label: 'Power' },
5904
+ { value: 'movingAvg', label: 'Moving Average' },
5905
+ ];
5906
+ const ERROR_BAR_VALTYPE_OPTIONS = [
5907
+ { value: '', label: 'None' },
5908
+ { value: 'fixedVal', label: 'Fixed value' },
5909
+ { value: 'percentage', label: 'Percentage' },
5910
+ { value: 'stdDev', label: 'Standard deviation' },
5911
+ { value: 'stdErr', label: 'Standard error' },
5912
+ ];
5913
+ const ERROR_BAR_TYPE_OPTIONS = [
5914
+ { value: 'both', label: 'Both' },
5915
+ { value: 'plus', label: 'Plus' },
5916
+ { value: 'minus', label: 'Minus' },
5917
+ ];
5918
+ const MARKER_SYMBOL_OPTIONS = [
5919
+ { value: '', label: 'Auto' },
5920
+ { value: 'none', label: 'None' },
5921
+ { value: 'circle', label: 'Circle' },
5922
+ { value: 'square', label: 'Square' },
5923
+ { value: 'diamond', label: 'Diamond' },
5924
+ { value: 'triangle', label: 'Triangle' },
5925
+ { value: 'x', label: 'X' },
5926
+ { value: 'star', label: 'Star' },
5927
+ { value: 'plus', label: 'Plus' },
5928
+ { value: 'dot', label: 'Dot' },
5929
+ { value: 'dash', label: 'Dash' },
5930
+ ];
5931
+ const GRIDLINE_DASH_OPTIONS = [
5932
+ { value: '', label: 'Default' },
5933
+ { value: 'solid', label: 'Solid' },
5934
+ { value: 'dash', label: 'Dash' },
5935
+ { value: 'dot', label: 'Dot' },
5936
+ { value: 'dashDot', label: 'Dash Dot' },
5937
+ { value: 'lgDash', label: 'Long Dash' },
5938
+ ];
5939
+ const COMBO_SERIES_TYPE_OPTIONS = [
5940
+ { value: '', label: 'Default' },
5941
+ { value: 'bar', label: 'Bar' },
5942
+ { value: 'line', label: 'Line' },
5943
+ { value: 'area', label: 'Area' },
5944
+ { value: 'scatter', label: 'Scatter' },
5945
+ ];
5946
+ /** Axis kinds the inspector exposes, with whether they carry a numeric scale. */
5947
+ const EDITABLE_AXIS_ROWS = [
5948
+ { type: 'valAx', label: 'Value axis', hasScale: true },
5949
+ { type: 'dateAx', label: 'Date axis', hasScale: true },
5950
+ { type: 'catAx', label: 'Category axis', hasScale: false },
5951
+ ];
5952
+ /** Chart types that support clustered/stacked grouping modes. */
5953
+ const GROUPING_SUPPORTED_TYPES = new Set([
5954
+ 'bar',
5955
+ 'line',
5956
+ 'area',
5957
+ ]);
5958
+ /** Chart types where trendlines are meaningful. */
5959
+ const TRENDLINE_SUPPORTED_TYPES = new Set([
5960
+ 'bar',
5961
+ 'line',
5962
+ 'area',
5963
+ 'scatter',
5964
+ 'bubble',
5965
+ ]);
5966
+ /** Chart types where error bars are meaningful. */
5967
+ const ERROR_BAR_SUPPORTED_TYPES = new Set([
5968
+ 'bar',
5969
+ 'line',
5970
+ 'area',
5971
+ 'scatter',
5972
+ 'bubble',
5973
+ ]);
5974
+ /** Value types that take a numeric amount (stdErr does not). */
5975
+ const ERROR_BAR_VALUE_TYPES = new Set([
5976
+ 'fixedVal',
5977
+ 'percentage',
5978
+ 'stdDev',
5979
+ ]);
5980
+ /** Chart types where series markers are meaningful. */
5981
+ const MARKER_SUPPORTED_TYPES = new Set([
5982
+ 'line',
5983
+ 'scatter',
5984
+ 'bubble',
5985
+ 'radar',
5986
+ ]);
5987
+ /** Cartesian chart types where a per-series combo type makes sense. */
5988
+ const COMBO_SUPPORTED_TYPES = new Set([
5989
+ 'bar',
5990
+ 'line',
5991
+ 'area',
5992
+ 'combo',
5993
+ ]);
5994
+ /** Chart types where per-point slice explosion (pull-out) is meaningful. */
5995
+ const EXPLOSION_SUPPORTED_TYPES = new Set([
5996
+ 'pie',
5997
+ 'pie3D',
5998
+ 'doughnut',
5999
+ 'ofPie',
6000
+ ]);
6001
+
5840
6002
  const GRIDLINE_COLOR$1 = '#e2e8f0';
5841
6003
  const AXIS_LABEL_COLOR$1 = '#64748b';
5842
6004
  const SECONDARY_GRID_COLOR = '#e2e8f0';
@@ -11339,6 +11501,217 @@ function finalizeClickGroup(steps, options) {
11339
11501
  return group;
11340
11502
  }
11341
11503
 
11504
+ /**
11505
+ * `animation-advanced-triggers` (pure evaluation of compound and simultaneous
11506
+ * OOXML timing-tree start/end conditions (`p:stCondLst` / `p:endCondLst`).
11507
+ *
11508
+ * The parse layer (`pptx-viewer-core`) preserves the FULL set of `p:cond`
11509
+ * entries per node on {@link PptxNativeAnimation.startConditions} /
11510
+ * {@link PptxNativeAnimation.endConditions}, but the historic playback model
11511
+ * collapsed every node down to a single `trigger` + `triggerDelayMs`. That lost
11512
+ * compound triggers (e.g. "fire on click OR after a 2s delay") and conditions
11513
+ * that depend on another time node ending (e.g. "after several preceding
11514
+ * effects"). This module re-derives correct sequencing semantics from the full
11515
+ * condition set so {@link import('./animation-timeline-builder').buildTimeline}
11516
+ * and {@link import('./animation-sequencer').AnimationSequencer} can honour them.
11517
+ *
11518
+ * ECMA-376 §19.5.28 (CT_TLTimeCondition): a `stCondLst` holds one OR MORE
11519
+ * conditions; the node may begin as soon as ANY one of them is satisfied (a
11520
+ * logical OR). `endCondLst` ends the node when ANY of its conditions fire. We
11521
+ * therefore model the set, then pick the condition that governs auto-playback
11522
+ * (the earliest auto-satisfiable one) while still recording whether a click /
11523
+ * hover alternative exists.
11524
+ *
11525
+ * @module render/animation-advanced-triggers
11526
+ */
11527
+ // ==========================================================================
11528
+ // Condition classification
11529
+ // ==========================================================================
11530
+ /** Events that begin a node as the result of a user click. */
11531
+ const CLICK_EVENTS = new Set(['onClick', 'onNext']);
11532
+ /** Events that begin a node as the result of a mouse hover. */
11533
+ const HOVER_EVENTS = new Set(['onMouseOver']);
11534
+ /**
11535
+ * Events that chain a node off another time node's lifecycle (begin/end).
11536
+ * These are the "after preceding effect(s)" conditions and always carry a
11537
+ * `targetTimeNodeId` (`@_tn`) pointing at the node they wait on.
11538
+ */
11539
+ const TIMENODE_EVENTS = new Set([
11540
+ 'onBegin',
11541
+ 'onEnd',
11542
+ 'begin',
11543
+ 'end',
11544
+ ]);
11545
+ /** Normalise a possibly-indefinite delay to a non-negative number of ms. */
11546
+ function normalizeDelay(delay) {
11547
+ if (delay === undefined) {
11548
+ return { ms: 0, indefinite: false };
11549
+ }
11550
+ if (delay < 0) {
11551
+ return { ms: 0, indefinite: true };
11552
+ }
11553
+ return { ms: delay, indefinite: false };
11554
+ }
11555
+ function classifyCondition(cond) {
11556
+ const evt = cond.event;
11557
+ if (evt && CLICK_EVENTS.has(evt)) {
11558
+ return 'click';
11559
+ }
11560
+ if (evt && HOVER_EVENTS.has(evt)) {
11561
+ return 'hover';
11562
+ }
11563
+ if (evt && TIMENODE_EVENTS.has(evt) && cond.targetTimeNodeId !== undefined) {
11564
+ return 'timenode';
11565
+ }
11566
+ // No event (or onBegin without a tn) with a delay is a pure time offset.
11567
+ return 'delay';
11568
+ }
11569
+ /**
11570
+ * Collapse a node's full `startConditions` OR-set into the single
11571
+ * {@link EffectiveStartCondition} that should drive playback, while preserving
11572
+ * the alternative interaction paths.
11573
+ *
11574
+ * Selection (OOXML "fire when ANY applies"):
11575
+ * 1. A time-node dependency (onEnd/onBegin of another node) governs sequencing
11576
+ * when present, so the effect chains after the referenced effect(s).
11577
+ * 2. Otherwise a pure delay governs (afterDelay / afterPrevious style).
11578
+ * 3. Otherwise the interactive trigger (click / hover) governs.
11579
+ * In every case, a coexisting click or hover condition is recorded as an
11580
+ * alternative so the engine can start the node early on user interaction.
11581
+ *
11582
+ * `fallbackTrigger` is the node's already-derived simple trigger; it is used
11583
+ * when the condition list is empty or carries no actionable semantics.
11584
+ */
11585
+ function resolveEffectiveStartCondition(conditions, fallbackTrigger) {
11586
+ const base = {
11587
+ trigger: fallbackTrigger,
11588
+ delayMs: 0,
11589
+ hasClickAlternative: false,
11590
+ hasHoverAlternative: false,
11591
+ indefinite: false,
11592
+ };
11593
+ if (!conditions || conditions.length === 0) {
11594
+ return base;
11595
+ }
11596
+ let timenode;
11597
+ let delayCond;
11598
+ let clickCond;
11599
+ let hoverCond;
11600
+ for (const cond of conditions) {
11601
+ switch (classifyCondition(cond)) {
11602
+ case 'timenode':
11603
+ // Prefer the smallest target node id for deterministic ordering.
11604
+ if (!timenode || (cond.targetTimeNodeId ?? 0) < (timenode.targetTimeNodeId ?? 0)) {
11605
+ timenode = cond;
11606
+ }
11607
+ break;
11608
+ case 'delay':
11609
+ // Prefer the smallest delay (earliest auto-fire) among delay conds.
11610
+ if (!delayCond || (cond.delay ?? 0) < (delayCond.delay ?? 0)) {
11611
+ delayCond = cond;
11612
+ }
11613
+ break;
11614
+ case 'click':
11615
+ if (!clickCond) {
11616
+ clickCond = cond;
11617
+ }
11618
+ break;
11619
+ case 'hover':
11620
+ if (!hoverCond) {
11621
+ hoverCond = cond;
11622
+ }
11623
+ break;
11624
+ }
11625
+ }
11626
+ base.hasClickAlternative = clickCond !== undefined;
11627
+ base.hasHoverAlternative = hoverCond !== undefined;
11628
+ if (clickCond?.targetShapeId) {
11629
+ base.clickTargetShapeId = clickCond.targetShapeId;
11630
+ }
11631
+ // 1. Time-node dependency governs sequencing.
11632
+ if (timenode) {
11633
+ const { ms, indefinite } = normalizeDelay(timenode.delay);
11634
+ base.trigger =
11635
+ timenode.event === 'onBegin' || timenode.event === 'begin' ? 'withPrevious' : 'afterPrevious';
11636
+ base.delayMs = ms;
11637
+ base.indefinite = indefinite;
11638
+ base.dependsOnTimeNodeId = timenode.targetTimeNodeId;
11639
+ base.dependsOnEvent = timenode.event;
11640
+ return base;
11641
+ }
11642
+ // 2. Pure delay governs.
11643
+ if (delayCond && (delayCond.delay ?? 0) !== 0) {
11644
+ const { ms, indefinite } = normalizeDelay(delayCond.delay);
11645
+ // A delay alongside a click means "auto after delay, or sooner on click".
11646
+ base.trigger = clickCond ? 'onClick' : 'afterDelay';
11647
+ base.delayMs = ms;
11648
+ base.indefinite = indefinite;
11649
+ return base;
11650
+ }
11651
+ // 3. Interactive trigger governs.
11652
+ if (clickCond) {
11653
+ base.trigger = clickCond.targetShapeId ? 'onShapeClick' : 'onClick';
11654
+ return base;
11655
+ }
11656
+ if (hoverCond) {
11657
+ base.trigger = 'onHover';
11658
+ return base;
11659
+ }
11660
+ // 4. Zero-delay-only condition (onBegin / @_delay="0"): keep fallback but
11661
+ // surface that nothing interactive is required (auto with previous).
11662
+ if (delayCond) {
11663
+ base.trigger = fallbackTrigger;
11664
+ base.delayMs = 0;
11665
+ }
11666
+ return base;
11667
+ }
11668
+ /**
11669
+ * Resolve the effective start condition for a parsed native animation,
11670
+ * combining its full `startConditions` OR-set with its already-derived simple
11671
+ * `trigger` as a fallback.
11672
+ */
11673
+ function resolveAnimationStart(anim) {
11674
+ return resolveEffectiveStartCondition(anim.startConditions, anim.trigger ?? 'onClick');
11675
+ }
11676
+ /** Collapse a node's `endConditions` OR-set into a playback-ready summary. */
11677
+ function resolveEffectiveEndCondition(conditions) {
11678
+ if (!conditions || conditions.length === 0) {
11679
+ return undefined;
11680
+ }
11681
+ const result = {
11682
+ endsOnMouseOut: false,
11683
+ endsOnClick: false,
11684
+ indefinite: false,
11685
+ };
11686
+ for (const cond of conditions) {
11687
+ const evt = cond.event;
11688
+ if (evt === 'onMouseOut') {
11689
+ result.endsOnMouseOut = true;
11690
+ }
11691
+ else if (evt && CLICK_EVENTS.has(evt)) {
11692
+ result.endsOnClick = true;
11693
+ if (cond.targetShapeId) {
11694
+ result.clickTargetShapeId = cond.targetShapeId;
11695
+ }
11696
+ }
11697
+ else if (evt && TIMENODE_EVENTS.has(evt) && cond.targetTimeNodeId !== undefined) {
11698
+ result.endsWithTimeNodeId = cond.targetTimeNodeId;
11699
+ }
11700
+ const { ms, indefinite } = normalizeDelay(cond.delay);
11701
+ if (indefinite) {
11702
+ result.indefinite = true;
11703
+ }
11704
+ else if (ms > 0 && result.delayMs === undefined) {
11705
+ result.delayMs = ms;
11706
+ }
11707
+ }
11708
+ return result;
11709
+ }
11710
+ /** Resolve the effective end condition for a parsed native animation. */
11711
+ function resolveAnimationEnd(anim) {
11712
+ return resolveEffectiveEndCondition(anim.endConditions);
11713
+ }
11714
+
11342
11715
  /**
11343
11716
  * `animation-timeline-text-build` — pure expansion of text-build animations
11344
11717
  * (by-paragraph / by-word / by-char) into staggered per-segment sub-animations.
@@ -11636,10 +12009,16 @@ class AnimationSequencer {
11636
12009
  this.dynamicKeyframeBlocks.push(dynamic.css);
11637
12010
  }
11638
12011
  const elementId = anim.targetId ?? '';
11639
- const trigger = anim.trigger ?? 'onClick';
12012
+ // Resolve compound / simultaneous start conditions (the OR-set in
12013
+ // p:stCondLst) into the effective sequencing trigger + governing
12014
+ // delay, instead of the single collapsed trigger.
12015
+ const effective = resolveAnimationStart(anim);
12016
+ const trigger = effective.trigger;
11640
12017
  const duration = anim.durationMs ?? defaultDuration(anim.presetClass);
11641
12018
  const animDelay = anim.delayMs ?? 0;
11642
- const triggerDelay = anim.triggerDelayMs ?? 0;
12019
+ const triggerDelay = anim.startConditions && anim.startConditions.length > 0
12020
+ ? effective.delayMs
12021
+ : (anim.triggerDelayMs ?? 0);
11643
12022
  const fill = fillModeForClass(anim.presetClass);
11644
12023
  // Compute repeat / direction from the native animation
11645
12024
  const iterCount = anim.repeatCount ?? 1;
@@ -11648,8 +12027,9 @@ class AnimationSequencer {
11648
12027
  let stepDelay;
11649
12028
  switch (trigger) {
11650
12029
  case 'onClick':
12030
+ case 'onShapeClick':
11651
12031
  case 'onHover':
11652
- // onClick / onHover resets the timeline delay only from explicit delay values.
12032
+ // onClick / onShapeClick / onHover reset the timeline: delay only from explicit delay values.
11653
12033
  cumulativeMs = 0;
11654
12034
  stepDelay = animDelay + triggerDelay;
11655
12035
  break;
@@ -11812,10 +12192,19 @@ function buildTimeline(nativeAnimations) {
11812
12192
  dynamicBlocks.push(dynamic.css);
11813
12193
  }
11814
12194
  const elementId = singleAnim.targetId ?? '';
11815
- const trigger = singleAnim.trigger ?? 'onClick';
12195
+ // Honour the FULL start-condition OR-set (compound / simultaneous
12196
+ // triggers) rather than the collapsed single trigger. The effective
12197
+ // condition drives grouping and supplies the governing start delay.
12198
+ const effective = resolveAnimationStart(singleAnim);
12199
+ const trigger = effective.trigger;
11816
12200
  const duration = singleAnim.durationMs ?? defaultDuration(singleAnim.presetClass);
11817
12201
  const animDelay = singleAnim.delayMs ?? 0;
11818
- const triggerDelay = singleAnim.triggerDelayMs ?? 0;
12202
+ // Use the governing condition delay when conditions were present;
12203
+ // otherwise fall back to the simple triggerDelayMs (afterDelay) so
12204
+ // existing single-condition slides are unchanged.
12205
+ const triggerDelay = singleAnim.startConditions && singleAnim.startConditions.length > 0
12206
+ ? effective.delayMs
12207
+ : (singleAnim.triggerDelayMs ?? 0);
11819
12208
  const presetClass = singleAnim.presetClass ?? 'entr';
11820
12209
  const fill = fillModeForClass(singleAnim.presetClass);
11821
12210
  // Compute repeat / direction
@@ -11825,8 +12214,10 @@ function buildTimeline(nativeAnimations) {
11825
12214
  if (presetClass === 'entr' && elementId) {
11826
12215
  entranceIds.add(elementId);
11827
12216
  }
11828
- // Determine whether to start a new click-group
11829
- const isOnClick = trigger === 'onClick';
12217
+ // Determine whether to start a new click-group. A compound condition
12218
+ // that resolves to a click (onClick, or an inline shape click that was
12219
+ // not split into an interactive sequence) also starts a new group.
12220
+ const isOnClick = trigger === 'onClick' || trigger === 'onShapeClick';
11830
12221
  const isFirstAnimation = clickGroups.length === 0 && currentGroup.length === 0;
11831
12222
  if (isOnClick || isFirstAnimation) {
11832
12223
  // Flush current group if non-empty
@@ -14785,6 +15176,151 @@ function setCellText(element, rowIndex, colIndex, text) {
14785
15176
  return { ...element, tableData: { ...tableData, rows } };
14786
15177
  }
14787
15178
 
15179
+ function cloneTextStyle(style) {
15180
+ if (!style) {
15181
+ return undefined;
15182
+ }
15183
+ return { ...style };
15184
+ }
15185
+ function cloneShapeStyle(style) {
15186
+ if (!style) {
15187
+ return undefined;
15188
+ }
15189
+ return {
15190
+ ...style,
15191
+ ...(style.fillGradientStops
15192
+ ? { fillGradientStops: style.fillGradientStops.map((stop) => ({ ...stop })) }
15193
+ : {}),
15194
+ };
15195
+ }
15196
+ function cloneSlideTransition(transition) {
15197
+ if (!transition) {
15198
+ return undefined;
15199
+ }
15200
+ return { ...transition };
15201
+ }
15202
+ function cloneElementAnimation(animation) {
15203
+ return { ...animation };
15204
+ }
15205
+ function cloneChartData(data) {
15206
+ if (!data) {
15207
+ return undefined;
15208
+ }
15209
+ return {
15210
+ ...data,
15211
+ categories: [...(data.categories || [])],
15212
+ series: (data.series || []).map((series) => ({
15213
+ ...series,
15214
+ values: [...(series.values || [])],
15215
+ })),
15216
+ };
15217
+ }
15218
+ function cloneSmartArtData(data) {
15219
+ if (!data) {
15220
+ return undefined;
15221
+ }
15222
+ return {
15223
+ ...data,
15224
+ nodes: (data.nodes || []).map((node) => ({ ...node })),
15225
+ };
15226
+ }
15227
+ function cloneElement(element) {
15228
+ switch (element.type) {
15229
+ case 'text':
15230
+ case 'shape':
15231
+ return {
15232
+ ...element,
15233
+ ...(element.textStyle ? { textStyle: cloneTextStyle(element.textStyle) } : {}),
15234
+ ...(element.shapeStyle ? { shapeStyle: cloneShapeStyle(element.shapeStyle) } : {}),
15235
+ ...(element.shapeAdjustments ? { shapeAdjustments: { ...element.shapeAdjustments } } : {}),
15236
+ ...(element.textSegments
15237
+ ? {
15238
+ textSegments: element.textSegments.map((segment) => ({
15239
+ ...segment,
15240
+ style: cloneTextStyle(segment.style) || {},
15241
+ })),
15242
+ }
15243
+ : {}),
15244
+ };
15245
+ case 'connector':
15246
+ case 'image':
15247
+ case 'picture':
15248
+ return {
15249
+ ...element,
15250
+ ...(element.shapeStyle ? { shapeStyle: cloneShapeStyle(element.shapeStyle) } : {}),
15251
+ ...(element.shapeAdjustments ? { shapeAdjustments: { ...element.shapeAdjustments } } : {}),
15252
+ };
15253
+ case 'table':
15254
+ return { ...element };
15255
+ case 'chart':
15256
+ return {
15257
+ ...element,
15258
+ chartData: cloneChartData(element.chartData),
15259
+ };
15260
+ case 'smartArt':
15261
+ return {
15262
+ ...element,
15263
+ smartArtData: cloneSmartArtData(element.smartArtData),
15264
+ };
15265
+ case 'ole':
15266
+ case 'media':
15267
+ case 'group':
15268
+ case 'ink':
15269
+ case 'zoom':
15270
+ case 'contentPart':
15271
+ case 'unknown':
15272
+ case 'model3d':
15273
+ return { ...element };
15274
+ default: {
15275
+ const _exhaustive = element;
15276
+ return _exhaustive;
15277
+ }
15278
+ }
15279
+ }
15280
+ function cloneSlide(slide) {
15281
+ return {
15282
+ ...slide,
15283
+ transition: cloneSlideTransition(slide.transition),
15284
+ animations: slide.animations?.map(cloneElementAnimation),
15285
+ comments: slide.comments?.map((comment) => ({ ...comment })),
15286
+ warnings: slide.warnings?.map((warning) => ({ ...warning })),
15287
+ elements: slide.elements.map(cloneElement),
15288
+ };
15289
+ }
15290
+ function cloneTemplateElementsBySlideId(templateElementsBySlideId) {
15291
+ const cloned = {};
15292
+ Object.entries(templateElementsBySlideId).forEach(([slideId, elements]) => {
15293
+ cloned[slideId] = elements.map(cloneElement);
15294
+ });
15295
+ return cloned;
15296
+ }
15297
+ /**
15298
+ * Deep-clone the cloneable fields of a history snapshot. Note: matches the
15299
+ * original React behaviour by NOT copying any binding-specific extras (e.g.
15300
+ * `actionLabel`); only the structural {@link HistorySnapshotLike} fields are
15301
+ * rebuilt.
15302
+ */
15303
+ function cloneHistorySnapshot(snapshot) {
15304
+ return {
15305
+ width: snapshot.width,
15306
+ height: snapshot.height,
15307
+ activeSlideIndex: snapshot.activeSlideIndex,
15308
+ slides: snapshot.slides.map(cloneSlide),
15309
+ templateElementsBySlideId: cloneTemplateElementsBySlideId(snapshot.templateElementsBySlideId),
15310
+ };
15311
+ }
15312
+ function cloneXmlObject(value) {
15313
+ if (!value) {
15314
+ return undefined;
15315
+ }
15316
+ try {
15317
+ return JSON.parse(JSON.stringify(value));
15318
+ }
15319
+ catch {
15320
+ return undefined;
15321
+ }
15322
+ }
15323
+
14788
15324
  /**
14789
15325
  * Pure geometry helpers for the align / distribute editor operations.
14790
15326
  *
@@ -20129,6 +20665,527 @@ function matchTextTokens(fromTokens, toTokens) {
20129
20665
  return pairs;
20130
20666
  }
20131
20667
 
20668
+ /**
20669
+ * Intelligent token-level diffing and morph animation for text morphing.
20670
+ *
20671
+ * The base matcher in `morph-text` pairs tokens by exact text then proximity,
20672
+ * which loses ordering when the same word repeats and treats every change as a
20673
+ * crossfade. This module adds a Longest-Common-Subsequence (LCS) alignment so
20674
+ * that tokens present in both the outgoing and incoming text are matched in a
20675
+ * stable, order-preserving way and animated as a move (slide + restyle), while
20676
+ * tokens that only appear on one side are classified as added (fade in) or
20677
+ * removed (fade out).
20678
+ *
20679
+ * @module render/morph-text-tokens
20680
+ */
20681
+ /**
20682
+ * Diff two ordered token lists into shared/added/removed operations using an
20683
+ * LCS backtrace. Shared tokens preserve order and repetition; insertions and
20684
+ * deletions are emitted in their natural sequence position.
20685
+ *
20686
+ * @param fromTokens Tokens from the outgoing element.
20687
+ * @param toTokens Tokens from the incoming element.
20688
+ * @returns Ordered diff operations describing the token-level transition.
20689
+ */
20690
+ function diffTokens(fromTokens, toTokens) {
20691
+ const n = fromTokens.length;
20692
+ const m = toTokens.length;
20693
+ // LCS length table.
20694
+ const lcs = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
20695
+ for (let i = n - 1; i >= 0; i--) {
20696
+ for (let j = m - 1; j >= 0; j--) {
20697
+ lcs[i][j] =
20698
+ fromTokens[i].text === toTokens[j].text
20699
+ ? lcs[i + 1][j + 1] + 1
20700
+ : Math.max(lcs[i + 1][j], lcs[i][j + 1]);
20701
+ }
20702
+ }
20703
+ // Backtrace into ordered ops.
20704
+ const ops = [];
20705
+ let i = 0;
20706
+ let j = 0;
20707
+ while (i < n && j < m) {
20708
+ if (fromTokens[i].text === toTokens[j].text) {
20709
+ ops.push({
20710
+ change: 'shared',
20711
+ from: fromTokens[i],
20712
+ to: toTokens[j],
20713
+ fromIndex: i,
20714
+ toIndex: j,
20715
+ });
20716
+ i++;
20717
+ j++;
20718
+ }
20719
+ else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
20720
+ ops.push({ change: 'removed', from: fromTokens[i], to: null, fromIndex: i, toIndex: -1 });
20721
+ i++;
20722
+ }
20723
+ else {
20724
+ ops.push({ change: 'added', from: null, to: toTokens[j], fromIndex: -1, toIndex: j });
20725
+ j++;
20726
+ }
20727
+ }
20728
+ while (i < n) {
20729
+ ops.push({ change: 'removed', from: fromTokens[i], to: null, fromIndex: i, toIndex: -1 });
20730
+ i++;
20731
+ }
20732
+ while (j < m) {
20733
+ ops.push({ change: 'added', from: null, to: toTokens[j], fromIndex: -1, toIndex: j });
20734
+ j++;
20735
+ }
20736
+ return ops;
20737
+ }
20738
+ // ---------------------------------------------------------------------------
20739
+ // Animation building
20740
+ // ---------------------------------------------------------------------------
20741
+ /** Resolve a token colour to a CSS string, preferring a parsed rgba form. */
20742
+ function tokenColor(token) {
20743
+ const parsed = parseHexColor(token.color);
20744
+ return parsed ? lerpColor(parsed, parsed, 0) : token.color;
20745
+ }
20746
+ /** Build a shared-token move keyframe: slide + restyle between positions. */
20747
+ function sharedKeyframes(name, op) {
20748
+ const from = op.from;
20749
+ const to = op.to;
20750
+ return `
20751
+ @keyframes ${name} {
20752
+ \tfrom {
20753
+ \t\tleft: ${Number((from.x * 100).toFixed(2))}%;
20754
+ \t\ttop: ${Number((from.y * 100).toFixed(2))}%;
20755
+ \t\tfont-size: ${from.fontSize}pt;
20756
+ \t\tfont-weight: ${from.fontWeight};
20757
+ \t\tcolor: ${tokenColor(from)};
20758
+ \t\topacity: 1;
20759
+ \t}
20760
+ \tto {
20761
+ \t\tleft: ${Number((to.x * 100).toFixed(2))}%;
20762
+ \t\ttop: ${Number((to.y * 100).toFixed(2))}%;
20763
+ \t\tfont-size: ${to.fontSize}pt;
20764
+ \t\tfont-weight: ${to.fontWeight};
20765
+ \t\tcolor: ${tokenColor(to)};
20766
+ \t\topacity: 1;
20767
+ \t}
20768
+ }`;
20769
+ }
20770
+ /** Build a fade keyframe for an added (in) or removed (out) token. */
20771
+ function fadeKeyframes(name, fadeIn) {
20772
+ const a = fadeIn ? 0 : 1;
20773
+ const b = fadeIn ? 1 : 0;
20774
+ return `
20775
+ @keyframes ${name} {
20776
+ \tfrom { opacity: ${a}; }
20777
+ \tto { opacity: ${b}; }
20778
+ }`;
20779
+ }
20780
+ /**
20781
+ * Build per-token CSS animations from a token diff. Shared tokens animate their
20782
+ * position/size/colour between states; added tokens fade in; removed tokens
20783
+ * fade out. Element ids embed the originating token index so bindings can map
20784
+ * a generated `<span>` to its keyframe.
20785
+ *
20786
+ * @param ops Ordered token diff from {@link diffTokens}.
20787
+ * @param fromId The outgoing element id (used for removed tokens).
20788
+ * @param toId The incoming element id (used for shared/added tokens).
20789
+ * @param durationMs Animation duration in milliseconds.
20790
+ * @param pairIndex Index of the owning element pair for unique naming.
20791
+ * @returns Per-token animation style descriptors.
20792
+ */
20793
+ function buildTokenMorphAnimations(ops, fromId, toId, durationMs, pairIndex) {
20794
+ const animations = [];
20795
+ for (let k = 0; k < ops.length; k++) {
20796
+ const op = ops[k];
20797
+ const name = `pptx-morph-tok-${pairIndex}-${k}`;
20798
+ const anim = `${name} ${durationMs}ms ${MORPH_EASING} forwards`;
20799
+ if (op.change === 'shared') {
20800
+ animations.push({
20801
+ elementId: `${toId}__token_${op.toIndex}`,
20802
+ animation: anim,
20803
+ keyframes: sharedKeyframes(name, op),
20804
+ });
20805
+ }
20806
+ else if (op.change === 'added') {
20807
+ animations.push({
20808
+ elementId: `${toId}__token_${op.toIndex}`,
20809
+ animation: anim,
20810
+ keyframes: fadeKeyframes(name, true),
20811
+ });
20812
+ }
20813
+ else {
20814
+ animations.push({
20815
+ elementId: `${fromId}__token_${op.fromIndex}`,
20816
+ animation: anim,
20817
+ keyframes: fadeKeyframes(name, false),
20818
+ });
20819
+ }
20820
+ }
20821
+ return animations;
20822
+ }
20823
+
20824
+ /** Number of sample points each outline is resampled to before interpolating. */
20825
+ const OUTLINE_SAMPLE_COUNT = 64;
20826
+ /** Number of points used to approximate an ellipse/circle outline. */
20827
+ const ELLIPSE_SAMPLE_COUNT = 48;
20828
+ // ---------------------------------------------------------------------------
20829
+ // CSS clip-path -> polygon
20830
+ // ---------------------------------------------------------------------------
20831
+ /** Resolve a CSS length token (`50%`, `12px`, `8`) against an axis length. */
20832
+ function resolveLength(token, axis) {
20833
+ const trimmed = token.trim();
20834
+ if (trimmed.endsWith('%')) {
20835
+ const pct = Number.parseFloat(trimmed.slice(0, -1));
20836
+ return Number.isFinite(pct) ? (pct / 100) * axis : 0;
20837
+ }
20838
+ const px = Number.parseFloat(trimmed.replace(/px$/u, ''));
20839
+ return Number.isFinite(px) ? px : 0;
20840
+ }
20841
+ /** Parse a `polygon(x y, x y, ...)` clip-path into a pixel-space polygon. */
20842
+ function polygonClipToPoints(value, width, height) {
20843
+ const inner = value.slice(value.indexOf('(') + 1, value.lastIndexOf(')'));
20844
+ const points = [];
20845
+ for (const pairStr of inner.split(',')) {
20846
+ const parts = pairStr.trim().split(/\s+/u);
20847
+ if (parts.length < 2) {
20848
+ continue;
20849
+ }
20850
+ points.push({
20851
+ x: resolveLength(parts[0], width),
20852
+ y: resolveLength(parts[1], height),
20853
+ });
20854
+ }
20855
+ return points;
20856
+ }
20857
+ /** Parse an `inset(t r b l ...)` clip-path into a rectangle polygon. */
20858
+ function insetClipToPoints(value, width, height) {
20859
+ const inner = value.slice(value.indexOf('(') + 1, value.lastIndexOf(')'));
20860
+ const tokens = inner.split(/round/u)[0].trim().split(/\s+/u);
20861
+ const top = resolveLength(tokens[0] ?? '0', height);
20862
+ const right = resolveLength(tokens[1] ?? tokens[0] ?? '0', width);
20863
+ const bottom = resolveLength(tokens[2] ?? tokens[0] ?? '0', height);
20864
+ const left = resolveLength(tokens[3] ?? tokens[1] ?? tokens[0] ?? '0', width);
20865
+ return [
20866
+ { x: left, y: top },
20867
+ { x: width - right, y: top },
20868
+ { x: width - right, y: height - bottom },
20869
+ { x: left, y: height - bottom },
20870
+ ];
20871
+ }
20872
+ /** Sample an `ellipse(...)` / `circle(...)` clip-path into a polygon. */
20873
+ function ellipseClipToPoints(value, width, height) {
20874
+ const isCircle = value.startsWith('circle');
20875
+ const inner = value.slice(value.indexOf('(') + 1, value.lastIndexOf(')'));
20876
+ const [radii, position] = inner.split(/\s+at\s+/u);
20877
+ const radiusTokens = radii.trim().split(/\s+/u).filter(Boolean);
20878
+ let rx = width / 2;
20879
+ let ry = height / 2;
20880
+ if (isCircle) {
20881
+ const r = resolveLength(radiusTokens[0] ?? '50%', Math.min(width, height));
20882
+ rx = r;
20883
+ ry = r;
20884
+ }
20885
+ else {
20886
+ rx = resolveLength(radiusTokens[0] ?? '50%', width);
20887
+ ry = resolveLength(radiusTokens[1] ?? radiusTokens[0] ?? '50%', height);
20888
+ }
20889
+ let cx = width / 2;
20890
+ let cy = height / 2;
20891
+ if (position) {
20892
+ const posTokens = position.trim().split(/\s+/u);
20893
+ cx = resolveLength(posTokens[0] ?? '50%', width);
20894
+ cy = resolveLength(posTokens[1] ?? posTokens[0] ?? '50%', height);
20895
+ }
20896
+ const points = [];
20897
+ for (let i = 0; i < ELLIPSE_SAMPLE_COUNT; i++) {
20898
+ const a = (i / ELLIPSE_SAMPLE_COUNT) * Math.PI * 2;
20899
+ points.push({ x: cx + Math.cos(a) * rx, y: cy + Math.sin(a) * ry });
20900
+ }
20901
+ return points;
20902
+ }
20903
+ /**
20904
+ * Convert a CSS `clip-path` value into a single pixel-space polygon. Supports
20905
+ * `polygon`, `inset`, `ellipse`, `circle`, and `path('...')` forms. For `path`
20906
+ * outlines the largest sub-path (by point count) is used.
20907
+ *
20908
+ * @param value The CSS clip-path string.
20909
+ * @param width Element width in pixels.
20910
+ * @param height Element height in pixels.
20911
+ * @returns The outline polygon, or an empty array if it cannot be parsed.
20912
+ */
20913
+ function clipPathToPolygon(value, width, height) {
20914
+ const v = value.trim();
20915
+ if (v.startsWith('polygon')) {
20916
+ return polygonClipToPoints(v, width, height);
20917
+ }
20918
+ if (v.startsWith('inset')) {
20919
+ return insetClipToPoints(v, width, height);
20920
+ }
20921
+ if (v.startsWith('ellipse') || v.startsWith('circle')) {
20922
+ return ellipseClipToPoints(v, width, height);
20923
+ }
20924
+ if (v.startsWith('path')) {
20925
+ const start = v.indexOf("'") >= 0 ? v.indexOf("'") : v.indexOf('"');
20926
+ const end = v.lastIndexOf("'") >= 0 ? v.lastIndexOf("'") : v.lastIndexOf('"');
20927
+ if (start >= 0 && end > start) {
20928
+ const d = v.slice(start + 1, end);
20929
+ const polys = svgPathToPolygons(d);
20930
+ if (polys.length === 0) {
20931
+ return [];
20932
+ }
20933
+ return polys.reduce((a, b) => (b.length > a.length ? b : a));
20934
+ }
20935
+ }
20936
+ return [];
20937
+ }
20938
+ // ---------------------------------------------------------------------------
20939
+ // Element outline resolution
20940
+ // ---------------------------------------------------------------------------
20941
+ /**
20942
+ * Resolve an element's outline polygon in its own pixel box. Falls back to the
20943
+ * element's bounding rectangle when no preset clip-path is available (e.g. a
20944
+ * plain `rect`, an image, or an unknown shape).
20945
+ *
20946
+ * @param element The element to resolve an outline for.
20947
+ * @returns A pixel-space polygon describing the element's outline.
20948
+ */
20949
+ function resolveElementOutline(element) {
20950
+ const width = Math.max(element.width, 1);
20951
+ const height = Math.max(element.height, 1);
20952
+ const shapeType = element.shapeType;
20953
+ const adjustments = element.shapeAdjustments;
20954
+ const clip = getResolvedShapeClipPathFor$1(shapeType, width, height, adjustments);
20955
+ if (clip) {
20956
+ const poly = clipPathToPolygon(clip, width, height);
20957
+ if (poly.length >= 3) {
20958
+ return poly;
20959
+ }
20960
+ }
20961
+ // Rectangle fallback (covers rect / roundrect / images / unmatched presets).
20962
+ return [
20963
+ { x: 0, y: 0 },
20964
+ { x: width, y: 0 },
20965
+ { x: width, y: height },
20966
+ { x: 0, y: height },
20967
+ ];
20968
+ }
20969
+
20970
+ // ---------------------------------------------------------------------------
20971
+ // Perimeter resampling
20972
+ // ---------------------------------------------------------------------------
20973
+ /** Euclidean distance between two points. */
20974
+ function dist(a, b) {
20975
+ return Math.hypot(a.x - b.x, a.y - b.y);
20976
+ }
20977
+ /**
20978
+ * Resample a closed polygon to exactly `count` points spaced uniformly by arc
20979
+ * length around its perimeter.
20980
+ *
20981
+ * @param poly The source polygon (treated as closed).
20982
+ * @param count Desired number of output points (>= 3).
20983
+ * @returns A new polygon with `count` evenly-spaced vertices.
20984
+ */
20985
+ function resamplePolygon(poly, count) {
20986
+ if (poly.length < 2) {
20987
+ return Array.from({ length: count }, () => ({ x: poly[0]?.x ?? 0, y: poly[0]?.y ?? 0 }));
20988
+ }
20989
+ // Cumulative perimeter length (closing edge included).
20990
+ const ring = [...poly, poly[0]];
20991
+ const segLen = [];
20992
+ let total = 0;
20993
+ for (let i = 0; i < ring.length - 1; i++) {
20994
+ const d = dist(ring[i], ring[i + 1]);
20995
+ segLen.push(d);
20996
+ total += d;
20997
+ }
20998
+ if (total === 0) {
20999
+ return Array.from({ length: count }, () => ({ x: poly[0].x, y: poly[0].y }));
21000
+ }
21001
+ const result = [];
21002
+ const step = total / count;
21003
+ let seg = 0;
21004
+ let segStart = 0;
21005
+ for (let i = 0; i < count; i++) {
21006
+ const target = i * step;
21007
+ while (seg < segLen.length - 1 && segStart + segLen[seg] < target) {
21008
+ segStart += segLen[seg];
21009
+ seg++;
21010
+ }
21011
+ const within = segLen[seg] > 0 ? (target - segStart) / segLen[seg] : 0;
21012
+ const a = ring[seg];
21013
+ const b = ring[seg + 1];
21014
+ result.push({ x: a.x + (b.x - a.x) * within, y: a.y + (b.y - a.y) * within });
21015
+ }
21016
+ return result;
21017
+ }
21018
+ // ---------------------------------------------------------------------------
21019
+ // Winding + rotation alignment
21020
+ // ---------------------------------------------------------------------------
21021
+ /** Signed area of a polygon; positive for counter-clockwise (SVG y-down). */
21022
+ function signedArea(poly) {
21023
+ let area = 0;
21024
+ for (let i = 0; i < poly.length; i++) {
21025
+ const a = poly[i];
21026
+ const b = poly[(i + 1) % poly.length];
21027
+ area += a.x * b.y - b.x * a.y;
21028
+ }
21029
+ return area / 2;
21030
+ }
21031
+ /** Rotate a ring so element `offset` becomes index 0. */
21032
+ function rotateRing(poly, offset) {
21033
+ const n = poly.length;
21034
+ const o = ((offset % n) + n) % n;
21035
+ return poly.slice(o).concat(poly.slice(0, o));
21036
+ }
21037
+ /**
21038
+ * Find the rotation of `to` that minimises total squared distance to `from`.
21039
+ * Both rings must already have the same length.
21040
+ */
21041
+ function bestRotation(from, to) {
21042
+ let bestOffset = 0;
21043
+ let bestCost = Infinity;
21044
+ for (let offset = 0; offset < to.length; offset++) {
21045
+ let cost = 0;
21046
+ for (let i = 0; i < from.length; i++) {
21047
+ const t = to[(i + offset) % to.length];
21048
+ const dx = from[i].x - t.x;
21049
+ const dy = from[i].y - t.y;
21050
+ cost += dx * dx + dy * dy;
21051
+ if (cost >= bestCost) {
21052
+ break;
21053
+ }
21054
+ }
21055
+ if (cost < bestCost) {
21056
+ bestCost = cost;
21057
+ bestOffset = offset;
21058
+ }
21059
+ }
21060
+ return bestOffset;
21061
+ }
21062
+ // ---------------------------------------------------------------------------
21063
+ // Pair normalisation
21064
+ // ---------------------------------------------------------------------------
21065
+ /**
21066
+ * Normalise two outlines into equal-length, like-oriented, rotation-aligned
21067
+ * rings ready for vertex-wise interpolation.
21068
+ *
21069
+ * @param fromPoly The outgoing outline polygon.
21070
+ * @param toPoly The incoming outline polygon.
21071
+ * @param count Common sample count (defaults to {@link OUTLINE_SAMPLE_COUNT}).
21072
+ * @returns A tuple of two normalised polygons of identical length.
21073
+ */
21074
+ function normalizeOutlinePair(fromPoly, toPoly, count = OUTLINE_SAMPLE_COUNT) {
21075
+ const from = resamplePolygon(fromPoly, count);
21076
+ let to = resamplePolygon(toPoly, count);
21077
+ // Align winding order so both rings travel the same direction.
21078
+ if (Math.sign(signedArea(from)) !== Math.sign(signedArea(to))) {
21079
+ to = [...to].reverse();
21080
+ }
21081
+ // Rotate `to` so its first vertex best matches `from`'s first vertex.
21082
+ const offset = bestRotation(from, to);
21083
+ to = rotateRing(to, offset);
21084
+ return [from, to];
21085
+ }
21086
+ // ---------------------------------------------------------------------------
21087
+ // Interpolation + serialisation
21088
+ // ---------------------------------------------------------------------------
21089
+ /**
21090
+ * Interpolate two normalised outlines at parameter `t` and serialise to an SVG
21091
+ * path `d` string (a single closed sub-path). Inputs should come from
21092
+ * {@link normalizeOutlinePair}; if lengths differ the shorter length is used.
21093
+ *
21094
+ * @param from The outgoing normalised polygon.
21095
+ * @param to The incoming normalised polygon.
21096
+ * @param t Interpolation parameter, clamped to [0, 1].
21097
+ * @returns An SVG path `d` string for the intermediate outline.
21098
+ */
21099
+ function interpolateOutline(from, to, t) {
21100
+ const clamped = Math.max(0, Math.min(1, t));
21101
+ const len = Math.min(from.length, to.length);
21102
+ if (len === 0) {
21103
+ return '';
21104
+ }
21105
+ const parts = [];
21106
+ for (let i = 0; i < len; i++) {
21107
+ const x = from[i].x + (to[i].x - from[i].x) * clamped;
21108
+ const y = from[i].y + (to[i].y - from[i].y) * clamped;
21109
+ parts.push(`${i === 0 ? 'M' : 'L'}${Number(x.toFixed(2))} ${Number(y.toFixed(2))}`);
21110
+ }
21111
+ parts.push('Z');
21112
+ return parts.join(' ');
21113
+ }
21114
+
21115
+ /** Number of intermediate keyframe stops baked into a geometry morph. */
21116
+ const GEOMETRY_MORPH_STEPS = 8;
21117
+ /**
21118
+ * Decide whether a matched pair warrants geometry morphing rather than a plain
21119
+ * crossfade. True when both elements carry a shape type and those types differ
21120
+ * (or their adjustment outlines differ), since same-type same-adjustment shapes
21121
+ * already morph correctly via the transform/scale animation.
21122
+ *
21123
+ * @param pair The matched element pair.
21124
+ * @returns True when geometry interpolation should be applied.
21125
+ */
21126
+ function shouldGeometryMorph(pair) {
21127
+ const fromType = pair.fromElement.shapeType;
21128
+ const toType = pair.toElement.shapeType;
21129
+ if (!fromType || !toType) {
21130
+ return false;
21131
+ }
21132
+ if (fromType.toLowerCase() !== toType.toLowerCase()) {
21133
+ return true;
21134
+ }
21135
+ const fromAdj = JSON.stringify(pair.fromElement.shapeAdjustments ?? {});
21136
+ const toAdj = JSON.stringify(pair.toElement.shapeAdjustments ?? {});
21137
+ return fromAdj !== toAdj;
21138
+ }
21139
+ /** Build the per-stop `clip-path` keyframe body for an outline morph. */
21140
+ function buildClipKeyframeBody(from, to, steps) {
21141
+ const [fromRing, toRing] = normalizeOutlinePair(resolveElementOutline(from), resolveElementOutline(to));
21142
+ if (fromRing.length === 0 || toRing.length === 0) {
21143
+ return null;
21144
+ }
21145
+ const lines = [];
21146
+ for (let s = 0; s <= steps; s++) {
21147
+ const t = s / steps;
21148
+ const d = interpolateOutline(fromRing, toRing, t);
21149
+ if (!d) {
21150
+ return null;
21151
+ }
21152
+ const pct = Number(((s / steps) * 100).toFixed(2));
21153
+ lines.push(`\t${pct}% {\n\t\tclip-path: path('${d}');\n\t}`);
21154
+ }
21155
+ return lines.join('\n');
21156
+ }
21157
+ /**
21158
+ * Generate a `clip-path` geometry-morph animation for a matched pair whose
21159
+ * shape outline changes. Returns null when the pair does not need geometry
21160
+ * morphing or its outlines cannot be resolved.
21161
+ *
21162
+ * The emitted animation targets the incoming element id and is intended to run
21163
+ * alongside the pair's transform/opacity animation, replacing the crossfade for
21164
+ * shape-type changes with a true outline tween.
21165
+ *
21166
+ * @param pair The matched element pair.
21167
+ * @param durationMs Animation duration in milliseconds.
21168
+ * @param pairIndex Index of the pair for unique keyframe naming.
21169
+ * @param steps Number of baked stops (defaults to {@link GEOMETRY_MORPH_STEPS}).
21170
+ * @returns A geometry-morph animation style, or null.
21171
+ */
21172
+ function generateGeometryMorphAnimation(pair, durationMs, pairIndex, steps = GEOMETRY_MORPH_STEPS) {
21173
+ if (!shouldGeometryMorph(pair)) {
21174
+ return null;
21175
+ }
21176
+ const body = buildClipKeyframeBody(pair.fromElement, pair.toElement, steps);
21177
+ if (!body) {
21178
+ return null;
21179
+ }
21180
+ const safeName = `pptx-morph-geo-${pairIndex}-${pair.toElement.id.replace(/[^a-zA-Z0-9]/gu, '')}`;
21181
+ const keyframes = `\n@keyframes ${safeName} {\n${body}\n}`;
21182
+ return {
21183
+ elementId: pair.toElement.id,
21184
+ animation: `${safeName} ${durationMs}ms ${MORPH_EASING} forwards`,
21185
+ keyframes,
21186
+ };
21187
+ }
21188
+
20132
21189
  // ---------------------------------------------------------------------------
20133
21190
  // Build colour/stroke interpolation keyframes
20134
21191
  // ---------------------------------------------------------------------------
@@ -20332,70 +21389,10 @@ function generateTextMorphAnimations(pair, durationMs, mode, pairIndex) {
20332
21389
  if (fromTokens.length === 0 && toTokens.length === 0) {
20333
21390
  return [];
20334
21391
  }
20335
- const tokenPairs = matchTextTokens(fromTokens, toTokens);
20336
- const animations = [];
20337
- for (let ti = 0; ti < tokenPairs.length; ti++) {
20338
- const tp = tokenPairs[ti];
20339
- const safeName = `pptx-morph-text-${pairIndex}-${ti}`;
20340
- if (tp.from && tp.to) {
20341
- // Matched token: animate position, size, color
20342
- const fromX = tp.from.x * 100;
20343
- const toX = tp.to.x * 100;
20344
- const fromColor = parseHexColor(tp.from.color);
20345
- const toColor = parseHexColor(tp.to.color);
20346
- const fromColorStr = fromColor ? lerpColor(fromColor, fromColor, 0) : tp.from.color;
20347
- const toColorStr = toColor ? lerpColor(toColor, toColor, 0) : tp.to.color;
20348
- const keyframes = `
20349
- @keyframes ${safeName} {
20350
- \tfrom {
20351
- \t\tleft: ${fromX}%;
20352
- \t\tfont-size: ${tp.from.fontSize}pt;
20353
- \t\tfont-weight: ${tp.from.fontWeight};
20354
- \t\tcolor: ${fromColorStr};
20355
- \t\topacity: 1;
20356
- \t}
20357
- \tto {
20358
- \t\tleft: ${toX}%;
20359
- \t\tfont-size: ${tp.to.fontSize}pt;
20360
- \t\tfont-weight: ${tp.to.fontWeight};
20361
- \t\tcolor: ${toColorStr};
20362
- \t\topacity: 1;
20363
- \t}
20364
- }`;
20365
- animations.push({
20366
- elementId: `${pair.toElement.id}__token_${ti}`,
20367
- animation: `${safeName} ${durationMs}ms ${MORPH_EASING} forwards`,
20368
- keyframes,
20369
- });
20370
- }
20371
- else if (tp.from && !tp.to) {
20372
- // Disappearing token: fade out
20373
- const keyframes = `
20374
- @keyframes ${safeName} {
20375
- \tfrom { opacity: 1; }
20376
- \tto { opacity: 0; }
20377
- }`;
20378
- animations.push({
20379
- elementId: `${pair.fromElement.id}__token_${ti}`,
20380
- animation: `${safeName} ${durationMs}ms ${MORPH_EASING} forwards`,
20381
- keyframes,
20382
- });
20383
- }
20384
- else if (!tp.from && tp.to) {
20385
- // Appearing token: fade in
20386
- const keyframes = `
20387
- @keyframes ${safeName} {
20388
- \tfrom { opacity: 0; }
20389
- \tto { opacity: 1; }
20390
- }`;
20391
- animations.push({
20392
- elementId: `${pair.toElement.id}__token_${ti}`,
20393
- animation: `${safeName} ${durationMs}ms ${MORPH_EASING} forwards`,
20394
- keyframes,
20395
- });
20396
- }
20397
- }
20398
- return animations;
21392
+ // Order-preserving LCS diff: shared tokens slide/restyle between positions,
21393
+ // added tokens fade in, removed tokens fade out (intelligent token morph).
21394
+ const ops = diffTokens(fromTokens, toTokens);
21395
+ return buildTokenMorphAnimations(ops, pair.fromElement.id, pair.toElement.id, durationMs, pairIndex);
20399
21396
  }
20400
21397
  // ---------------------------------------------------------------------------
20401
21398
  // Full morph transition orchestrator
@@ -20418,6 +21415,15 @@ function generateFullMorphTransition(fromSlide, toSlide, durationMs, mode = 'obj
20418
21415
  // Generate main element morph animations
20419
21416
  const pairAnims = generateMorphAnimations(matchResult.pairs, durationMs, mode);
20420
21417
  allAnimations.push(...pairAnims);
21418
+ // Shape-geometry morph: for matched pairs whose shape outline changes
21419
+ // (different shape type or adjustment outline), interpolate the resolved
21420
+ // outlines instead of relying on a plain crossfade.
21421
+ for (let i = 0; i < matchResult.pairs.length; i++) {
21422
+ const geo = generateGeometryMorphAnimation(matchResult.pairs[i], durationMs, i);
21423
+ if (geo) {
21424
+ allAnimations.push(geo);
21425
+ }
21426
+ }
20421
21427
  // Generate text morph animations for text-bearing matched pairs
20422
21428
  if (mode === 'word' || mode === 'character') {
20423
21429
  for (let i = 0; i < matchResult.pairs.length; i++) {
@@ -25518,6 +26524,13 @@ function createTouchGestureRecognizer(config) {
25518
26524
  // Swipe state.
25519
26525
  let swipeStartX = 0;
25520
26526
  let swipeStartY = 0;
26527
+ // Most recent single-finger position. Lets a swipe still be measured when
26528
+ // `touchend` arrives with an empty `changedTouches` list (some synthetic
26529
+ // dispatchers, e.g. CDP `Input.dispatchTouchEvent`, omit it). Real
26530
+ // browsers always populate `changedTouches`, so their behaviour is
26531
+ // unchanged.
26532
+ let swipeLastX = 0;
26533
+ let swipeLastY = 0;
25521
26534
  // Long-press state.
25522
26535
  let longPressTimer = null;
25523
26536
  let longPressStartX = 0;
@@ -25541,6 +26554,8 @@ function createTouchGestureRecognizer(config) {
25541
26554
  // Potential swipe or long-press.
25542
26555
  swipeStartX = e.touches[0].clientX;
25543
26556
  swipeStartY = e.touches[0].clientY;
26557
+ swipeLastX = swipeStartX;
26558
+ swipeLastY = swipeStartY;
25544
26559
  longPressStartX = e.touches[0].clientX;
25545
26560
  longPressStartY = e.touches[0].clientY;
25546
26561
  cancelLongPress();
@@ -25561,6 +26576,8 @@ function createTouchGestureRecognizer(config) {
25561
26576
  }
25562
26577
  }
25563
26578
  else if (e.touches.length === 1) {
26579
+ swipeLastX = e.touches[0].clientX;
26580
+ swipeLastY = e.touches[0].clientY;
25564
26581
  // Cancel the long-press if the finger moved too far.
25565
26582
  const dx = e.touches[0].clientX - longPressStartX;
25566
26583
  const dy = e.touches[0].clientY - longPressStartY;
@@ -25577,10 +26594,13 @@ function createTouchGestureRecognizer(config) {
25577
26594
  return;
25578
26595
  }
25579
26596
  cancelLongPress();
25580
- // Detect a swipe from the touch that just ended.
25581
- if (e.changedTouches.length === 1 && e.touches.length === 0) {
25582
- const endX = e.changedTouches[0].clientX;
25583
- const endY = e.changedTouches[0].clientY;
26597
+ // Detect a swipe from the touch that just ended, but only once every
26598
+ // finger is up. Prefer the lifted touch's coordinates; fall back to the
26599
+ // last tracked move position when `changedTouches` is empty (synthetic
26600
+ // dispatchers), so a swipe is not silently dropped.
26601
+ if (e.touches.length === 0) {
26602
+ const endX = e.changedTouches.length >= 1 ? e.changedTouches[0].clientX : swipeLastX;
26603
+ const endY = e.changedTouches.length >= 1 ? e.changedTouches[0].clientY : swipeLastY;
25584
26604
  const deltaX = endX - swipeStartX;
25585
26605
  const deltaY = endY - swipeStartY;
25586
26606
  if (Math.abs(deltaX) >= SWIPE_THRESHOLD_PX && Math.abs(deltaY) < SWIPE_MAX_VERTICAL_PX) {
@@ -33562,6 +34582,559 @@ function numberFromInput(event) {
33562
34582
  return Number.isFinite(parsed) ? parsed : null;
33563
34583
  }
33564
34584
 
34585
+ /**
34586
+ * chart-advanced-helpers.ts: Pure immutable wrappers around the remaining
34587
+ * advanced chart-editing core ops that `chart-data-helpers.ts` does not already
34588
+ * expose (legend, chart-level data labels, axis scale/format edits, chart title,
34589
+ * per-series trendlines/error bars, per-data-point label overrides).
34590
+ *
34591
+ * Each function clones the element's `chartData`, runs the in-place core op on
34592
+ * the clone, and returns a new `ChartPptxElement` so the Angular editor's
34593
+ * immutable contract (and clean history entries) is preserved. The heavy lifting
34594
+ * lives in `pptx-viewer-core`; these are thin framework-agnostic adapters.
34595
+ *
34596
+ * @module angular-viewer/chart-advanced-helpers
34597
+ */
34598
+ /** Apply an in-place core chart op to a deep clone of `element`. */
34599
+ function withClonedChart$1(element, mutate) {
34600
+ if (!element.chartData) {
34601
+ return element;
34602
+ }
34603
+ const clone = {
34604
+ ...element,
34605
+ chartData: structuredClone(element.chartData),
34606
+ };
34607
+ mutate(clone);
34608
+ return clone;
34609
+ }
34610
+ // ---------------------------------------------------------------------------
34611
+ // Chart title
34612
+ // ---------------------------------------------------------------------------
34613
+ /** Set the chart title text. */
34614
+ function setTitle(element, title) {
34615
+ return withClonedChart$1(element, (el) => setChartTitle(el, title));
34616
+ }
34617
+ // ---------------------------------------------------------------------------
34618
+ // Legend
34619
+ // ---------------------------------------------------------------------------
34620
+ /** Show/hide the chart legend and/or set its position. */
34621
+ function setLegend(element, options) {
34622
+ return withClonedChart$1(element, (el) => setChartLegend(el, options));
34623
+ }
34624
+ // ---------------------------------------------------------------------------
34625
+ // Chart-level data labels
34626
+ // ---------------------------------------------------------------------------
34627
+ /** Toggle chart-level data labels and set their content/position. */
34628
+ function setDataLabels(element, edit) {
34629
+ return withClonedChart$1(element, (el) => setChartDataLabels(el, edit));
34630
+ }
34631
+ // ---------------------------------------------------------------------------
34632
+ // Axis scale / number format / gridline visibility / tick labels / units
34633
+ // ---------------------------------------------------------------------------
34634
+ /** Edit value/category axis formatting (min/max/units/format/gridlines/etc.). */
34635
+ function setAxis(element, axisType, edit) {
34636
+ return withClonedChart$1(element, (el) => setChartAxis(el, axisType, edit));
34637
+ }
34638
+ // ---------------------------------------------------------------------------
34639
+ // Per-series trendlines / error bars
34640
+ // ---------------------------------------------------------------------------
34641
+ /** Set or clear the primary trendline on a series. */
34642
+ function setSeriesTrendline(element, seriesIndex, trendline) {
34643
+ return withClonedChart$1(element, (el) => setChartSeriesTrendline(el, seriesIndex, trendline));
34644
+ }
34645
+ /** Set or clear the error bars on a series. */
34646
+ function setSeriesErrorBars(element, seriesIndex, errBars) {
34647
+ return withClonedChart$1(element, (el) => setChartSeriesErrorBars(el, seriesIndex, errBars));
34648
+ }
34649
+ // ---------------------------------------------------------------------------
34650
+ // Per-data-point label overrides
34651
+ // ---------------------------------------------------------------------------
34652
+ /** Set or clear a single data point's label override. */
34653
+ function setDataPointLabel(element, seriesIndex, pointIndex, edit) {
34654
+ return withClonedChart$1(element, (el) => setChartDataPointLabel(el, seriesIndex, pointIndex, edit));
34655
+ }
34656
+
34657
+ /**
34658
+ * chart-editor-styles.ts: Shared CSS string for the advanced chart-editor
34659
+ * inspector control components. Each control component reuses the same
34660
+ * `pptx-chart-card` look (heading, rows, inputs, swatches) so the panel reads
34661
+ * as one cohesive section. Kept as a single exported template-literal so the
34662
+ * styling lives in one place rather than being copy-pasted per component.
34663
+ *
34664
+ * @module angular-viewer/chart-editor-styles
34665
+ */
34666
+ /** Shared styles for all chart-editor control components. */
34667
+ const CHART_EDITOR_STYLES = `
34668
+ .pptx-chart-card {
34669
+ display: flex;
34670
+ flex-direction: column;
34671
+ gap: 0.35rem;
34672
+ padding: 0.5rem 0;
34673
+ border-top: 1px solid var(--pptx-inspector-border, #333);
34674
+ }
34675
+
34676
+ .pptx-chart-card__heading {
34677
+ font-size: 10px;
34678
+ font-weight: 600;
34679
+ text-transform: uppercase;
34680
+ letter-spacing: 0.05em;
34681
+ color: var(--pptx-inspector-muted, #888);
34682
+ margin: 0;
34683
+ }
34684
+
34685
+ .pptx-chart-card__group {
34686
+ display: flex;
34687
+ flex-direction: column;
34688
+ gap: 0.3rem;
34689
+ }
34690
+
34691
+ .pptx-chart-card__group--indent {
34692
+ margin-left: 0.5rem;
34693
+ }
34694
+
34695
+ .pptx-chart-card__subhead {
34696
+ font-size: 11px;
34697
+ font-weight: 600;
34698
+ }
34699
+
34700
+ .pptx-chart-card__row {
34701
+ display: flex;
34702
+ align-items: center;
34703
+ gap: 0.4rem;
34704
+ font-size: 11px;
34705
+ }
34706
+
34707
+ .pptx-chart-card__label {
34708
+ flex: 0 0 auto;
34709
+ width: 5rem;
34710
+ color: var(--pptx-inspector-muted, #888);
34711
+ }
34712
+
34713
+ .pptx-chart-card__label--wide {
34714
+ width: 6.5rem;
34715
+ }
34716
+
34717
+ .pptx-chart-card__name {
34718
+ flex: 1 1 auto;
34719
+ min-width: 0;
34720
+ overflow: hidden;
34721
+ text-overflow: ellipsis;
34722
+ white-space: nowrap;
34723
+ }
34724
+
34725
+ .pptx-chart-card__check {
34726
+ display: flex;
34727
+ align-items: center;
34728
+ gap: 0.35rem;
34729
+ font-size: 11px;
34730
+ cursor: pointer;
34731
+ }
34732
+
34733
+ .pptx-chart-card__input {
34734
+ flex: 1 1 auto;
34735
+ min-width: 0;
34736
+ box-sizing: border-box;
34737
+ padding: 2px 4px;
34738
+ font-size: 11px;
34739
+ background: var(--pptx-inspector-input-bg, #2d2d2d);
34740
+ border: 1px solid var(--pptx-inspector-border, #444);
34741
+ border-radius: 3px;
34742
+ color: inherit;
34743
+ outline: none;
34744
+ }
34745
+
34746
+ .pptx-chart-card__input--num {
34747
+ flex: 0 0 auto;
34748
+ width: 4rem;
34749
+ text-align: right;
34750
+ }
34751
+
34752
+ .pptx-chart-card__input:focus {
34753
+ border-color: var(--pptx-inspector-active, #0078d4);
34754
+ }
34755
+
34756
+ .pptx-chart-card__input:disabled {
34757
+ opacity: 0.6;
34758
+ cursor: not-allowed;
34759
+ }
34760
+
34761
+ .pptx-chart-card__color {
34762
+ flex: 0 0 auto;
34763
+ width: 26px;
34764
+ height: 20px;
34765
+ padding: 0;
34766
+ border: 1px solid var(--pptx-inspector-border, #444);
34767
+ border-radius: 3px;
34768
+ background: transparent;
34769
+ cursor: pointer;
34770
+ }
34771
+
34772
+ .pptx-chart-card__color:disabled {
34773
+ opacity: 0.6;
34774
+ cursor: not-allowed;
34775
+ }
34776
+
34777
+ .pptx-chart-card__clear {
34778
+ flex: 0 0 auto;
34779
+ padding: 0 2px;
34780
+ font-size: 12px;
34781
+ line-height: 1;
34782
+ background: none;
34783
+ border: none;
34784
+ color: var(--pptx-inspector-muted, #888);
34785
+ cursor: pointer;
34786
+ }
34787
+
34788
+ .pptx-chart-card__clear:hover {
34789
+ color: var(--pptx-inspector-danger, #f47c7c);
34790
+ }
34791
+
34792
+ .pptx-chart-card__input--num::-webkit-outer-spin-button,
34793
+ .pptx-chart-card__input--num::-webkit-inner-spin-button {
34794
+ -webkit-appearance: none;
34795
+ margin: 0;
34796
+ }
34797
+ `;
34798
+
34799
+ /**
34800
+ * chart-event-helpers.ts: Small typed DOM-event readers shared by the chart
34801
+ * inspector control components. They narrow `event.target` to the right element
34802
+ * type and pull the value out, returning `null` when the target is unexpected so
34803
+ * callers can bail without firing a no-op edit.
34804
+ *
34805
+ * @module angular-viewer/chart-event-helpers
34806
+ */
34807
+ /** Read the string value from an `<input>` or `<select>` change/input event. */
34808
+ function stringFromEvent$5(event) {
34809
+ const target = event.target;
34810
+ if (target instanceof HTMLInputElement || target instanceof HTMLSelectElement) {
34811
+ return target.value;
34812
+ }
34813
+ return null;
34814
+ }
34815
+ /** Read the value from a `<select>` change event. */
34816
+ function selectValue$1(event) {
34817
+ const target = event.target;
34818
+ return target instanceof HTMLSelectElement ? target.value : null;
34819
+ }
34820
+ /** Read the checked state from a checkbox `<input>` change event. */
34821
+ function boolFromEvent(event) {
34822
+ const target = event.target;
34823
+ return target instanceof HTMLInputElement ? target.checked : false;
34824
+ }
34825
+ /**
34826
+ * Read a numeric value from an `<input type="number">`. Returns `null` for an
34827
+ * empty field (a "clear the override" signal) and `undefined` for a non-finite
34828
+ * entry (ignore). A finite parse returns the number.
34829
+ */
34830
+ function numFromEvent(event) {
34831
+ const raw = stringFromEvent$5(event);
34832
+ if (raw === null || raw === '') {
34833
+ return null;
34834
+ }
34835
+ const num = Number.parseFloat(raw);
34836
+ return Number.isFinite(num) ? num : undefined;
34837
+ }
34838
+
34839
+ /**
34840
+ * chart-axis-options.component.ts: Per-axis scale + label formatting controls.
34841
+ *
34842
+ * Selector: `pptx-chart-axis-options`
34843
+ *
34844
+ * Mirrors React's `ChartAxisOptions.tsx`: for each axis present on the chart it
34845
+ * exposes min/max/major-unit/minor-unit (scaled axes only), display units, axis
34846
+ * title text, number format, tick-label position, and major/minor gridline
34847
+ * visibility. Edits route through the headless `setChartAxis` core op (via the
34848
+ * `setAxis` immutable wrapper) and emit a new `ChartPptxElement`.
34849
+ *
34850
+ * @module angular-viewer/chart-axis-options
34851
+ */
34852
+ const AXIS_DEFS$1 = [
34853
+ { type: 'valAx', label: 'Value axis', hasScale: true },
34854
+ { type: 'dateAx', label: 'Date axis', hasScale: true },
34855
+ { type: 'catAx', label: 'Category axis', hasScale: false },
34856
+ ];
34857
+ /** Numeric scale fields, paired with their label, rendered for scaled axes. */
34858
+ const SCALE_FIELDS = [
34859
+ { key: 'min', label: 'Minimum' },
34860
+ { key: 'max', label: 'Maximum' },
34861
+ { key: 'majorUnit', label: 'Major unit' },
34862
+ { key: 'minorUnit', label: 'Minor unit' },
34863
+ ];
34864
+ class ChartAxisOptionsComponent {
34865
+ element = input.required(/* @ts-ignore */
34866
+ ...(ngDevMode ? [{ debugName: "element" }] : /* istanbul ignore next */ []));
34867
+ canEdit = input(true, /* @ts-ignore */
34868
+ ...(ngDevMode ? [{ debugName: "canEdit" }] : /* istanbul ignore next */ []));
34869
+ elementChange = output();
34870
+ scaleFields = SCALE_FIELDS;
34871
+ displayUnitOptions = DISPLAY_UNITS_OPTIONS;
34872
+ tickPositionOptions = TICK_LABEL_POSITION_OPTIONS;
34873
+ /** Only the axes that actually exist on the chart (pie charts have none). */
34874
+ rows = computed(() => {
34875
+ const axes = this.element().chartData?.axes ?? [];
34876
+ const result = [];
34877
+ for (const def of AXIS_DEFS$1) {
34878
+ const axis = axes.find((a) => a.axisType === def.type);
34879
+ if (axis) {
34880
+ result.push({ ...def, axis });
34881
+ }
34882
+ }
34883
+ return result;
34884
+ }, /* @ts-ignore */
34885
+ ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
34886
+ numValue(axis, key) {
34887
+ const value = axis[key];
34888
+ return value === undefined ? '' : String(value);
34889
+ }
34890
+ onScaleField(axisType, key, event) {
34891
+ const num = numFromEvent(event);
34892
+ if (num === undefined) {
34893
+ return;
34894
+ }
34895
+ this.emit(axisType, { [key]: num });
34896
+ }
34897
+ onDisplayUnits(axisType, event) {
34898
+ const value = selectValue$1(event);
34899
+ if (value === null) {
34900
+ return;
34901
+ }
34902
+ this.emit(axisType, {
34903
+ displayUnits: (value || null),
34904
+ });
34905
+ }
34906
+ onTitleText(axisType, event) {
34907
+ const value = stringFromEvent$5(event);
34908
+ if (value === null) {
34909
+ return;
34910
+ }
34911
+ this.emit(axisType, { title: value });
34912
+ }
34913
+ onNumberFormat(axisType, event) {
34914
+ const value = stringFromEvent$5(event);
34915
+ if (value === null) {
34916
+ return;
34917
+ }
34918
+ this.emit(axisType, { numberFormat: value });
34919
+ }
34920
+ onTickPosition(axisType, event) {
34921
+ const value = selectValue$1(event);
34922
+ if (value === null) {
34923
+ return;
34924
+ }
34925
+ this.emit(axisType, { tickLabelPosition: value });
34926
+ }
34927
+ onGridlines(axisType, which, event) {
34928
+ const checked = boolFromEvent(event);
34929
+ this.emit(axisType, which === 'major' ? { majorGridlines: checked } : { minorGridlines: checked });
34930
+ }
34931
+ emit(axisType, edit) {
34932
+ this.elementChange.emit(setAxis(this.element(), axisType, edit));
34933
+ }
34934
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartAxisOptionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
34935
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ChartAxisOptionsComponent, isStandalone: true, selector: "pptx-chart-axis-options", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, canEdit: { classPropertyName: "canEdit", publicName: "canEdit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { elementChange: "elementChange" }, ngImport: i0, template: `
34936
+ @if (rows().length > 0) {
34937
+ <section class="pptx-chart-card" aria-label="Axis options">
34938
+ <h4 class="pptx-chart-card__heading">Axes</h4>
34939
+ @for (row of rows(); track row.type) {
34940
+ <div class="pptx-chart-card__group">
34941
+ <div class="pptx-chart-card__subhead">{{ row.label }}</div>
34942
+ <div class="pptx-chart-card__group pptx-chart-card__group--indent">
34943
+ @if (row.hasScale) {
34944
+ @for (field of scaleFields; track field.key) {
34945
+ <label class="pptx-chart-card__row">
34946
+ <span class="pptx-chart-card__label">{{ field.label }}</span>
34947
+ <input
34948
+ type="number"
34949
+ class="pptx-chart-card__input"
34950
+ placeholder="Auto"
34951
+ [disabled]="!canEdit()"
34952
+ [value]="numValue(row.axis, field.key)"
34953
+ (change)="onScaleField(row.type, field.key, $event)"
34954
+ />
34955
+ </label>
34956
+ }
34957
+ <label class="pptx-chart-card__row">
34958
+ <span class="pptx-chart-card__label">Display units</span>
34959
+ <select
34960
+ class="pptx-chart-card__input"
34961
+ [disabled]="!canEdit()"
34962
+ [value]="row.axis.displayUnits ?? ''"
34963
+ (change)="onDisplayUnits(row.type, $event)"
34964
+ >
34965
+ @for (opt of displayUnitOptions; track opt.value) {
34966
+ <option [value]="opt.value">{{ opt.label }}</option>
34967
+ }
34968
+ </select>
34969
+ </label>
34970
+ }
34971
+
34972
+ <label class="pptx-chart-card__row">
34973
+ <span class="pptx-chart-card__label">Axis title</span>
34974
+ <input
34975
+ type="text"
34976
+ class="pptx-chart-card__input"
34977
+ placeholder="Axis title"
34978
+ [disabled]="!canEdit()"
34979
+ [value]="row.axis.titleText ?? ''"
34980
+ (change)="onTitleText(row.type, $event)"
34981
+ />
34982
+ </label>
34983
+
34984
+ <label class="pptx-chart-card__row">
34985
+ <span class="pptx-chart-card__label">Number format</span>
34986
+ <input
34987
+ type="text"
34988
+ class="pptx-chart-card__input"
34989
+ placeholder="General"
34990
+ [disabled]="!canEdit()"
34991
+ [value]="row.axis.numFmt?.formatCode ?? ''"
34992
+ (change)="onNumberFormat(row.type, $event)"
34993
+ />
34994
+ </label>
34995
+
34996
+ <label class="pptx-chart-card__row">
34997
+ <span class="pptx-chart-card__label">Tick labels</span>
34998
+ <select
34999
+ class="pptx-chart-card__input"
35000
+ [disabled]="!canEdit()"
35001
+ [value]="row.axis.tickLblPos ?? 'nextTo'"
35002
+ (change)="onTickPosition(row.type, $event)"
35003
+ >
35004
+ @for (opt of tickPositionOptions; track opt.value) {
35005
+ <option [value]="opt.value">{{ opt.label }}</option>
35006
+ }
35007
+ </select>
35008
+ </label>
35009
+
35010
+ <label class="pptx-chart-card__check">
35011
+ <input
35012
+ type="checkbox"
35013
+ [disabled]="!canEdit()"
35014
+ [checked]="row.axis.majorGridlines ?? false"
35015
+ (change)="onGridlines(row.type, 'major', $event)"
35016
+ />
35017
+ <span>Major gridlines</span>
35018
+ </label>
35019
+ <label class="pptx-chart-card__check">
35020
+ <input
35021
+ type="checkbox"
35022
+ [disabled]="!canEdit()"
35023
+ [checked]="row.axis.minorGridlines ?? false"
35024
+ (change)="onGridlines(row.type, 'minor', $event)"
35025
+ />
35026
+ <span>Minor gridlines</span>
35027
+ </label>
35028
+ </div>
35029
+ </div>
35030
+ }
35031
+ </section>
35032
+ }
35033
+ `, isInline: true, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
35034
+ }
35035
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartAxisOptionsComponent, decorators: [{
35036
+ type: Component,
35037
+ args: [{ selector: 'pptx-chart-axis-options', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
35038
+ @if (rows().length > 0) {
35039
+ <section class="pptx-chart-card" aria-label="Axis options">
35040
+ <h4 class="pptx-chart-card__heading">Axes</h4>
35041
+ @for (row of rows(); track row.type) {
35042
+ <div class="pptx-chart-card__group">
35043
+ <div class="pptx-chart-card__subhead">{{ row.label }}</div>
35044
+ <div class="pptx-chart-card__group pptx-chart-card__group--indent">
35045
+ @if (row.hasScale) {
35046
+ @for (field of scaleFields; track field.key) {
35047
+ <label class="pptx-chart-card__row">
35048
+ <span class="pptx-chart-card__label">{{ field.label }}</span>
35049
+ <input
35050
+ type="number"
35051
+ class="pptx-chart-card__input"
35052
+ placeholder="Auto"
35053
+ [disabled]="!canEdit()"
35054
+ [value]="numValue(row.axis, field.key)"
35055
+ (change)="onScaleField(row.type, field.key, $event)"
35056
+ />
35057
+ </label>
35058
+ }
35059
+ <label class="pptx-chart-card__row">
35060
+ <span class="pptx-chart-card__label">Display units</span>
35061
+ <select
35062
+ class="pptx-chart-card__input"
35063
+ [disabled]="!canEdit()"
35064
+ [value]="row.axis.displayUnits ?? ''"
35065
+ (change)="onDisplayUnits(row.type, $event)"
35066
+ >
35067
+ @for (opt of displayUnitOptions; track opt.value) {
35068
+ <option [value]="opt.value">{{ opt.label }}</option>
35069
+ }
35070
+ </select>
35071
+ </label>
35072
+ }
35073
+
35074
+ <label class="pptx-chart-card__row">
35075
+ <span class="pptx-chart-card__label">Axis title</span>
35076
+ <input
35077
+ type="text"
35078
+ class="pptx-chart-card__input"
35079
+ placeholder="Axis title"
35080
+ [disabled]="!canEdit()"
35081
+ [value]="row.axis.titleText ?? ''"
35082
+ (change)="onTitleText(row.type, $event)"
35083
+ />
35084
+ </label>
35085
+
35086
+ <label class="pptx-chart-card__row">
35087
+ <span class="pptx-chart-card__label">Number format</span>
35088
+ <input
35089
+ type="text"
35090
+ class="pptx-chart-card__input"
35091
+ placeholder="General"
35092
+ [disabled]="!canEdit()"
35093
+ [value]="row.axis.numFmt?.formatCode ?? ''"
35094
+ (change)="onNumberFormat(row.type, $event)"
35095
+ />
35096
+ </label>
35097
+
35098
+ <label class="pptx-chart-card__row">
35099
+ <span class="pptx-chart-card__label">Tick labels</span>
35100
+ <select
35101
+ class="pptx-chart-card__input"
35102
+ [disabled]="!canEdit()"
35103
+ [value]="row.axis.tickLblPos ?? 'nextTo'"
35104
+ (change)="onTickPosition(row.type, $event)"
35105
+ >
35106
+ @for (opt of tickPositionOptions; track opt.value) {
35107
+ <option [value]="opt.value">{{ opt.label }}</option>
35108
+ }
35109
+ </select>
35110
+ </label>
35111
+
35112
+ <label class="pptx-chart-card__check">
35113
+ <input
35114
+ type="checkbox"
35115
+ [disabled]="!canEdit()"
35116
+ [checked]="row.axis.majorGridlines ?? false"
35117
+ (change)="onGridlines(row.type, 'major', $event)"
35118
+ />
35119
+ <span>Major gridlines</span>
35120
+ </label>
35121
+ <label class="pptx-chart-card__check">
35122
+ <input
35123
+ type="checkbox"
35124
+ [disabled]="!canEdit()"
35125
+ [checked]="row.axis.minorGridlines ?? false"
35126
+ (change)="onGridlines(row.type, 'minor', $event)"
35127
+ />
35128
+ <span>Minor gridlines</span>
35129
+ </label>
35130
+ </div>
35131
+ </div>
35132
+ }
35133
+ </section>
35134
+ }
35135
+ `, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"] }]
35136
+ }], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], elementChange: [{ type: i0.Output, args: ["elementChange"] }] } });
35137
+
33565
35138
  /**
33566
35139
  * chart-data-helpers.ts: Pure immutable helpers for chart data editing.
33567
35140
  *
@@ -33743,162 +35316,1696 @@ function removeCategory(element, catIndex) {
33743
35316
  * Update a single numeric value in a chart series, returning a new
33744
35317
  * `ChartPptxElement`.
33745
35318
  *
33746
- * Parses `rawValue` as a float. When the parsed value is not finite, the
33747
- * element is returned unchanged.
35319
+ * Parses `rawValue` as a float. When the parsed value is not finite, the
35320
+ * element is returned unchanged.
35321
+ *
35322
+ * @param element - The source chart element (not mutated).
35323
+ * @param seriesIndex - Zero-based series index.
35324
+ * @param catIndex - Zero-based category (point) index.
35325
+ * @param rawValue - The new value as a string (from an `<input type="number">`).
35326
+ * @returns A new `ChartPptxElement`, or the original when the value is invalid.
35327
+ *
35328
+ * @example
35329
+ * ```ts
35330
+ * const updated = setSeriesValue(el, 0, 2, "42.5");
35331
+ * ```
35332
+ */
35333
+ function setSeriesValue(element, seriesIndex, catIndex, rawValue) {
35334
+ const chartData = element.chartData;
35335
+ if (!chartData) {
35336
+ return element;
35337
+ }
35338
+ const num = parseFloat(rawValue);
35339
+ if (!Number.isFinite(num)) {
35340
+ return element;
35341
+ }
35342
+ return { ...element, chartData: chartDataUpdatePoint(chartData, seriesIndex, catIndex, num) };
35343
+ }
35344
+ // ---------------------------------------------------------------------------
35345
+ // setSeriesName
35346
+ // ---------------------------------------------------------------------------
35347
+ /**
35348
+ * Rename a series in a `ChartPptxElement`, returning a new element.
35349
+ *
35350
+ * @param element - The source chart element (not mutated).
35351
+ * @param seriesIndex - Zero-based index of the series to rename.
35352
+ * @param name - The new series name.
35353
+ * @returns A new `ChartPptxElement`.
35354
+ *
35355
+ * @example
35356
+ * ```ts
35357
+ * const updated = setSeriesName(el, 0, "Revenue");
35358
+ * ```
35359
+ */
35360
+ function setSeriesName(element, seriesIndex, name) {
35361
+ const chartData = element.chartData;
35362
+ if (!chartData) {
35363
+ return element;
35364
+ }
35365
+ const series = chartData.series.map((s, i) => (i === seriesIndex ? { ...s, name } : s));
35366
+ return { ...element, chartData: { ...chartData, series } };
35367
+ }
35368
+ // ---------------------------------------------------------------------------
35369
+ // setCategoryLabel
35370
+ // ---------------------------------------------------------------------------
35371
+ /**
35372
+ * Rename a category label in a `ChartPptxElement`, returning a new element.
35373
+ *
35374
+ * @param element - The source chart element (not mutated).
35375
+ * @param catIndex - Zero-based index of the category to rename.
35376
+ * @param label - The new label text.
35377
+ * @returns A new `ChartPptxElement`.
35378
+ *
35379
+ * @example
35380
+ * ```ts
35381
+ * const updated = setCategoryLabel(el, 0, "Q1 2025");
35382
+ * ```
35383
+ */
35384
+ function setCategoryLabel(element, catIndex, label) {
35385
+ const chartData = element.chartData;
35386
+ if (!chartData) {
35387
+ return element;
35388
+ }
35389
+ const categories = chartData.categories.map((c, i) => (i === catIndex ? label : c));
35390
+ return { ...element, chartData: { ...chartData, categories } };
35391
+ }
35392
+ // ---------------------------------------------------------------------------
35393
+ // setSeriesColor
35394
+ // ---------------------------------------------------------------------------
35395
+ /**
35396
+ * Set (or clear) the solid fill colour of a chart series, returning a new
35397
+ * `ChartPptxElement`. Pass a hex string (`#RRGGBB` or `RRGGBB`) to set the
35398
+ * colour, or `null` to clear it so the series falls back to its theme colour.
35399
+ *
35400
+ * Mirrors the headless `setChartSeriesColor` SDK op (which mutates in place)
35401
+ * with an immutable element-level wrapper for the inspector.
35402
+ *
35403
+ * @param element - The source chart element (not mutated).
35404
+ * @param seriesIndex - Zero-based index of the series to recolour.
35405
+ * @param color - Hex colour string, or `null` to clear.
35406
+ * @returns A new `ChartPptxElement`.
35407
+ *
35408
+ * @example
35409
+ * ```ts
35410
+ * const updated = setSeriesColor(el, 0, "#4472C4");
35411
+ * ```
35412
+ */
35413
+ function setSeriesColor(element, seriesIndex, color) {
35414
+ const chartData = element.chartData;
35415
+ if (!chartData) {
35416
+ return element;
35417
+ }
35418
+ const normalized = color ? normalizeHex(color) : undefined;
35419
+ const series = chartData.series.map((s, i) => (i === seriesIndex ? { ...s, color: normalized } : s));
35420
+ return { ...element, chartData: { ...chartData, series } };
35421
+ }
35422
+ /** Normalise a hex colour to a `#`-prefixed form, trimming whitespace. */
35423
+ function normalizeHex(color) {
35424
+ const trimmed = color.trim();
35425
+ return trimmed.startsWith('#') ? trimmed : `#${trimmed}`;
35426
+ }
35427
+ // ---------------------------------------------------------------------------
35428
+ // patchChartStyle
35429
+ // ---------------------------------------------------------------------------
35430
+ /**
35431
+ * Merge a `Partial<PptxChartStyle>` patch into a `ChartPptxElement`,
35432
+ * returning a new element.
35433
+ *
35434
+ * @param element - The source chart element (not mutated).
35435
+ * @param patch - Style fields to merge.
35436
+ * @returns A new `ChartPptxElement`.
35437
+ */
35438
+ function patchChartStyle(element, patch) {
35439
+ const chartData = element.chartData;
35440
+ if (!chartData) {
35441
+ return element;
35442
+ }
35443
+ return {
35444
+ ...element,
35445
+ chartData: { ...chartData, style: { ...chartData.style, ...patch } },
35446
+ };
35447
+ }
35448
+ // ---------------------------------------------------------------------------
35449
+ // patchChartData
35450
+ // ---------------------------------------------------------------------------
35451
+ /**
35452
+ * Merge a `Partial<PptxChartData>` patch into a `ChartPptxElement`.
35453
+ *
35454
+ * When the patch contains a `chartType` change, the smart
35455
+ * `chartDataChangeType` helper is used so grouping and category formats
35456
+ * are adapted automatically.
35457
+ *
35458
+ * @param element - The source chart element (not mutated).
35459
+ * @param patch - Chart data fields to merge.
35460
+ * @returns A new `ChartPptxElement`.
35461
+ */
35462
+ function patchChartData(element, patch) {
35463
+ const chartData = element.chartData;
35464
+ if (!chartData) {
35465
+ return element;
35466
+ }
35467
+ if (patch.chartType && patch.chartType !== chartData.chartType) {
35468
+ const adapted = chartDataChangeType(chartData, patch.chartType);
35469
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
35470
+ const { chartType: _ct, ...rest } = patch;
35471
+ return { ...element, chartData: { ...adapted, ...rest } };
35472
+ }
35473
+ return { ...element, chartData: { ...chartData, ...patch } };
35474
+ }
35475
+
35476
+ /**
35477
+ * chart-axis-style-options.component.ts: Per-axis log scaling, title font, and
35478
+ * gridline line styling.
35479
+ *
35480
+ * Selector: `pptx-chart-axis-style-options`
35481
+ *
35482
+ * Mirrors React's `ChartAxisStyleOptions.tsx`: logarithmic scale (value/date
35483
+ * axes) with a base, axis-title font family/size/bold/colour, and major/minor
35484
+ * gridline colour/width/dash styling (shown only when the matching gridlines are
35485
+ * enabled). Routes through `setAxisLogScale` / `setAxisTitleStyle` /
35486
+ * `setGridlineStyle` and emits a new `ChartPptxElement`.
35487
+ *
35488
+ * @module angular-viewer/chart-axis-style-options
35489
+ */
35490
+ const AXIS_DEFS = [
35491
+ { type: 'valAx', label: 'Value axis', hasScale: true },
35492
+ { type: 'dateAx', label: 'Date axis', hasScale: true },
35493
+ { type: 'catAx', label: 'Category axis', hasScale: false },
35494
+ ];
35495
+ class ChartAxisStyleOptionsComponent {
35496
+ element = input.required(/* @ts-ignore */
35497
+ ...(ngDevMode ? [{ debugName: "element" }] : /* istanbul ignore next */ []));
35498
+ canEdit = input(true, /* @ts-ignore */
35499
+ ...(ngDevMode ? [{ debugName: "canEdit" }] : /* istanbul ignore next */ []));
35500
+ elementChange = output();
35501
+ dashOptions = GRIDLINE_DASH_OPTIONS;
35502
+ gridlineKinds = ['major', 'minor'];
35503
+ rows = computed(() => {
35504
+ const axes = this.element().chartData?.axes ?? [];
35505
+ const result = [];
35506
+ for (const def of AXIS_DEFS) {
35507
+ const axis = axes.find((a) => a.axisType === def.type);
35508
+ if (axis) {
35509
+ result.push({ ...def, axis });
35510
+ }
35511
+ }
35512
+ return result;
35513
+ }, /* @ts-ignore */
35514
+ ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
35515
+ gridlineEnabled(axis, which) {
35516
+ return which === 'major' ? (axis.majorGridlines ?? false) : (axis.minorGridlines ?? false);
35517
+ }
35518
+ gridlineSpPr(axis, which) {
35519
+ return which === 'major' ? axis.majorGridlinesSpPr : axis.minorGridlinesSpPr;
35520
+ }
35521
+ onLogScale(axisType, axis, event) {
35522
+ this.elementChange.emit(setAxisLogScale(this.element(), axisType, {
35523
+ enabled: boolFromEvent(event),
35524
+ base: axis.logBase,
35525
+ }));
35526
+ }
35527
+ onLogBase(axisType, event) {
35528
+ const num = numFromEvent(event);
35529
+ if (typeof num !== 'number' || num <= 1) {
35530
+ return;
35531
+ }
35532
+ this.elementChange.emit(setAxisLogScale(this.element(), axisType, { enabled: true, base: num }));
35533
+ }
35534
+ onTitleFont(axisType, event) {
35535
+ const value = stringFromEvent$5(event);
35536
+ if (value === null) {
35537
+ return;
35538
+ }
35539
+ this.elementChange.emit(setAxisTitleStyle(this.element(), axisType, { fontFamily: value || null }));
35540
+ }
35541
+ onTitleSize(axisType, event) {
35542
+ const num = numFromEvent(event);
35543
+ if (num === undefined) {
35544
+ return;
35545
+ }
35546
+ this.elementChange.emit(setAxisTitleStyle(this.element(), axisType, { fontSize: num }));
35547
+ }
35548
+ onTitleBold(axisType, event) {
35549
+ this.elementChange.emit(setAxisTitleStyle(this.element(), axisType, { fontBold: boolFromEvent(event) }));
35550
+ }
35551
+ onTitleColor(axisType, event) {
35552
+ const value = stringFromEvent$5(event);
35553
+ if (value === null) {
35554
+ return;
35555
+ }
35556
+ this.elementChange.emit(setAxisTitleStyle(this.element(), axisType, { fontColor: value }));
35557
+ }
35558
+ onGridlineColor(axisType, which, event) {
35559
+ const value = stringFromEvent$5(event);
35560
+ if (value === null) {
35561
+ return;
35562
+ }
35563
+ this.elementChange.emit(setGridlineStyle(this.element(), axisType, which, { color: value }));
35564
+ }
35565
+ onGridlineWidth(axisType, which, event) {
35566
+ const num = numFromEvent(event);
35567
+ if (num === undefined) {
35568
+ return;
35569
+ }
35570
+ this.elementChange.emit(setGridlineStyle(this.element(), axisType, which, { width: num }));
35571
+ }
35572
+ onGridlineDash(axisType, which, event) {
35573
+ const value = selectValue$1(event);
35574
+ if (value === null) {
35575
+ return;
35576
+ }
35577
+ this.elementChange.emit(setGridlineStyle(this.element(), axisType, which, { dashStyle: value || null }));
35578
+ }
35579
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartAxisStyleOptionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
35580
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ChartAxisStyleOptionsComponent, isStandalone: true, selector: "pptx-chart-axis-style-options", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, canEdit: { classPropertyName: "canEdit", publicName: "canEdit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { elementChange: "elementChange" }, ngImport: i0, template: `
35581
+ @if (rows().length > 0) {
35582
+ <section class="pptx-chart-card" aria-label="Axis styling">
35583
+ <h4 class="pptx-chart-card__heading">Axis Styling</h4>
35584
+ @for (row of rows(); track row.type) {
35585
+ <div class="pptx-chart-card__group">
35586
+ <div class="pptx-chart-card__subhead">{{ row.label }}</div>
35587
+ <div class="pptx-chart-card__group pptx-chart-card__group--indent">
35588
+ @if (row.hasScale) {
35589
+ <div class="pptx-chart-card__row">
35590
+ <label class="pptx-chart-card__check">
35591
+ <input
35592
+ type="checkbox"
35593
+ [disabled]="!canEdit()"
35594
+ [checked]="row.axis.logScale ?? false"
35595
+ (change)="onLogScale(row.type, row.axis, $event)"
35596
+ />
35597
+ <span>Logarithmic scale</span>
35598
+ </label>
35599
+ @if (row.axis.logScale) {
35600
+ <input
35601
+ type="number"
35602
+ min="2"
35603
+ class="pptx-chart-card__input pptx-chart-card__input--num"
35604
+ title="Log base"
35605
+ [disabled]="!canEdit()"
35606
+ [value]="row.axis.logBase ?? 10"
35607
+ (change)="onLogBase(row.type, $event)"
35608
+ />
35609
+ }
35610
+ </div>
35611
+ }
35612
+
35613
+ <div class="pptx-chart-card__row">
35614
+ <span class="pptx-chart-card__label">Title font</span>
35615
+ <input
35616
+ type="text"
35617
+ class="pptx-chart-card__input"
35618
+ placeholder="Auto"
35619
+ [disabled]="!canEdit()"
35620
+ [value]="row.axis.fontFamily ?? ''"
35621
+ (change)="onTitleFont(row.type, $event)"
35622
+ />
35623
+ <input
35624
+ type="number"
35625
+ min="4"
35626
+ max="96"
35627
+ class="pptx-chart-card__input pptx-chart-card__input--num"
35628
+ title="Font size"
35629
+ placeholder="Auto"
35630
+ [disabled]="!canEdit()"
35631
+ [value]="row.axis.fontSize ?? ''"
35632
+ (change)="onTitleSize(row.type, $event)"
35633
+ />
35634
+ </div>
35635
+ <div class="pptx-chart-card__row">
35636
+ <label class="pptx-chart-card__check">
35637
+ <input
35638
+ type="checkbox"
35639
+ [disabled]="!canEdit()"
35640
+ [checked]="row.axis.fontBold ?? false"
35641
+ (change)="onTitleBold(row.type, $event)"
35642
+ />
35643
+ <span>Bold</span>
35644
+ </label>
35645
+ <span class="pptx-chart-card__label">Colour</span>
35646
+ <input
35647
+ type="color"
35648
+ class="pptx-chart-card__color"
35649
+ [disabled]="!canEdit()"
35650
+ [value]="row.axis.fontColor ?? '#000000'"
35651
+ (input)="onTitleColor(row.type, $event)"
35652
+ />
35653
+ </div>
35654
+
35655
+ @for (which of gridlineKinds; track which) {
35656
+ @if (gridlineEnabled(row.axis, which)) {
35657
+ <div class="pptx-chart-card__row">
35658
+ <span class="pptx-chart-card__label">
35659
+ {{ which === 'major' ? 'Major lines' : 'Minor lines' }}
35660
+ </span>
35661
+ <input
35662
+ type="color"
35663
+ class="pptx-chart-card__color"
35664
+ title="Gridline colour"
35665
+ [disabled]="!canEdit()"
35666
+ [value]="gridlineSpPr(row.axis, which)?.strokeColor ?? '#d9d9d9'"
35667
+ (input)="onGridlineColor(row.type, which, $event)"
35668
+ />
35669
+ <input
35670
+ type="number"
35671
+ min="0.25"
35672
+ step="0.25"
35673
+ class="pptx-chart-card__input pptx-chart-card__input--num"
35674
+ title="Gridline width"
35675
+ placeholder="Auto"
35676
+ [disabled]="!canEdit()"
35677
+ [value]="gridlineSpPr(row.axis, which)?.strokeWidth ?? ''"
35678
+ (change)="onGridlineWidth(row.type, which, $event)"
35679
+ />
35680
+ <select
35681
+ class="pptx-chart-card__input"
35682
+ title="Gridline dash"
35683
+ [disabled]="!canEdit()"
35684
+ [value]="gridlineSpPr(row.axis, which)?.strokeDashStyle ?? ''"
35685
+ (change)="onGridlineDash(row.type, which, $event)"
35686
+ >
35687
+ @for (opt of dashOptions; track opt.value) {
35688
+ <option [value]="opt.value">{{ opt.label }}</option>
35689
+ }
35690
+ </select>
35691
+ </div>
35692
+ }
35693
+ }
35694
+ </div>
35695
+ </div>
35696
+ }
35697
+ </section>
35698
+ }
35699
+ `, isInline: true, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
35700
+ }
35701
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartAxisStyleOptionsComponent, decorators: [{
35702
+ type: Component,
35703
+ args: [{ selector: 'pptx-chart-axis-style-options', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
35704
+ @if (rows().length > 0) {
35705
+ <section class="pptx-chart-card" aria-label="Axis styling">
35706
+ <h4 class="pptx-chart-card__heading">Axis Styling</h4>
35707
+ @for (row of rows(); track row.type) {
35708
+ <div class="pptx-chart-card__group">
35709
+ <div class="pptx-chart-card__subhead">{{ row.label }}</div>
35710
+ <div class="pptx-chart-card__group pptx-chart-card__group--indent">
35711
+ @if (row.hasScale) {
35712
+ <div class="pptx-chart-card__row">
35713
+ <label class="pptx-chart-card__check">
35714
+ <input
35715
+ type="checkbox"
35716
+ [disabled]="!canEdit()"
35717
+ [checked]="row.axis.logScale ?? false"
35718
+ (change)="onLogScale(row.type, row.axis, $event)"
35719
+ />
35720
+ <span>Logarithmic scale</span>
35721
+ </label>
35722
+ @if (row.axis.logScale) {
35723
+ <input
35724
+ type="number"
35725
+ min="2"
35726
+ class="pptx-chart-card__input pptx-chart-card__input--num"
35727
+ title="Log base"
35728
+ [disabled]="!canEdit()"
35729
+ [value]="row.axis.logBase ?? 10"
35730
+ (change)="onLogBase(row.type, $event)"
35731
+ />
35732
+ }
35733
+ </div>
35734
+ }
35735
+
35736
+ <div class="pptx-chart-card__row">
35737
+ <span class="pptx-chart-card__label">Title font</span>
35738
+ <input
35739
+ type="text"
35740
+ class="pptx-chart-card__input"
35741
+ placeholder="Auto"
35742
+ [disabled]="!canEdit()"
35743
+ [value]="row.axis.fontFamily ?? ''"
35744
+ (change)="onTitleFont(row.type, $event)"
35745
+ />
35746
+ <input
35747
+ type="number"
35748
+ min="4"
35749
+ max="96"
35750
+ class="pptx-chart-card__input pptx-chart-card__input--num"
35751
+ title="Font size"
35752
+ placeholder="Auto"
35753
+ [disabled]="!canEdit()"
35754
+ [value]="row.axis.fontSize ?? ''"
35755
+ (change)="onTitleSize(row.type, $event)"
35756
+ />
35757
+ </div>
35758
+ <div class="pptx-chart-card__row">
35759
+ <label class="pptx-chart-card__check">
35760
+ <input
35761
+ type="checkbox"
35762
+ [disabled]="!canEdit()"
35763
+ [checked]="row.axis.fontBold ?? false"
35764
+ (change)="onTitleBold(row.type, $event)"
35765
+ />
35766
+ <span>Bold</span>
35767
+ </label>
35768
+ <span class="pptx-chart-card__label">Colour</span>
35769
+ <input
35770
+ type="color"
35771
+ class="pptx-chart-card__color"
35772
+ [disabled]="!canEdit()"
35773
+ [value]="row.axis.fontColor ?? '#000000'"
35774
+ (input)="onTitleColor(row.type, $event)"
35775
+ />
35776
+ </div>
35777
+
35778
+ @for (which of gridlineKinds; track which) {
35779
+ @if (gridlineEnabled(row.axis, which)) {
35780
+ <div class="pptx-chart-card__row">
35781
+ <span class="pptx-chart-card__label">
35782
+ {{ which === 'major' ? 'Major lines' : 'Minor lines' }}
35783
+ </span>
35784
+ <input
35785
+ type="color"
35786
+ class="pptx-chart-card__color"
35787
+ title="Gridline colour"
35788
+ [disabled]="!canEdit()"
35789
+ [value]="gridlineSpPr(row.axis, which)?.strokeColor ?? '#d9d9d9'"
35790
+ (input)="onGridlineColor(row.type, which, $event)"
35791
+ />
35792
+ <input
35793
+ type="number"
35794
+ min="0.25"
35795
+ step="0.25"
35796
+ class="pptx-chart-card__input pptx-chart-card__input--num"
35797
+ title="Gridline width"
35798
+ placeholder="Auto"
35799
+ [disabled]="!canEdit()"
35800
+ [value]="gridlineSpPr(row.axis, which)?.strokeWidth ?? ''"
35801
+ (change)="onGridlineWidth(row.type, which, $event)"
35802
+ />
35803
+ <select
35804
+ class="pptx-chart-card__input"
35805
+ title="Gridline dash"
35806
+ [disabled]="!canEdit()"
35807
+ [value]="gridlineSpPr(row.axis, which)?.strokeDashStyle ?? ''"
35808
+ (change)="onGridlineDash(row.type, which, $event)"
35809
+ >
35810
+ @for (opt of dashOptions; track opt.value) {
35811
+ <option [value]="opt.value">{{ opt.label }}</option>
35812
+ }
35813
+ </select>
35814
+ </div>
35815
+ }
35816
+ }
35817
+ </div>
35818
+ </div>
35819
+ }
35820
+ </section>
35821
+ }
35822
+ `, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"] }]
35823
+ }], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], elementChange: [{ type: i0.Output, args: ["elementChange"] }] } });
35824
+
35825
+ /**
35826
+ * chart-combo-type-options.component.ts: Per-series combo chart-type controls.
35827
+ *
35828
+ * Selector: `pptx-chart-combo-type-options`
35829
+ *
35830
+ * Mirrors React's `ChartComboTypeOptions.tsx`: for cartesian charts (and existing
35831
+ * combos) with 2+ series, each series gets a chart-type selector so it can be
35832
+ * plotted with a different type (e.g. a line series inside a bar chart). Routes
35833
+ * through the `setSeriesChartType` immutable wrapper and emits a new
35834
+ * `ChartPptxElement`.
35835
+ *
35836
+ * @module angular-viewer/chart-combo-type-options
35837
+ */
35838
+ class ChartComboTypeOptionsComponent {
35839
+ element = input.required(/* @ts-ignore */
35840
+ ...(ngDevMode ? [{ debugName: "element" }] : /* istanbul ignore next */ []));
35841
+ canEdit = input(true, /* @ts-ignore */
35842
+ ...(ngDevMode ? [{ debugName: "canEdit" }] : /* istanbul ignore next */ []));
35843
+ elementChange = output();
35844
+ typeOptions = COMBO_SERIES_TYPE_OPTIONS;
35845
+ series = computed(() => this.element().chartData?.series ?? [], /* @ts-ignore */
35846
+ ...(ngDevMode ? [{ debugName: "series" }] : /* istanbul ignore next */ []));
35847
+ supported = computed(() => {
35848
+ const type = this.element().chartData?.chartType;
35849
+ return type !== undefined && COMBO_SUPPORTED_TYPES.has(type) && this.series().length >= 2;
35850
+ }, /* @ts-ignore */
35851
+ ...(ngDevMode ? [{ debugName: "supported" }] : /* istanbul ignore next */ []));
35852
+ onType(index, event) {
35853
+ const value = selectValue$1(event);
35854
+ if (value === null) {
35855
+ return;
35856
+ }
35857
+ this.elementChange.emit(setSeriesChartType(this.element(), index, value === '' ? null : value));
35858
+ }
35859
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartComboTypeOptionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
35860
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ChartComboTypeOptionsComponent, isStandalone: true, selector: "pptx-chart-combo-type-options", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, canEdit: { classPropertyName: "canEdit", publicName: "canEdit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { elementChange: "elementChange" }, ngImport: i0, template: `
35861
+ @if (supported()) {
35862
+ <section class="pptx-chart-card" aria-label="Combo type options">
35863
+ <h4 class="pptx-chart-card__heading">Combo Types</h4>
35864
+ <div class="pptx-chart-card__group">
35865
+ @for (s of series(); track $index; let i = $index) {
35866
+ <div class="pptx-chart-card__row">
35867
+ <span class="pptx-chart-card__name" [title]="s.name">{{ s.name }}</span>
35868
+ <select
35869
+ class="pptx-chart-card__input"
35870
+ [disabled]="!canEdit()"
35871
+ [value]="s.seriesChartType ?? ''"
35872
+ (change)="onType(i, $event)"
35873
+ >
35874
+ @for (opt of typeOptions; track opt.value) {
35875
+ <option [value]="opt.value">{{ opt.label }}</option>
35876
+ }
35877
+ </select>
35878
+ </div>
35879
+ }
35880
+ </div>
35881
+ </section>
35882
+ }
35883
+ `, isInline: true, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
35884
+ }
35885
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartComboTypeOptionsComponent, decorators: [{
35886
+ type: Component,
35887
+ args: [{ selector: 'pptx-chart-combo-type-options', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
35888
+ @if (supported()) {
35889
+ <section class="pptx-chart-card" aria-label="Combo type options">
35890
+ <h4 class="pptx-chart-card__heading">Combo Types</h4>
35891
+ <div class="pptx-chart-card__group">
35892
+ @for (s of series(); track $index; let i = $index) {
35893
+ <div class="pptx-chart-card__row">
35894
+ <span class="pptx-chart-card__name" [title]="s.name">{{ s.name }}</span>
35895
+ <select
35896
+ class="pptx-chart-card__input"
35897
+ [disabled]="!canEdit()"
35898
+ [value]="s.seriesChartType ?? ''"
35899
+ (change)="onType(i, $event)"
35900
+ >
35901
+ @for (opt of typeOptions; track opt.value) {
35902
+ <option [value]="opt.value">{{ opt.label }}</option>
35903
+ }
35904
+ </select>
35905
+ </div>
35906
+ }
35907
+ </div>
35908
+ </section>
35909
+ }
35910
+ `, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"] }]
35911
+ }], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], elementChange: [{ type: i0.Output, args: ["elementChange"] }] } });
35912
+
35913
+ /**
35914
+ * chart-data-label-options.component.ts: Chart-level data-label content +
35915
+ * position controls.
35916
+ *
35917
+ * Selector: `pptx-chart-data-label-options`
35918
+ *
35919
+ * Mirrors React's `ChartDataLabelOptions.tsx`: only renders when data labels are
35920
+ * switched on (the master toggle lives in `pptx-chart-display-options`). Lets the
35921
+ * user pick which content appears (value/category/series name/percent/legend key)
35922
+ * and the label position. Emits a complete new `ChartPptxElement`.
35923
+ *
35924
+ * @module angular-viewer/chart-data-label-options
35925
+ */
35926
+ class ChartDataLabelOptionsComponent {
35927
+ element = input.required(/* @ts-ignore */
35928
+ ...(ngDevMode ? [{ debugName: "element" }] : /* istanbul ignore next */ []));
35929
+ canEdit = input(true, /* @ts-ignore */
35930
+ ...(ngDevMode ? [{ debugName: "canEdit" }] : /* istanbul ignore next */ []));
35931
+ elementChange = output();
35932
+ contentOptions = DATA_LABEL_CONTENT_OPTIONS;
35933
+ positionOptions = DATA_LABEL_POSITION_OPTIONS;
35934
+ style = computed(() => this.element().chartData?.style ?? {}, /* @ts-ignore */
35935
+ ...(ngDevMode ? [{ debugName: "style" }] : /* istanbul ignore next */ []));
35936
+ labels = computed(() => this.style().dataLabels ?? {}, /* @ts-ignore */
35937
+ ...(ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []));
35938
+ onToggleContent(key, event) {
35939
+ this.elementChange.emit(setDataLabels(this.element(), { [key]: boolFromEvent(event) }));
35940
+ }
35941
+ onPosition(event) {
35942
+ const value = selectValue$1(event);
35943
+ if (value === null) {
35944
+ return;
35945
+ }
35946
+ this.elementChange.emit(setDataLabels(this.element(), {
35947
+ position: (value || undefined),
35948
+ }));
35949
+ }
35950
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartDataLabelOptionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
35951
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ChartDataLabelOptionsComponent, isStandalone: true, selector: "pptx-chart-data-label-options", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, canEdit: { classPropertyName: "canEdit", publicName: "canEdit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { elementChange: "elementChange" }, ngImport: i0, template: `
35952
+ @if (style().hasDataLabels) {
35953
+ <section class="pptx-chart-card" aria-label="Data label options">
35954
+ <h4 class="pptx-chart-card__heading">Data Labels</h4>
35955
+ <div class="pptx-chart-card__group">
35956
+ @for (opt of contentOptions; track opt.key) {
35957
+ <label class="pptx-chart-card__check">
35958
+ <input
35959
+ type="checkbox"
35960
+ [disabled]="!canEdit()"
35961
+ [checked]="labels()[opt.key] ?? false"
35962
+ (change)="onToggleContent(opt.key, $event)"
35963
+ />
35964
+ <span>{{ opt.label }}</span>
35965
+ </label>
35966
+ }
35967
+
35968
+ <label class="pptx-chart-card__row">
35969
+ <span class="pptx-chart-card__label">Position</span>
35970
+ <select
35971
+ class="pptx-chart-card__input"
35972
+ [disabled]="!canEdit()"
35973
+ [value]="labels().position ?? ''"
35974
+ (change)="onPosition($event)"
35975
+ >
35976
+ @for (opt of positionOptions; track opt.value) {
35977
+ <option [value]="opt.value">{{ opt.label }}</option>
35978
+ }
35979
+ </select>
35980
+ </label>
35981
+ </div>
35982
+ </section>
35983
+ }
35984
+ `, isInline: true, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
35985
+ }
35986
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartDataLabelOptionsComponent, decorators: [{
35987
+ type: Component,
35988
+ args: [{ selector: 'pptx-chart-data-label-options', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
35989
+ @if (style().hasDataLabels) {
35990
+ <section class="pptx-chart-card" aria-label="Data label options">
35991
+ <h4 class="pptx-chart-card__heading">Data Labels</h4>
35992
+ <div class="pptx-chart-card__group">
35993
+ @for (opt of contentOptions; track opt.key) {
35994
+ <label class="pptx-chart-card__check">
35995
+ <input
35996
+ type="checkbox"
35997
+ [disabled]="!canEdit()"
35998
+ [checked]="labels()[opt.key] ?? false"
35999
+ (change)="onToggleContent(opt.key, $event)"
36000
+ />
36001
+ <span>{{ opt.label }}</span>
36002
+ </label>
36003
+ }
36004
+
36005
+ <label class="pptx-chart-card__row">
36006
+ <span class="pptx-chart-card__label">Position</span>
36007
+ <select
36008
+ class="pptx-chart-card__input"
36009
+ [disabled]="!canEdit()"
36010
+ [value]="labels().position ?? ''"
36011
+ (change)="onPosition($event)"
36012
+ >
36013
+ @for (opt of positionOptions; track opt.value) {
36014
+ <option [value]="opt.value">{{ opt.label }}</option>
36015
+ }
36016
+ </select>
36017
+ </label>
36018
+ </div>
36019
+ </section>
36020
+ }
36021
+ `, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"] }]
36022
+ }], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], elementChange: [{ type: i0.Output, args: ["elementChange"] }] } });
36023
+
36024
+ /**
36025
+ * chart-datapoint-options.component.ts: Per-data-point fill + slice explosion.
36026
+ *
36027
+ * Selector: `pptx-chart-datapoint-options`
36028
+ *
36029
+ * Mirrors React's `ChartDataPointOptions.tsx`: a series picker (per-point edits
36030
+ * target one series at a time) and, per category, a fill-colour override (with a
36031
+ * clear button) plus a pie/doughnut slice-explosion amount for the chart types
36032
+ * that support it. Routes through `setDataPointFill` / `setDataPointExplosion`
36033
+ * and emits a new `ChartPptxElement`.
36034
+ *
36035
+ * @module angular-viewer/chart-datapoint-options
36036
+ */
36037
+ class ChartDatapointOptionsComponent {
36038
+ element = input.required(/* @ts-ignore */
36039
+ ...(ngDevMode ? [{ debugName: "element" }] : /* istanbul ignore next */ []));
36040
+ canEdit = input(true, /* @ts-ignore */
36041
+ ...(ngDevMode ? [{ debugName: "canEdit" }] : /* istanbul ignore next */ []));
36042
+ elementChange = output();
36043
+ seriesIndex = signal(0, /* @ts-ignore */
36044
+ ...(ngDevMode ? [{ debugName: "seriesIndex" }] : /* istanbul ignore next */ []));
36045
+ series = computed(() => this.element().chartData?.series ?? [], /* @ts-ignore */
36046
+ ...(ngDevMode ? [{ debugName: "series" }] : /* istanbul ignore next */ []));
36047
+ categories = computed(() => this.element().chartData?.categories ?? [], /* @ts-ignore */
36048
+ ...(ngDevMode ? [{ debugName: "categories" }] : /* istanbul ignore next */ []));
36049
+ /** Clamp the chosen series index to the current series count. */
36050
+ activeIndex = computed(() => Math.min(this.seriesIndex(), Math.max(0, this.series().length - 1)), /* @ts-ignore */
36051
+ ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
36052
+ showExplosion = computed(() => {
36053
+ const type = this.element().chartData?.chartType;
36054
+ return type !== undefined && EXPLOSION_SUPPORTED_TYPES.has(type);
36055
+ }, /* @ts-ignore */
36056
+ ...(ngDevMode ? [{ debugName: "showExplosion" }] : /* istanbul ignore next */ []));
36057
+ pointAt(pointIndex) {
36058
+ return this.series()[this.activeIndex()]?.dataPoints?.find((p) => p.idx === pointIndex);
36059
+ }
36060
+ pointFill(pointIndex) {
36061
+ const active = this.series()[this.activeIndex()];
36062
+ return this.pointAt(pointIndex)?.spPr?.fillColor ?? active?.color ?? '#4472c4';
36063
+ }
36064
+ onSeries(event) {
36065
+ const value = selectValue$1(event);
36066
+ if (value === null) {
36067
+ return;
36068
+ }
36069
+ const num = Number.parseInt(value, 10);
36070
+ if (Number.isFinite(num)) {
36071
+ this.seriesIndex.set(num);
36072
+ }
36073
+ }
36074
+ onFill(pointIndex, event) {
36075
+ const value = stringFromEvent$5(event);
36076
+ if (value === null) {
36077
+ return;
36078
+ }
36079
+ this.elementChange.emit(setDataPointFill(this.element(), this.activeIndex(), pointIndex, value));
36080
+ }
36081
+ onClearFill(pointIndex) {
36082
+ this.elementChange.emit(setDataPointFill(this.element(), this.activeIndex(), pointIndex, null));
36083
+ }
36084
+ onExplosion(pointIndex, event) {
36085
+ const num = numFromEvent(event);
36086
+ if (num === undefined) {
36087
+ return;
36088
+ }
36089
+ this.elementChange.emit(setDataPointExplosion(this.element(), this.activeIndex(), pointIndex, num));
36090
+ }
36091
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartDatapointOptionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
36092
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ChartDatapointOptionsComponent, isStandalone: true, selector: "pptx-chart-datapoint-options", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, canEdit: { classPropertyName: "canEdit", publicName: "canEdit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { elementChange: "elementChange" }, ngImport: i0, template: `
36093
+ @if (categories().length > 0 && series().length > 0) {
36094
+ <section class="pptx-chart-card" aria-label="Data point options">
36095
+ <h4 class="pptx-chart-card__heading">Data Points</h4>
36096
+
36097
+ @if (series().length > 1) {
36098
+ <label class="pptx-chart-card__row">
36099
+ <span class="pptx-chart-card__label">Series</span>
36100
+ <select
36101
+ class="pptx-chart-card__input"
36102
+ [disabled]="!canEdit()"
36103
+ [value]="activeIndex()"
36104
+ (change)="onSeries($event)"
36105
+ >
36106
+ @for (s of series(); track $index; let i = $index) {
36107
+ <option [value]="i">{{ s.name }}</option>
36108
+ }
36109
+ </select>
36110
+ </label>
36111
+ }
36112
+
36113
+ <div class="pptx-chart-card__group">
36114
+ @for (cat of categories(); track $index; let ci = $index) {
36115
+ <div class="pptx-chart-card__row">
36116
+ <span class="pptx-chart-card__name" [title]="cat">{{ cat }}</span>
36117
+ <input
36118
+ type="color"
36119
+ class="pptx-chart-card__color"
36120
+ title="Point fill"
36121
+ [disabled]="!canEdit()"
36122
+ [value]="pointFill(ci)"
36123
+ (input)="onFill(ci, $event)"
36124
+ />
36125
+ @if (pointAt(ci)?.spPr?.fillColor) {
36126
+ <button
36127
+ type="button"
36128
+ class="pptx-chart-card__clear"
36129
+ title="Clear point fill"
36130
+ [disabled]="!canEdit()"
36131
+ (click)="onClearFill(ci)"
36132
+ >
36133
+ &times;
36134
+ </button>
36135
+ }
36136
+ @if (showExplosion()) {
36137
+ <input
36138
+ type="number"
36139
+ min="0"
36140
+ max="100"
36141
+ class="pptx-chart-card__input pptx-chart-card__input--num"
36142
+ title="Slice explosion"
36143
+ placeholder="0"
36144
+ [disabled]="!canEdit()"
36145
+ [value]="pointAt(ci)?.explosion ?? ''"
36146
+ (change)="onExplosion(ci, $event)"
36147
+ />
36148
+ }
36149
+ </div>
36150
+ }
36151
+ </div>
36152
+ </section>
36153
+ }
36154
+ `, isInline: true, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
36155
+ }
36156
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartDatapointOptionsComponent, decorators: [{
36157
+ type: Component,
36158
+ args: [{ selector: 'pptx-chart-datapoint-options', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
36159
+ @if (categories().length > 0 && series().length > 0) {
36160
+ <section class="pptx-chart-card" aria-label="Data point options">
36161
+ <h4 class="pptx-chart-card__heading">Data Points</h4>
36162
+
36163
+ @if (series().length > 1) {
36164
+ <label class="pptx-chart-card__row">
36165
+ <span class="pptx-chart-card__label">Series</span>
36166
+ <select
36167
+ class="pptx-chart-card__input"
36168
+ [disabled]="!canEdit()"
36169
+ [value]="activeIndex()"
36170
+ (change)="onSeries($event)"
36171
+ >
36172
+ @for (s of series(); track $index; let i = $index) {
36173
+ <option [value]="i">{{ s.name }}</option>
36174
+ }
36175
+ </select>
36176
+ </label>
36177
+ }
36178
+
36179
+ <div class="pptx-chart-card__group">
36180
+ @for (cat of categories(); track $index; let ci = $index) {
36181
+ <div class="pptx-chart-card__row">
36182
+ <span class="pptx-chart-card__name" [title]="cat">{{ cat }}</span>
36183
+ <input
36184
+ type="color"
36185
+ class="pptx-chart-card__color"
36186
+ title="Point fill"
36187
+ [disabled]="!canEdit()"
36188
+ [value]="pointFill(ci)"
36189
+ (input)="onFill(ci, $event)"
36190
+ />
36191
+ @if (pointAt(ci)?.spPr?.fillColor) {
36192
+ <button
36193
+ type="button"
36194
+ class="pptx-chart-card__clear"
36195
+ title="Clear point fill"
36196
+ [disabled]="!canEdit()"
36197
+ (click)="onClearFill(ci)"
36198
+ >
36199
+ &times;
36200
+ </button>
36201
+ }
36202
+ @if (showExplosion()) {
36203
+ <input
36204
+ type="number"
36205
+ min="0"
36206
+ max="100"
36207
+ class="pptx-chart-card__input pptx-chart-card__input--num"
36208
+ title="Slice explosion"
36209
+ placeholder="0"
36210
+ [disabled]="!canEdit()"
36211
+ [value]="pointAt(ci)?.explosion ?? ''"
36212
+ (change)="onExplosion(ci, $event)"
36213
+ />
36214
+ }
36215
+ </div>
36216
+ }
36217
+ </div>
36218
+ </section>
36219
+ }
36220
+ `, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"] }]
36221
+ }], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], elementChange: [{ type: i0.Output, args: ["elementChange"] }] } });
36222
+
36223
+ /**
36224
+ * chart-display-options.component.ts: Chart-level display toggles.
36225
+ *
36226
+ * Selector: `pptx-chart-display-options`
36227
+ *
36228
+ * Mirrors React's `ChartDisplayOptions.tsx`: show/hide title, legend (with
36229
+ * position), gridlines, and the data-labels master toggle. Emits a complete new
36230
+ * `ChartPptxElement` via `elementChange` after each edit; holds no mutable state.
36231
+ *
36232
+ * @module angular-viewer/chart-display-options
36233
+ */
36234
+ class ChartDisplayOptionsComponent {
36235
+ element = input.required(/* @ts-ignore */
36236
+ ...(ngDevMode ? [{ debugName: "element" }] : /* istanbul ignore next */ []));
36237
+ canEdit = input(true, /* @ts-ignore */
36238
+ ...(ngDevMode ? [{ debugName: "canEdit" }] : /* istanbul ignore next */ []));
36239
+ elementChange = output();
36240
+ legendPositions = LEGEND_POSITION_OPTIONS;
36241
+ style = computed(() => this.element().chartData?.style ?? {}, /* @ts-ignore */
36242
+ ...(ngDevMode ? [{ debugName: "style" }] : /* istanbul ignore next */ []));
36243
+ onToggleTitle(event) {
36244
+ this.elementChange.emit(patchChartStyle(this.element(), { hasTitle: boolFromEvent(event) }));
36245
+ }
36246
+ onToggleLegend(event) {
36247
+ this.elementChange.emit(setLegend(this.element(), { show: boolFromEvent(event) }));
36248
+ }
36249
+ onLegendPosition(event) {
36250
+ const value = selectValue$1(event);
36251
+ if (value === null) {
36252
+ return;
36253
+ }
36254
+ this.elementChange.emit(setLegend(this.element(), { position: value }));
36255
+ }
36256
+ onToggleGridlines(event) {
36257
+ this.elementChange.emit(patchChartStyle(this.element(), { hasGridlines: boolFromEvent(event) }));
36258
+ }
36259
+ onToggleDataLabels(event) {
36260
+ // Route through the dedicated op so content keys initialise consistently.
36261
+ this.elementChange.emit(setDataLabels(this.element(), { show: boolFromEvent(event) }));
36262
+ }
36263
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartDisplayOptionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
36264
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ChartDisplayOptionsComponent, isStandalone: true, selector: "pptx-chart-display-options", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, canEdit: { classPropertyName: "canEdit", publicName: "canEdit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { elementChange: "elementChange" }, ngImport: i0, template: `
36265
+ <section class="pptx-chart-card" aria-label="Chart display options">
36266
+ <h4 class="pptx-chart-card__heading">Display</h4>
36267
+ <div class="pptx-chart-card__group">
36268
+ <label class="pptx-chart-card__check">
36269
+ <input
36270
+ type="checkbox"
36271
+ [disabled]="!canEdit()"
36272
+ [checked]="style().hasTitle ?? false"
36273
+ (change)="onToggleTitle($event)"
36274
+ />
36275
+ <span>Show title</span>
36276
+ </label>
36277
+
36278
+ <label class="pptx-chart-card__check">
36279
+ <input
36280
+ type="checkbox"
36281
+ [disabled]="!canEdit()"
36282
+ [checked]="style().hasLegend ?? false"
36283
+ (change)="onToggleLegend($event)"
36284
+ />
36285
+ <span>Show legend</span>
36286
+ </label>
36287
+
36288
+ @if (style().hasLegend) {
36289
+ <label class="pptx-chart-card__row pptx-chart-card__group--indent">
36290
+ <span class="pptx-chart-card__label">Position</span>
36291
+ <select
36292
+ class="pptx-chart-card__input"
36293
+ [disabled]="!canEdit()"
36294
+ [value]="style().legendPosition ?? 'b'"
36295
+ (change)="onLegendPosition($event)"
36296
+ >
36297
+ @for (opt of legendPositions; track opt.value) {
36298
+ <option [value]="opt.value">{{ opt.label }}</option>
36299
+ }
36300
+ </select>
36301
+ </label>
36302
+ }
36303
+
36304
+ <label class="pptx-chart-card__check">
36305
+ <input
36306
+ type="checkbox"
36307
+ [disabled]="!canEdit()"
36308
+ [checked]="style().hasGridlines ?? false"
36309
+ (change)="onToggleGridlines($event)"
36310
+ />
36311
+ <span>Show gridlines</span>
36312
+ </label>
36313
+
36314
+ <label class="pptx-chart-card__check">
36315
+ <input
36316
+ type="checkbox"
36317
+ [disabled]="!canEdit()"
36318
+ [checked]="style().hasDataLabels ?? false"
36319
+ (change)="onToggleDataLabels($event)"
36320
+ />
36321
+ <span>Show data labels</span>
36322
+ </label>
36323
+ </div>
36324
+ </section>
36325
+ `, isInline: true, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
36326
+ }
36327
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartDisplayOptionsComponent, decorators: [{
36328
+ type: Component,
36329
+ args: [{ selector: 'pptx-chart-display-options', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
36330
+ <section class="pptx-chart-card" aria-label="Chart display options">
36331
+ <h4 class="pptx-chart-card__heading">Display</h4>
36332
+ <div class="pptx-chart-card__group">
36333
+ <label class="pptx-chart-card__check">
36334
+ <input
36335
+ type="checkbox"
36336
+ [disabled]="!canEdit()"
36337
+ [checked]="style().hasTitle ?? false"
36338
+ (change)="onToggleTitle($event)"
36339
+ />
36340
+ <span>Show title</span>
36341
+ </label>
36342
+
36343
+ <label class="pptx-chart-card__check">
36344
+ <input
36345
+ type="checkbox"
36346
+ [disabled]="!canEdit()"
36347
+ [checked]="style().hasLegend ?? false"
36348
+ (change)="onToggleLegend($event)"
36349
+ />
36350
+ <span>Show legend</span>
36351
+ </label>
36352
+
36353
+ @if (style().hasLegend) {
36354
+ <label class="pptx-chart-card__row pptx-chart-card__group--indent">
36355
+ <span class="pptx-chart-card__label">Position</span>
36356
+ <select
36357
+ class="pptx-chart-card__input"
36358
+ [disabled]="!canEdit()"
36359
+ [value]="style().legendPosition ?? 'b'"
36360
+ (change)="onLegendPosition($event)"
36361
+ >
36362
+ @for (opt of legendPositions; track opt.value) {
36363
+ <option [value]="opt.value">{{ opt.label }}</option>
36364
+ }
36365
+ </select>
36366
+ </label>
36367
+ }
36368
+
36369
+ <label class="pptx-chart-card__check">
36370
+ <input
36371
+ type="checkbox"
36372
+ [disabled]="!canEdit()"
36373
+ [checked]="style().hasGridlines ?? false"
36374
+ (change)="onToggleGridlines($event)"
36375
+ />
36376
+ <span>Show gridlines</span>
36377
+ </label>
36378
+
36379
+ <label class="pptx-chart-card__check">
36380
+ <input
36381
+ type="checkbox"
36382
+ [disabled]="!canEdit()"
36383
+ [checked]="style().hasDataLabels ?? false"
36384
+ (change)="onToggleDataLabels($event)"
36385
+ />
36386
+ <span>Show data labels</span>
36387
+ </label>
36388
+ </div>
36389
+ </section>
36390
+ `, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"] }]
36391
+ }], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], elementChange: [{ type: i0.Output, args: ["elementChange"] }] } });
36392
+
36393
+ /**
36394
+ * chart-error-bar-options.component.ts: Per-series error-bar controls.
36395
+ *
36396
+ * Selector: `pptx-chart-error-bar-options`
33748
36397
  *
33749
- * @param element - The source chart element (not mutated).
33750
- * @param seriesIndex - Zero-based series index.
33751
- * @param catIndex - Zero-based category (point) index.
33752
- * @param rawValue - The new value as a string (from an `<input type="number">`).
33753
- * @returns A new `ChartPptxElement`, or the original when the value is invalid.
36398
+ * Mirrors React's `ChartErrorBarOptions.tsx`: for bar/line/area/scatter/bubble
36399
+ * charts, each series gets an error-bar value-type selector, and (when set) a
36400
+ * direction-type selector plus a numeric amount for the value types that take
36401
+ * one. Routes through the `setSeriesErrorBars` immutable wrapper and emits a new
36402
+ * `ChartPptxElement`.
33754
36403
  *
33755
- * @example
33756
- * ```ts
33757
- * const updated = setSeriesValue(el, 0, 2, "42.5");
33758
- * ```
36404
+ * @module angular-viewer/chart-error-bar-options
33759
36405
  */
33760
- function setSeriesValue(element, seriesIndex, catIndex, rawValue) {
33761
- const chartData = element.chartData;
33762
- if (!chartData) {
33763
- return element;
36406
+ class ChartErrorBarOptionsComponent {
36407
+ element = input.required(/* @ts-ignore */
36408
+ ...(ngDevMode ? [{ debugName: "element" }] : /* istanbul ignore next */ []));
36409
+ canEdit = input(true, /* @ts-ignore */
36410
+ ...(ngDevMode ? [{ debugName: "canEdit" }] : /* istanbul ignore next */ []));
36411
+ elementChange = output();
36412
+ valTypeOptions = ERROR_BAR_VALTYPE_OPTIONS;
36413
+ barTypeOptions = ERROR_BAR_TYPE_OPTIONS;
36414
+ series = computed(() => this.element().chartData?.series ?? [], /* @ts-ignore */
36415
+ ...(ngDevMode ? [{ debugName: "series" }] : /* istanbul ignore next */ []));
36416
+ supported = computed(() => {
36417
+ const type = this.element().chartData?.chartType;
36418
+ return type !== undefined && ERROR_BAR_SUPPORTED_TYPES.has(type) && this.series().length > 0;
36419
+ }, /* @ts-ignore */
36420
+ ...(ngDevMode ? [{ debugName: "supported" }] : /* istanbul ignore next */ []));
36421
+ barsOf(s) {
36422
+ return s.errBars?.[0];
33764
36423
  }
33765
- const num = parseFloat(rawValue);
33766
- if (!Number.isFinite(num)) {
33767
- return element;
36424
+ showValue(bars) {
36425
+ return ERROR_BAR_VALUE_TYPES.has(bars.valType);
33768
36426
  }
33769
- return { ...element, chartData: chartDataUpdatePoint(chartData, seriesIndex, catIndex, num) };
33770
- }
33771
- // ---------------------------------------------------------------------------
33772
- // setSeriesName
33773
- // ---------------------------------------------------------------------------
33774
- /**
33775
- * Rename a series in a `ChartPptxElement`, returning a new element.
33776
- *
33777
- * @param element - The source chart element (not mutated).
33778
- * @param seriesIndex - Zero-based index of the series to rename.
33779
- * @param name - The new series name.
33780
- * @returns A new `ChartPptxElement`.
33781
- *
33782
- * @example
33783
- * ```ts
33784
- * const updated = setSeriesName(el, 0, "Revenue");
33785
- * ```
33786
- */
33787
- function setSeriesName(element, seriesIndex, name) {
33788
- const chartData = element.chartData;
33789
- if (!chartData) {
33790
- return element;
36427
+ onValType(index, s, event) {
36428
+ const value = selectValue$1(event);
36429
+ if (value === null) {
36430
+ return;
36431
+ }
36432
+ if (value === '') {
36433
+ this.elementChange.emit(setSeriesErrorBars(this.element(), index, null));
36434
+ return;
36435
+ }
36436
+ const existing = this.barsOf(s);
36437
+ this.elementChange.emit(setSeriesErrorBars(this.element(), index, {
36438
+ direction: existing?.direction ?? 'y',
36439
+ barType: existing?.barType ?? 'both',
36440
+ valType: value,
36441
+ val: existing?.val,
36442
+ }));
33791
36443
  }
33792
- const series = chartData.series.map((s, i) => (i === seriesIndex ? { ...s, name } : s));
33793
- return { ...element, chartData: { ...chartData, series } };
36444
+ onBarType(index, bars, event) {
36445
+ const value = selectValue$1(event);
36446
+ if (value === null) {
36447
+ return;
36448
+ }
36449
+ this.elementChange.emit(setSeriesErrorBars(this.element(), index, {
36450
+ ...bars,
36451
+ barType: value,
36452
+ }));
36453
+ }
36454
+ onValue(index, bars, event) {
36455
+ const num = numFromEvent(event);
36456
+ if (num === undefined) {
36457
+ return;
36458
+ }
36459
+ this.elementChange.emit(setSeriesErrorBars(this.element(), index, { ...bars, val: num ?? undefined }));
36460
+ }
36461
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartErrorBarOptionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
36462
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ChartErrorBarOptionsComponent, isStandalone: true, selector: "pptx-chart-error-bar-options", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, canEdit: { classPropertyName: "canEdit", publicName: "canEdit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { elementChange: "elementChange" }, ngImport: i0, template: `
36463
+ @if (supported()) {
36464
+ <section class="pptx-chart-card" aria-label="Error bar options">
36465
+ <h4 class="pptx-chart-card__heading">Error Bars</h4>
36466
+ @for (s of series(); track $index; let i = $index) {
36467
+ <div class="pptx-chart-card__group">
36468
+ <div class="pptx-chart-card__row">
36469
+ <span class="pptx-chart-card__name" [title]="s.name">{{ s.name }}</span>
36470
+ <select
36471
+ class="pptx-chart-card__input"
36472
+ [disabled]="!canEdit()"
36473
+ [value]="barsOf(s)?.valType ?? ''"
36474
+ (change)="onValType(i, s, $event)"
36475
+ >
36476
+ @for (opt of valTypeOptions; track opt.value) {
36477
+ <option [value]="opt.value">{{ opt.label }}</option>
36478
+ }
36479
+ </select>
36480
+ </div>
36481
+
36482
+ @if (barsOf(s); as bars) {
36483
+ <div class="pptx-chart-card__row pptx-chart-card__group--indent">
36484
+ <select
36485
+ class="pptx-chart-card__input"
36486
+ [disabled]="!canEdit()"
36487
+ [value]="bars.barType"
36488
+ (change)="onBarType(i, bars, $event)"
36489
+ >
36490
+ @for (opt of barTypeOptions; track opt.value) {
36491
+ <option [value]="opt.value">{{ opt.label }}</option>
36492
+ }
36493
+ </select>
36494
+ @if (showValue(bars)) {
36495
+ <input
36496
+ type="number"
36497
+ class="pptx-chart-card__input pptx-chart-card__input--num"
36498
+ placeholder="Amount"
36499
+ [disabled]="!canEdit()"
36500
+ [value]="bars.val ?? ''"
36501
+ (change)="onValue(i, bars, $event)"
36502
+ />
36503
+ }
36504
+ </div>
36505
+ }
36506
+ </div>
36507
+ }
36508
+ </section>
36509
+ }
36510
+ `, isInline: true, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
33794
36511
  }
33795
- // ---------------------------------------------------------------------------
33796
- // setCategoryLabel
33797
- // ---------------------------------------------------------------------------
36512
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartErrorBarOptionsComponent, decorators: [{
36513
+ type: Component,
36514
+ args: [{ selector: 'pptx-chart-error-bar-options', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
36515
+ @if (supported()) {
36516
+ <section class="pptx-chart-card" aria-label="Error bar options">
36517
+ <h4 class="pptx-chart-card__heading">Error Bars</h4>
36518
+ @for (s of series(); track $index; let i = $index) {
36519
+ <div class="pptx-chart-card__group">
36520
+ <div class="pptx-chart-card__row">
36521
+ <span class="pptx-chart-card__name" [title]="s.name">{{ s.name }}</span>
36522
+ <select
36523
+ class="pptx-chart-card__input"
36524
+ [disabled]="!canEdit()"
36525
+ [value]="barsOf(s)?.valType ?? ''"
36526
+ (change)="onValType(i, s, $event)"
36527
+ >
36528
+ @for (opt of valTypeOptions; track opt.value) {
36529
+ <option [value]="opt.value">{{ opt.label }}</option>
36530
+ }
36531
+ </select>
36532
+ </div>
36533
+
36534
+ @if (barsOf(s); as bars) {
36535
+ <div class="pptx-chart-card__row pptx-chart-card__group--indent">
36536
+ <select
36537
+ class="pptx-chart-card__input"
36538
+ [disabled]="!canEdit()"
36539
+ [value]="bars.barType"
36540
+ (change)="onBarType(i, bars, $event)"
36541
+ >
36542
+ @for (opt of barTypeOptions; track opt.value) {
36543
+ <option [value]="opt.value">{{ opt.label }}</option>
36544
+ }
36545
+ </select>
36546
+ @if (showValue(bars)) {
36547
+ <input
36548
+ type="number"
36549
+ class="pptx-chart-card__input pptx-chart-card__input--num"
36550
+ placeholder="Amount"
36551
+ [disabled]="!canEdit()"
36552
+ [value]="bars.val ?? ''"
36553
+ (change)="onValue(i, bars, $event)"
36554
+ />
36555
+ }
36556
+ </div>
36557
+ }
36558
+ </div>
36559
+ }
36560
+ </section>
36561
+ }
36562
+ `, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"] }]
36563
+ }], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], elementChange: [{ type: i0.Output, args: ["elementChange"] }] } });
36564
+
33798
36565
  /**
33799
- * Rename a category label in a `ChartPptxElement`, returning a new element.
36566
+ * chart-marker-options.component.ts: Per-series marker controls.
33800
36567
  *
33801
- * @param element - The source chart element (not mutated).
33802
- * @param catIndex - Zero-based index of the category to rename.
33803
- * @param label - The new label text.
33804
- * @returns A new `ChartPptxElement`.
36568
+ * Selector: `pptx-chart-marker-options`
33805
36569
  *
33806
- * @example
33807
- * ```ts
33808
- * const updated = setCategoryLabel(el, 0, "Q1 2025");
33809
- * ```
36570
+ * Mirrors React's `ChartMarkerOptions.tsx`: for line/scatter/bubble/radar charts,
36571
+ * each series gets a marker-symbol selector and (when a visible symbol is chosen)
36572
+ * size and fill-colour controls. Routes through `setSeriesMarker` and emits a new
36573
+ * `ChartPptxElement`.
36574
+ *
36575
+ * @module angular-viewer/chart-marker-options
33810
36576
  */
33811
- function setCategoryLabel(element, catIndex, label) {
33812
- const chartData = element.chartData;
33813
- if (!chartData) {
33814
- return element;
36577
+ class ChartMarkerOptionsComponent {
36578
+ element = input.required(/* @ts-ignore */
36579
+ ...(ngDevMode ? [{ debugName: "element" }] : /* istanbul ignore next */ []));
36580
+ canEdit = input(true, /* @ts-ignore */
36581
+ ...(ngDevMode ? [{ debugName: "canEdit" }] : /* istanbul ignore next */ []));
36582
+ elementChange = output();
36583
+ symbolOptions = MARKER_SYMBOL_OPTIONS;
36584
+ series = computed(() => this.element().chartData?.series ?? [], /* @ts-ignore */
36585
+ ...(ngDevMode ? [{ debugName: "series" }] : /* istanbul ignore next */ []));
36586
+ supported = computed(() => {
36587
+ const type = this.element().chartData?.chartType;
36588
+ return type !== undefined && MARKER_SUPPORTED_TYPES.has(type) && this.series().length > 0;
36589
+ }, /* @ts-ignore */
36590
+ ...(ngDevMode ? [{ debugName: "supported" }] : /* istanbul ignore next */ []));
36591
+ onSymbol(index, event) {
36592
+ const value = selectValue$1(event);
36593
+ if (value === null) {
36594
+ return;
36595
+ }
36596
+ this.elementChange.emit(setSeriesMarker(this.element(), index, value === '' ? null : { symbol: value }));
33815
36597
  }
33816
- const categories = chartData.categories.map((c, i) => (i === catIndex ? label : c));
33817
- return { ...element, chartData: { ...chartData, categories } };
36598
+ onSize(index, event) {
36599
+ const num = numFromEvent(event);
36600
+ this.elementChange.emit(setSeriesMarker(this.element(), index, { size: typeof num === 'number' ? num : undefined }));
36601
+ }
36602
+ onFill(index, event) {
36603
+ const value = stringFromEvent$5(event);
36604
+ if (value === null) {
36605
+ return;
36606
+ }
36607
+ this.elementChange.emit(setSeriesMarker(this.element(), index, { fillColor: value }));
36608
+ }
36609
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartMarkerOptionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
36610
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ChartMarkerOptionsComponent, isStandalone: true, selector: "pptx-chart-marker-options", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, canEdit: { classPropertyName: "canEdit", publicName: "canEdit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { elementChange: "elementChange" }, ngImport: i0, template: `
36611
+ @if (supported()) {
36612
+ <section class="pptx-chart-card" aria-label="Marker options">
36613
+ <h4 class="pptx-chart-card__heading">Markers</h4>
36614
+ @for (s of series(); track $index; let i = $index) {
36615
+ <div class="pptx-chart-card__group">
36616
+ <div class="pptx-chart-card__row">
36617
+ <span class="pptx-chart-card__name" [title]="s.name">{{ s.name }}</span>
36618
+ <select
36619
+ class="pptx-chart-card__input"
36620
+ [disabled]="!canEdit()"
36621
+ [value]="s.marker?.symbol ?? ''"
36622
+ (change)="onSymbol(i, $event)"
36623
+ >
36624
+ @for (opt of symbolOptions; track opt.value) {
36625
+ <option [value]="opt.value">{{ opt.label }}</option>
36626
+ }
36627
+ </select>
36628
+ </div>
36629
+
36630
+ @if (s.marker && s.marker.symbol !== 'none') {
36631
+ <div class="pptx-chart-card__row pptx-chart-card__group--indent">
36632
+ <span class="pptx-chart-card__label">Size</span>
36633
+ <input
36634
+ type="number"
36635
+ min="2"
36636
+ max="72"
36637
+ class="pptx-chart-card__input pptx-chart-card__input--num"
36638
+ placeholder="Auto"
36639
+ [disabled]="!canEdit()"
36640
+ [value]="s.marker.size ?? ''"
36641
+ (change)="onSize(i, $event)"
36642
+ />
36643
+ <span class="pptx-chart-card__label">Fill</span>
36644
+ <input
36645
+ type="color"
36646
+ class="pptx-chart-card__color"
36647
+ [disabled]="!canEdit()"
36648
+ [value]="s.marker.spPr?.fillColor ?? '#4472c4'"
36649
+ (input)="onFill(i, $event)"
36650
+ />
36651
+ </div>
36652
+ }
36653
+ </div>
36654
+ }
36655
+ </section>
36656
+ }
36657
+ `, isInline: true, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
33818
36658
  }
33819
- // ---------------------------------------------------------------------------
33820
- // setSeriesColor
33821
- // ---------------------------------------------------------------------------
36659
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartMarkerOptionsComponent, decorators: [{
36660
+ type: Component,
36661
+ args: [{ selector: 'pptx-chart-marker-options', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
36662
+ @if (supported()) {
36663
+ <section class="pptx-chart-card" aria-label="Marker options">
36664
+ <h4 class="pptx-chart-card__heading">Markers</h4>
36665
+ @for (s of series(); track $index; let i = $index) {
36666
+ <div class="pptx-chart-card__group">
36667
+ <div class="pptx-chart-card__row">
36668
+ <span class="pptx-chart-card__name" [title]="s.name">{{ s.name }}</span>
36669
+ <select
36670
+ class="pptx-chart-card__input"
36671
+ [disabled]="!canEdit()"
36672
+ [value]="s.marker?.symbol ?? ''"
36673
+ (change)="onSymbol(i, $event)"
36674
+ >
36675
+ @for (opt of symbolOptions; track opt.value) {
36676
+ <option [value]="opt.value">{{ opt.label }}</option>
36677
+ }
36678
+ </select>
36679
+ </div>
36680
+
36681
+ @if (s.marker && s.marker.symbol !== 'none') {
36682
+ <div class="pptx-chart-card__row pptx-chart-card__group--indent">
36683
+ <span class="pptx-chart-card__label">Size</span>
36684
+ <input
36685
+ type="number"
36686
+ min="2"
36687
+ max="72"
36688
+ class="pptx-chart-card__input pptx-chart-card__input--num"
36689
+ placeholder="Auto"
36690
+ [disabled]="!canEdit()"
36691
+ [value]="s.marker.size ?? ''"
36692
+ (change)="onSize(i, $event)"
36693
+ />
36694
+ <span class="pptx-chart-card__label">Fill</span>
36695
+ <input
36696
+ type="color"
36697
+ class="pptx-chart-card__color"
36698
+ [disabled]="!canEdit()"
36699
+ [value]="s.marker.spPr?.fillColor ?? '#4472c4'"
36700
+ (input)="onFill(i, $event)"
36701
+ />
36702
+ </div>
36703
+ }
36704
+ </div>
36705
+ }
36706
+ </section>
36707
+ }
36708
+ `, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"] }]
36709
+ }], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], elementChange: [{ type: i0.Output, args: ["elementChange"] }] } });
36710
+
33822
36711
  /**
33823
- * Set (or clear) the solid fill colour of a chart series, returning a new
33824
- * `ChartPptxElement`. Pass a hex string (`#RRGGBB` or `RRGGBB`) to set the
33825
- * colour, or `null` to clear it so the series falls back to its theme colour.
36712
+ * chart-trendline-options.component.ts: Per-series trendline controls.
33826
36713
  *
33827
- * Mirrors the headless `setChartSeriesColor` SDK op (which mutates in place)
33828
- * with an immutable element-level wrapper for the inspector.
36714
+ * Selector: `pptx-chart-trendline-options`
33829
36715
  *
33830
- * @param element - The source chart element (not mutated).
33831
- * @param seriesIndex - Zero-based index of the series to recolour.
33832
- * @param color - Hex colour string, or `null` to clear.
33833
- * @returns A new `ChartPptxElement`.
36716
+ * Mirrors React's `ChartTrendlineOptions.tsx`: for bar/line/area/scatter/bubble
36717
+ * charts, each series gets a trendline-type selector plus (when a trendline is
36718
+ * set) display-equation and display-R-squared toggles. Routes through the
36719
+ * `setSeriesTrendline` immutable wrapper and emits a new `ChartPptxElement`.
33834
36720
  *
33835
- * @example
33836
- * ```ts
33837
- * const updated = setSeriesColor(el, 0, "#4472C4");
33838
- * ```
36721
+ * @module angular-viewer/chart-trendline-options
33839
36722
  */
33840
- function setSeriesColor(element, seriesIndex, color) {
33841
- const chartData = element.chartData;
33842
- if (!chartData) {
33843
- return element;
36723
+ class ChartTrendlineOptionsComponent {
36724
+ element = input.required(/* @ts-ignore */
36725
+ ...(ngDevMode ? [{ debugName: "element" }] : /* istanbul ignore next */ []));
36726
+ canEdit = input(true, /* @ts-ignore */
36727
+ ...(ngDevMode ? [{ debugName: "canEdit" }] : /* istanbul ignore next */ []));
36728
+ elementChange = output();
36729
+ typeOptions = TRENDLINE_TYPE_OPTIONS;
36730
+ series = computed(() => this.element().chartData?.series ?? [], /* @ts-ignore */
36731
+ ...(ngDevMode ? [{ debugName: "series" }] : /* istanbul ignore next */ []));
36732
+ supported = computed(() => {
36733
+ const type = this.element().chartData?.chartType;
36734
+ return type !== undefined && TRENDLINE_SUPPORTED_TYPES.has(type) && this.series().length > 0;
36735
+ }, /* @ts-ignore */
36736
+ ...(ngDevMode ? [{ debugName: "supported" }] : /* istanbul ignore next */ []));
36737
+ trendlineOf(s) {
36738
+ return s.trendlines?.[0];
33844
36739
  }
33845
- const normalized = color ? normalizeHex(color) : undefined;
33846
- const series = chartData.series.map((s, i) => (i === seriesIndex ? { ...s, color: normalized } : s));
33847
- return { ...element, chartData: { ...chartData, series } };
33848
- }
33849
- /** Normalise a hex colour to a `#`-prefixed form, trimming whitespace. */
33850
- function normalizeHex(color) {
33851
- const trimmed = color.trim();
33852
- return trimmed.startsWith('#') ? trimmed : `#${trimmed}`;
33853
- }
33854
- // ---------------------------------------------------------------------------
33855
- // patchChartStyle
33856
- // ---------------------------------------------------------------------------
33857
- /**
33858
- * Merge a `Partial<PptxChartStyle>` patch into a `ChartPptxElement`,
33859
- * returning a new element.
33860
- *
33861
- * @param element - The source chart element (not mutated).
33862
- * @param patch - Style fields to merge.
33863
- * @returns A new `ChartPptxElement`.
33864
- */
33865
- function patchChartStyle(element, patch) {
33866
- const chartData = element.chartData;
33867
- if (!chartData) {
33868
- return element;
36740
+ onType(index, s, event) {
36741
+ const value = selectValue$1(event);
36742
+ if (value === null) {
36743
+ return;
36744
+ }
36745
+ if (value === '') {
36746
+ this.elementChange.emit(setSeriesTrendline(this.element(), index, null));
36747
+ return;
36748
+ }
36749
+ const existing = this.trendlineOf(s);
36750
+ this.elementChange.emit(setSeriesTrendline(this.element(), index, {
36751
+ ...existing,
36752
+ trendlineType: value,
36753
+ }));
33869
36754
  }
33870
- return {
33871
- ...element,
33872
- chartData: { ...chartData, style: { ...chartData.style, ...patch } },
33873
- };
36755
+ onToggleEq(index, tl, event) {
36756
+ this.elementChange.emit(setSeriesTrendline(this.element(), index, { ...tl, displayEq: boolFromEvent(event) }));
36757
+ }
36758
+ onToggleRSq(index, tl, event) {
36759
+ this.elementChange.emit(setSeriesTrendline(this.element(), index, { ...tl, displayRSq: boolFromEvent(event) }));
36760
+ }
36761
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartTrendlineOptionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
36762
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ChartTrendlineOptionsComponent, isStandalone: true, selector: "pptx-chart-trendline-options", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, canEdit: { classPropertyName: "canEdit", publicName: "canEdit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { elementChange: "elementChange" }, ngImport: i0, template: `
36763
+ @if (supported()) {
36764
+ <section class="pptx-chart-card" aria-label="Trendline options">
36765
+ <h4 class="pptx-chart-card__heading">Trendlines</h4>
36766
+ @for (s of series(); track $index; let i = $index) {
36767
+ <div class="pptx-chart-card__group">
36768
+ <div class="pptx-chart-card__row">
36769
+ <span class="pptx-chart-card__name" [title]="s.name">{{ s.name }}</span>
36770
+ <select
36771
+ class="pptx-chart-card__input"
36772
+ [disabled]="!canEdit()"
36773
+ [value]="trendlineOf(s)?.trendlineType ?? ''"
36774
+ (change)="onType(i, s, $event)"
36775
+ >
36776
+ @for (opt of typeOptions; track opt.value) {
36777
+ <option [value]="opt.value">{{ opt.label }}</option>
36778
+ }
36779
+ </select>
36780
+ </div>
36781
+
36782
+ @if (trendlineOf(s); as tl) {
36783
+ <div class="pptx-chart-card__row pptx-chart-card__group--indent">
36784
+ <label class="pptx-chart-card__check">
36785
+ <input
36786
+ type="checkbox"
36787
+ [disabled]="!canEdit()"
36788
+ [checked]="tl.displayEq ?? false"
36789
+ (change)="onToggleEq(i, tl, $event)"
36790
+ />
36791
+ <span>Equation</span>
36792
+ </label>
36793
+ <label class="pptx-chart-card__check">
36794
+ <input
36795
+ type="checkbox"
36796
+ [disabled]="!canEdit()"
36797
+ [checked]="tl.displayRSq ?? false"
36798
+ (change)="onToggleRSq(i, tl, $event)"
36799
+ />
36800
+ <span>R-squared</span>
36801
+ </label>
36802
+ </div>
36803
+ }
36804
+ </div>
36805
+ }
36806
+ </section>
36807
+ }
36808
+ `, isInline: true, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
33874
36809
  }
33875
- // ---------------------------------------------------------------------------
33876
- // patchChartData
33877
- // ---------------------------------------------------------------------------
36810
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartTrendlineOptionsComponent, decorators: [{
36811
+ type: Component,
36812
+ args: [{ selector: 'pptx-chart-trendline-options', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
36813
+ @if (supported()) {
36814
+ <section class="pptx-chart-card" aria-label="Trendline options">
36815
+ <h4 class="pptx-chart-card__heading">Trendlines</h4>
36816
+ @for (s of series(); track $index; let i = $index) {
36817
+ <div class="pptx-chart-card__group">
36818
+ <div class="pptx-chart-card__row">
36819
+ <span class="pptx-chart-card__name" [title]="s.name">{{ s.name }}</span>
36820
+ <select
36821
+ class="pptx-chart-card__input"
36822
+ [disabled]="!canEdit()"
36823
+ [value]="trendlineOf(s)?.trendlineType ?? ''"
36824
+ (change)="onType(i, s, $event)"
36825
+ >
36826
+ @for (opt of typeOptions; track opt.value) {
36827
+ <option [value]="opt.value">{{ opt.label }}</option>
36828
+ }
36829
+ </select>
36830
+ </div>
36831
+
36832
+ @if (trendlineOf(s); as tl) {
36833
+ <div class="pptx-chart-card__row pptx-chart-card__group--indent">
36834
+ <label class="pptx-chart-card__check">
36835
+ <input
36836
+ type="checkbox"
36837
+ [disabled]="!canEdit()"
36838
+ [checked]="tl.displayEq ?? false"
36839
+ (change)="onToggleEq(i, tl, $event)"
36840
+ />
36841
+ <span>Equation</span>
36842
+ </label>
36843
+ <label class="pptx-chart-card__check">
36844
+ <input
36845
+ type="checkbox"
36846
+ [disabled]="!canEdit()"
36847
+ [checked]="tl.displayRSq ?? false"
36848
+ (change)="onToggleRSq(i, tl, $event)"
36849
+ />
36850
+ <span>R-squared</span>
36851
+ </label>
36852
+ </div>
36853
+ }
36854
+ </div>
36855
+ }
36856
+ </section>
36857
+ }
36858
+ `, styles: [".pptx-chart-card{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-top:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-card__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-card__group{display:flex;flex-direction:column;gap:.3rem}.pptx-chart-card__group--indent{margin-left:.5rem}.pptx-chart-card__subhead{font-size:11px;font-weight:600}.pptx-chart-card__row{display:flex;align-items:center;gap:.4rem;font-size:11px}.pptx-chart-card__label{flex:0 0 auto;width:5rem;color:var(--pptx-inspector-muted, #888)}.pptx-chart-card__label--wide{width:6.5rem}.pptx-chart-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-chart-card__check{display:flex;align-items:center;gap:.35rem;font-size:11px;cursor:pointer}.pptx-chart-card__input{flex:1 1 auto;min-width:0;box-sizing:border-box;padding:2px 4px;font-size:11px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;color:inherit;outline:none}.pptx-chart-card__input--num{flex:0 0 auto;width:4rem;text-align:right}.pptx-chart-card__input:focus{border-color:var(--pptx-inspector-active, #0078d4)}.pptx-chart-card__input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__color{flex:0 0 auto;width:26px;height:20px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-card__color:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-card__clear{flex:0 0 auto;padding:0 2px;font-size:12px;line-height:1;background:none;border:none;color:var(--pptx-inspector-muted, #888);cursor:pointer}.pptx-chart-card__clear:hover{color:var(--pptx-inspector-danger, #f47c7c)}.pptx-chart-card__input--num::-webkit-outer-spin-button,.pptx-chart-card__input--num::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n"] }]
36859
+ }], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], elementChange: [{ type: i0.Output, args: ["elementChange"] }] } });
36860
+
33878
36861
  /**
33879
- * Merge a `Partial<PptxChartData>` patch into a `ChartPptxElement`.
36862
+ * advanced-chart-editor.component.ts: Full chart inspector for the Angular
36863
+ * binding, at parity with the React `ChartDataPanel`.
33880
36864
  *
33881
- * When the patch contains a `chartType` change, the smart
33882
- * `chartDataChangeType` helper is used so grouping and category formats
33883
- * are adapted automatically.
36865
+ * Selector: `pptx-advanced-chart-editor`
33884
36866
  *
33885
- * @param element - The source chart element (not mutated).
33886
- * @param patch - Chart data fields to merge.
33887
- * @returns A new `ChartPptxElement`.
36867
+ * Composes the existing data grid (`pptx-chart-data-editor`) with the advanced
36868
+ * formatting controls: display toggles, data labels, axis scale/format, axis
36869
+ * styling, markers, combo per-series types, per-data-point overrides, trendlines,
36870
+ * and error bars. Every child emits a complete new `ChartPptxElement`, which this
36871
+ * component re-emits via its own `elementChange` output so the parent commits a
36872
+ * single history entry per edit. Holds no mutable state.
36873
+ *
36874
+ * @module angular-viewer/advanced-chart-editor
33888
36875
  */
33889
- function patchChartData(element, patch) {
33890
- const chartData = element.chartData;
33891
- if (!chartData) {
33892
- return element;
33893
- }
33894
- if (patch.chartType && patch.chartType !== chartData.chartType) {
33895
- const adapted = chartDataChangeType(chartData, patch.chartType);
33896
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
33897
- const { chartType: _ct, ...rest } = patch;
33898
- return { ...element, chartData: { ...adapted, ...rest } };
33899
- }
33900
- return { ...element, chartData: { ...chartData, ...patch } };
36876
+ class AdvancedChartEditorComponent {
36877
+ /** The chart element being edited. */
36878
+ element = input.required(/* @ts-ignore */
36879
+ ...(ngDevMode ? [{ debugName: "element" }] : /* istanbul ignore next */ []));
36880
+ /** Whether editing is enabled (read-only mode when false). */
36881
+ canEdit = input(true, /* @ts-ignore */
36882
+ ...(ngDevMode ? [{ debugName: "canEdit" }] : /* istanbul ignore next */ []));
36883
+ /** Emits the updated element after any edit operation in any child control. */
36884
+ elementChange = output();
36885
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: AdvancedChartEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
36886
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.2", type: AdvancedChartEditorComponent, isStandalone: true, selector: "pptx-advanced-chart-editor", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, canEdit: { classPropertyName: "canEdit", publicName: "canEdit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { elementChange: "elementChange" }, ngImport: i0, template: `
36887
+ <div class="pptx-advanced-chart">
36888
+ <pptx-chart-display-options
36889
+ [element]="element()"
36890
+ [canEdit]="canEdit()"
36891
+ (elementChange)="elementChange.emit($event)"
36892
+ />
36893
+ <pptx-chart-data-label-options
36894
+ [element]="element()"
36895
+ [canEdit]="canEdit()"
36896
+ (elementChange)="elementChange.emit($event)"
36897
+ />
36898
+ <pptx-chart-axis-options
36899
+ [element]="element()"
36900
+ [canEdit]="canEdit()"
36901
+ (elementChange)="elementChange.emit($event)"
36902
+ />
36903
+ <pptx-chart-axis-style-options
36904
+ [element]="element()"
36905
+ [canEdit]="canEdit()"
36906
+ (elementChange)="elementChange.emit($event)"
36907
+ />
36908
+ <pptx-chart-marker-options
36909
+ [element]="element()"
36910
+ [canEdit]="canEdit()"
36911
+ (elementChange)="elementChange.emit($event)"
36912
+ />
36913
+ <pptx-chart-combo-type-options
36914
+ [element]="element()"
36915
+ [canEdit]="canEdit()"
36916
+ (elementChange)="elementChange.emit($event)"
36917
+ />
36918
+ <pptx-chart-datapoint-options
36919
+ [element]="element()"
36920
+ [canEdit]="canEdit()"
36921
+ (elementChange)="elementChange.emit($event)"
36922
+ />
36923
+ <pptx-chart-trendline-options
36924
+ [element]="element()"
36925
+ [canEdit]="canEdit()"
36926
+ (elementChange)="elementChange.emit($event)"
36927
+ />
36928
+ <pptx-chart-error-bar-options
36929
+ [element]="element()"
36930
+ [canEdit]="canEdit()"
36931
+ (elementChange)="elementChange.emit($event)"
36932
+ />
36933
+ <pptx-chart-data-editor
36934
+ [element]="element()"
36935
+ [canEdit]="canEdit()"
36936
+ (elementChange)="elementChange.emit($event)"
36937
+ />
36938
+ </div>
36939
+ `, isInline: true, styles: [".pptx-advanced-chart{display:flex;flex-direction:column}\n"], dependencies: [{ kind: "component", type: ChartDataEditorComponent, selector: "pptx-chart-data-editor", inputs: ["element", "canEdit"], outputs: ["elementChange"] }, { kind: "component", type: ChartDisplayOptionsComponent, selector: "pptx-chart-display-options", inputs: ["element", "canEdit"], outputs: ["elementChange"] }, { kind: "component", type: ChartDataLabelOptionsComponent, selector: "pptx-chart-data-label-options", inputs: ["element", "canEdit"], outputs: ["elementChange"] }, { kind: "component", type: ChartAxisOptionsComponent, selector: "pptx-chart-axis-options", inputs: ["element", "canEdit"], outputs: ["elementChange"] }, { kind: "component", type: ChartAxisStyleOptionsComponent, selector: "pptx-chart-axis-style-options", inputs: ["element", "canEdit"], outputs: ["elementChange"] }, { kind: "component", type: ChartMarkerOptionsComponent, selector: "pptx-chart-marker-options", inputs: ["element", "canEdit"], outputs: ["elementChange"] }, { kind: "component", type: ChartComboTypeOptionsComponent, selector: "pptx-chart-combo-type-options", inputs: ["element", "canEdit"], outputs: ["elementChange"] }, { kind: "component", type: ChartDatapointOptionsComponent, selector: "pptx-chart-datapoint-options", inputs: ["element", "canEdit"], outputs: ["elementChange"] }, { kind: "component", type: ChartTrendlineOptionsComponent, selector: "pptx-chart-trendline-options", inputs: ["element", "canEdit"], outputs: ["elementChange"] }, { kind: "component", type: ChartErrorBarOptionsComponent, selector: "pptx-chart-error-bar-options", inputs: ["element", "canEdit"], outputs: ["elementChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
33901
36940
  }
36941
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: AdvancedChartEditorComponent, decorators: [{
36942
+ type: Component,
36943
+ args: [{ selector: 'pptx-advanced-chart-editor', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
36944
+ ChartDataEditorComponent,
36945
+ ChartDisplayOptionsComponent,
36946
+ ChartDataLabelOptionsComponent,
36947
+ ChartAxisOptionsComponent,
36948
+ ChartAxisStyleOptionsComponent,
36949
+ ChartMarkerOptionsComponent,
36950
+ ChartComboTypeOptionsComponent,
36951
+ ChartDatapointOptionsComponent,
36952
+ ChartTrendlineOptionsComponent,
36953
+ ChartErrorBarOptionsComponent,
36954
+ ], template: `
36955
+ <div class="pptx-advanced-chart">
36956
+ <pptx-chart-display-options
36957
+ [element]="element()"
36958
+ [canEdit]="canEdit()"
36959
+ (elementChange)="elementChange.emit($event)"
36960
+ />
36961
+ <pptx-chart-data-label-options
36962
+ [element]="element()"
36963
+ [canEdit]="canEdit()"
36964
+ (elementChange)="elementChange.emit($event)"
36965
+ />
36966
+ <pptx-chart-axis-options
36967
+ [element]="element()"
36968
+ [canEdit]="canEdit()"
36969
+ (elementChange)="elementChange.emit($event)"
36970
+ />
36971
+ <pptx-chart-axis-style-options
36972
+ [element]="element()"
36973
+ [canEdit]="canEdit()"
36974
+ (elementChange)="elementChange.emit($event)"
36975
+ />
36976
+ <pptx-chart-marker-options
36977
+ [element]="element()"
36978
+ [canEdit]="canEdit()"
36979
+ (elementChange)="elementChange.emit($event)"
36980
+ />
36981
+ <pptx-chart-combo-type-options
36982
+ [element]="element()"
36983
+ [canEdit]="canEdit()"
36984
+ (elementChange)="elementChange.emit($event)"
36985
+ />
36986
+ <pptx-chart-datapoint-options
36987
+ [element]="element()"
36988
+ [canEdit]="canEdit()"
36989
+ (elementChange)="elementChange.emit($event)"
36990
+ />
36991
+ <pptx-chart-trendline-options
36992
+ [element]="element()"
36993
+ [canEdit]="canEdit()"
36994
+ (elementChange)="elementChange.emit($event)"
36995
+ />
36996
+ <pptx-chart-error-bar-options
36997
+ [element]="element()"
36998
+ [canEdit]="canEdit()"
36999
+ (elementChange)="elementChange.emit($event)"
37000
+ />
37001
+ <pptx-chart-data-editor
37002
+ [element]="element()"
37003
+ [canEdit]="canEdit()"
37004
+ (elementChange)="elementChange.emit($event)"
37005
+ />
37006
+ </div>
37007
+ `, styles: [".pptx-advanced-chart{display:flex;flex-direction:column}\n"] }]
37008
+ }], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], elementChange: [{ type: i0.Output, args: ["elementChange"] }] } });
33902
37009
 
33903
37010
  /**
33904
37011
  * chart-data-editor.component.ts: Presentational chart data editor panel.
@@ -34152,15 +37259,20 @@ class ChartDataEditorComponent {
34152
37259
  </tbody>
34153
37260
  </table>
34154
37261
  </div>
37262
+ <pptx-advanced-chart-editor
37263
+ [element]="element()"
37264
+ [canEdit]="canEdit()"
37265
+ (elementChange)="elementChange.emit($event)"
37266
+ />
34155
37267
  } @else {
34156
37268
  <p class="pptx-chart-editor__empty">No chart data available.</p>
34157
37269
  }
34158
37270
  </section>
34159
- `, isInline: true, styles: [".pptx-chart-editor{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-bottom:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-editor__header{display:flex;align-items:center;justify-content:space-between;gap:.35rem;flex-wrap:wrap}.pptx-chart-editor__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-editor__actions{display:flex;gap:.2rem;flex-wrap:wrap}.pptx-chart-editor__btn{padding:2px 5px;font-size:10px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);color:inherit;border-radius:3px;cursor:pointer;white-space:nowrap}.pptx-chart-editor__btn:disabled{opacity:.4;cursor:not-allowed}.pptx-chart-editor__btn--danger{color:var(--pptx-inspector-danger, #f47c7c);border-color:var(--pptx-inspector-danger-border, #6b2a2a)}.pptx-chart-editor__scroll{overflow-x:auto}.pptx-chart-editor__table{border-collapse:collapse;font-size:11px;min-width:100%}.pptx-chart-editor__corner{min-width:64px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-editor__series-header{background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #333);padding:2px 3px;white-space:nowrap;min-width:80px}.pptx-chart-editor__name-input{width:72px;box-sizing:border-box;padding:2px 3px;font-size:11px;background:transparent;border:1px solid transparent;color:inherit;outline:none}.pptx-chart-editor__name-input:focus{border-color:var(--pptx-inspector-active, #0078d4);background:var(--pptx-inspector-active-bg, #1a3a5c)}.pptx-chart-editor__name-input:disabled{opacity:.6}.pptx-chart-editor__remove-btn{padding:0 2px;font-size:11px;line-height:1;background:none;border:none;color:var(--pptx-inspector-danger, #f47c7c);cursor:pointer;vertical-align:middle}.pptx-chart-editor__color-wrap{display:inline-flex;align-items:center;gap:1px;margin-left:2px;vertical-align:middle}.pptx-chart-editor__color-input{width:22px;height:18px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-editor__color-input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-editor__cat-cell{border:1px solid var(--pptx-inspector-border, #333);padding:1px;background:var(--pptx-inspector-input-bg, #2d2d2d)}.pptx-chart-editor__cat-wrap{display:flex;align-items:center;gap:1px}.pptx-chart-editor__cat-input{width:60px;box-sizing:border-box;padding:2px 3px;font-size:11px;background:transparent;border:none;color:var(--pptx-inspector-muted, #aaa);outline:none}.pptx-chart-editor__cat-input:focus{color:inherit;background:var(--pptx-inspector-active-bg, #1a3a5c)}.pptx-chart-editor__cat-input:disabled{opacity:.6}.pptx-chart-editor__value-cell{border:1px solid var(--pptx-inspector-border, #333);padding:1px}.pptx-chart-editor__value-input{width:72px;box-sizing:border-box;padding:2px 3px;font-size:11px;text-align:right;background:var(--pptx-inspector-input-bg, #2d2d2d);border:none;color:inherit;outline:none}.pptx-chart-editor__value-input:focus{background:var(--pptx-inspector-active-bg, #1a3a5c)}.pptx-chart-editor__value-input:disabled{opacity:.6}.pptx-chart-editor__value-input::-webkit-outer-spin-button,.pptx-chart-editor__value-input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.pptx-chart-editor__empty{font-size:11px;color:var(--pptx-inspector-muted, #888);margin:.25rem 0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
37271
+ `, isInline: true, styles: [".pptx-chart-editor{display:flex;flex-direction:column;gap:.35rem;padding:.5rem 0;border-bottom:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-editor__header{display:flex;align-items:center;justify-content:space-between;gap:.35rem;flex-wrap:wrap}.pptx-chart-editor__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0}.pptx-chart-editor__actions{display:flex;gap:.2rem;flex-wrap:wrap}.pptx-chart-editor__btn{padding:2px 5px;font-size:10px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);color:inherit;border-radius:3px;cursor:pointer;white-space:nowrap}.pptx-chart-editor__btn:disabled{opacity:.4;cursor:not-allowed}.pptx-chart-editor__btn--danger{color:var(--pptx-inspector-danger, #f47c7c);border-color:var(--pptx-inspector-danger-border, #6b2a2a)}.pptx-chart-editor__scroll{overflow-x:auto}.pptx-chart-editor__table{border-collapse:collapse;font-size:11px;min-width:100%}.pptx-chart-editor__corner{min-width:64px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #333)}.pptx-chart-editor__series-header{background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #333);padding:2px 3px;white-space:nowrap;min-width:80px}.pptx-chart-editor__name-input{width:72px;box-sizing:border-box;padding:2px 3px;font-size:11px;background:transparent;border:1px solid transparent;color:inherit;outline:none}.pptx-chart-editor__name-input:focus{border-color:var(--pptx-inspector-active, #0078d4);background:var(--pptx-inspector-active-bg, #1a3a5c)}.pptx-chart-editor__name-input:disabled{opacity:.6}.pptx-chart-editor__remove-btn{padding:0 2px;font-size:11px;line-height:1;background:none;border:none;color:var(--pptx-inspector-danger, #f47c7c);cursor:pointer;vertical-align:middle}.pptx-chart-editor__color-wrap{display:inline-flex;align-items:center;gap:1px;margin-left:2px;vertical-align:middle}.pptx-chart-editor__color-input{width:22px;height:18px;padding:0;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;background:transparent;cursor:pointer}.pptx-chart-editor__color-input:disabled{opacity:.6;cursor:not-allowed}.pptx-chart-editor__cat-cell{border:1px solid var(--pptx-inspector-border, #333);padding:1px;background:var(--pptx-inspector-input-bg, #2d2d2d)}.pptx-chart-editor__cat-wrap{display:flex;align-items:center;gap:1px}.pptx-chart-editor__cat-input{width:60px;box-sizing:border-box;padding:2px 3px;font-size:11px;background:transparent;border:none;color:var(--pptx-inspector-muted, #aaa);outline:none}.pptx-chart-editor__cat-input:focus{color:inherit;background:var(--pptx-inspector-active-bg, #1a3a5c)}.pptx-chart-editor__cat-input:disabled{opacity:.6}.pptx-chart-editor__value-cell{border:1px solid var(--pptx-inspector-border, #333);padding:1px}.pptx-chart-editor__value-input{width:72px;box-sizing:border-box;padding:2px 3px;font-size:11px;text-align:right;background:var(--pptx-inspector-input-bg, #2d2d2d);border:none;color:inherit;outline:none}.pptx-chart-editor__value-input:focus{background:var(--pptx-inspector-active-bg, #1a3a5c)}.pptx-chart-editor__value-input:disabled{opacity:.6}.pptx-chart-editor__value-input::-webkit-outer-spin-button,.pptx-chart-editor__value-input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.pptx-chart-editor__empty{font-size:11px;color:var(--pptx-inspector-muted, #888);margin:.25rem 0}\n"], dependencies: [{ kind: "component", type: AdvancedChartEditorComponent, selector: "pptx-advanced-chart-editor", inputs: ["element", "canEdit"], outputs: ["elementChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
34160
37272
  }
34161
37273
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ChartDataEditorComponent, decorators: [{
34162
37274
  type: Component,
34163
- args: [{ selector: 'pptx-chart-data-editor', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
37275
+ args: [{ selector: 'pptx-chart-data-editor', standalone: true, imports: [AdvancedChartEditorComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
34164
37276
  <section class="pptx-chart-editor" aria-label="Chart data editor">
34165
37277
  <header class="pptx-chart-editor__header">
34166
37278
  <h3 class="pptx-chart-editor__heading">Chart Data</h3>
@@ -34300,6 +37412,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
34300
37412
  </tbody>
34301
37413
  </table>
34302
37414
  </div>
37415
+ <pptx-advanced-chart-editor
37416
+ [element]="element()"
37417
+ [canEdit]="canEdit()"
37418
+ (elementChange)="elementChange.emit($event)"
37419
+ />
34303
37420
  } @else {
34304
37421
  <p class="pptx-chart-editor__empty">No chart data available.</p>
34305
37422
  }
@@ -48457,6 +51574,67 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
48457
51574
  `, styles: [":host{display:block;position:absolute;inset:0;overflow:hidden;pointer-events:none}.pptx-ng-transition-layer{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}\n"] }]
48458
51575
  }], ctorParameters: () => [], propDecorators: { outgoingSlide: [{ type: i0.Input, args: [{ isSignal: true, alias: "outgoingSlide", required: true }] }], canvasSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "canvasSize", required: true }] }], transition: [{ type: i0.Input, args: [{ isSignal: true, alias: "transition", required: true }] }], templateElements: [{ type: i0.Input, args: [{ isSignal: true, alias: "templateElements", required: false }] }], mediaDataUrls: [{ type: i0.Input, args: [{ isSignal: true, alias: "mediaDataUrls", required: false }] }], durationMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "durationMs", required: false }] }], complete: [{ type: i0.Output, args: ["complete"] }] } });
48459
51576
 
51577
+ /**
51578
+ * touch-gestures.ts: thin Angular adapter over the framework-agnostic
51579
+ * `createTouchGestureRecognizer` from `pptx-viewer-shared`.
51580
+ *
51581
+ * The gesture state machine itself (pinch-to-zoom, swipe, long-press) lives in
51582
+ * shared and is identical across React / Vue / Angular. This adapter only owns
51583
+ * the DOM listener attach/detach lifecycle, mirroring the React hook
51584
+ * `packages/react/src/viewer/hooks/useTouchGestures.ts`:
51585
+ * - `touchstart` / `touchmove` are registered with `{ passive: false }` so the
51586
+ * recogniser can call `preventDefault()` to suppress the browser's native
51587
+ * pinch-zoom on the canvas;
51588
+ * - `touchend` / `touchcancel` are passive (they never call preventDefault).
51589
+ *
51590
+ * `attachTouchGestures(element, config)` returns a teardown function that
51591
+ * removes every listener and cancels any pending long-press timer. Components
51592
+ * call it from `afterNextRender` (when the target node is live) and run the
51593
+ * teardown via `DestroyRef.onDestroy`.
51594
+ *
51595
+ * @module touch-gestures (angular)
51596
+ */
51597
+ /**
51598
+ * Viewer zoom bounds. The Angular viewer clamps zoom to [0.2, 3] (see
51599
+ * `ZOOM_MIN` / `ZOOM_MAX` in `power-point-viewer.component.ts`); the pinch path
51600
+ * must clamp to the same range so a pinch can never exceed the buttons.
51601
+ */
51602
+ const MIN_ZOOM_SCALE = 0.2;
51603
+ const MAX_ZOOM_SCALE = 3;
51604
+ /** Clamp a scale value to the Angular viewer's allowed zoom range. */
51605
+ function clampScale(value) {
51606
+ return clampScale$1(value, MIN_ZOOM_SCALE, MAX_ZOOM_SCALE);
51607
+ }
51608
+ /**
51609
+ * Attach the shared touch-gesture recogniser to `element` and return a teardown
51610
+ * function. Mirrors the React hook's listener lifecycle exactly:
51611
+ * `touchstart`/`touchmove` are non-passive (so pinch can `preventDefault`),
51612
+ * `touchend`/`touchcancel` are passive.
51613
+ */
51614
+ function attachTouchGestures(element, config) {
51615
+ const recognizer = createTouchGestureRecognizer({
51616
+ getScale: config.getScale,
51617
+ minScale: MIN_ZOOM_SCALE,
51618
+ maxScale: MAX_ZOOM_SCALE,
51619
+ callbacks: config.callbacks,
51620
+ });
51621
+ const onStart = (e) => recognizer.onTouchStart(e);
51622
+ const onMove = (e) => recognizer.onTouchMove(e);
51623
+ const onEnd = (e) => recognizer.onTouchEnd(e);
51624
+ const onCancel = () => recognizer.onTouchCancel();
51625
+ element.addEventListener('touchstart', onStart, { passive: false });
51626
+ element.addEventListener('touchmove', onMove, { passive: false });
51627
+ element.addEventListener('touchend', onEnd, { passive: true });
51628
+ element.addEventListener('touchcancel', onCancel, { passive: true });
51629
+ return () => {
51630
+ element.removeEventListener('touchstart', onStart);
51631
+ element.removeEventListener('touchmove', onMove);
51632
+ element.removeEventListener('touchend', onEnd);
51633
+ element.removeEventListener('touchcancel', onCancel);
51634
+ recognizer.cancel();
51635
+ };
51636
+ }
51637
+
48460
51638
  /**
48461
51639
  * PresentationOverlayComponent: full-viewport black overlay that renders
48462
51640
  * slides in presentation (kiosk) mode.
@@ -48526,7 +51704,11 @@ class PresentationOverlayComponent {
48526
51704
  /** The slide stage root; animation styles are applied to its elements. */
48527
51705
  stageRef = viewChild('stage', /* @ts-ignore */
48528
51706
  ...(ngDevMode ? [{ debugName: "stageRef" }] : /* istanbul ignore next */ []));
51707
+ /** The overlay root; the shared touch-gesture recogniser attaches here. */
51708
+ rootRef = viewChild('root', /* @ts-ignore */
51709
+ ...(ngDevMode ? [{ debugName: "rootRef" }] : /* istanbul ignore next */ []));
48529
51710
  constructor() {
51711
+ this.setupTouchGestures();
48530
51712
  // Feed the current slide's element animations into playback (resets to the
48531
51713
  // pre-build state so entrance-animated elements start hidden).
48532
51714
  effect(() => {
@@ -48690,21 +51872,36 @@ class PresentationOverlayComponent {
48690
51872
  'z-index': '10001',
48691
51873
  };
48692
51874
  // ------------------------------------------------------------------
48693
- // Touch / swipe tracking
51875
+ // Touch / swipe handling (delegated to the shared gesture recogniser)
48694
51876
  // ------------------------------------------------------------------
48695
- /** Horizontal swipe distance (px) required to trigger navigation. */
48696
- static SWIPE_THRESHOLD = 50;
48697
- /** X coordinate captured on touchstart, or null when no swipe is active. */
48698
- touchStartX = null;
48699
- touchStartY = null;
48700
51877
  /**
48701
- * Last position seen during the gesture (updated on every touchmove). Some
48702
- * touch dispatchers (e.g. CDP `Input.dispatchTouchEvent`) fire `touchEnd`
48703
- * with an empty `changedTouches` list, so we fall back to the final move
48704
- * coordinates to compute the swipe delta.
51878
+ * Wire the shared touch-gesture recogniser to the overlay root so a
51879
+ * horizontal swipe navigates: swipe left (direction -1) advances to the
51880
+ * next visible slide, swipe right (direction 1) returns to the previous,
51881
+ * matching the prior bespoke handler's semantics. Pinch is made inert
51882
+ * (equal min/max scale, no-op getScale) and there is no long-press in
51883
+ * presentation mode. Attach happens once the root node is live
51884
+ * (afterNextRender) and is torn down on destroy.
48705
51885
  */
48706
- touchLastX = null;
48707
- touchLastY = null;
51886
+ setupTouchGestures() {
51887
+ const destroyRef = inject(DestroyRef);
51888
+ afterNextRender(() => {
51889
+ const el = this.rootRef()?.nativeElement;
51890
+ if (!el) {
51891
+ return;
51892
+ }
51893
+ const teardown = attachTouchGestures(el, {
51894
+ getScale: () => 1,
51895
+ callbacks: {
51896
+ onSwipe: (direction) => {
51897
+ // direction 1 = swipe right (previous), -1 = swipe left (next).
51898
+ this.navigate(direction === 1 ? 'prev' : 'next');
51899
+ },
51900
+ },
51901
+ });
51902
+ destroyRef.onDestroy(teardown);
51903
+ });
51904
+ }
48708
51905
  // ------------------------------------------------------------------
48709
51906
  // Lifecycle
48710
51907
  // ------------------------------------------------------------------
@@ -48715,9 +51912,6 @@ class PresentationOverlayComponent {
48715
51912
  // Snapshot the viewport dimensions on mount (SSR-safe guard).
48716
51913
  this.snapViewport();
48717
51914
  }
48718
- ngOnDestroy() {
48719
- // Nothing to clean up; HostListeners are removed automatically.
48720
- }
48721
51915
  // ------------------------------------------------------------------
48722
51916
  // Resize awareness
48723
51917
  // ------------------------------------------------------------------
@@ -48821,73 +52015,6 @@ class PresentationOverlayComponent {
48821
52015
  this.navigate('next');
48822
52016
  }
48823
52017
  // ------------------------------------------------------------------
48824
- // Swipe handling (touch devices have no keyboard)
48825
- // ------------------------------------------------------------------
48826
- /** Record the initial touch position. */
48827
- onTouchStart(event) {
48828
- const touch = event.changedTouches[0];
48829
- if (!touch) {
48830
- this.touchStartX = null;
48831
- this.touchStartY = null;
48832
- this.touchLastX = null;
48833
- this.touchLastY = null;
48834
- return;
48835
- }
48836
- this.touchStartX = touch.clientX;
48837
- this.touchStartY = touch.clientY;
48838
- this.touchLastX = touch.clientX;
48839
- this.touchLastY = touch.clientY;
48840
- }
48841
- /** Track the latest gesture position so touchend can compute the delta even
48842
- * when the touchend event carries no `changedTouches`. */
48843
- onTouchMove(event) {
48844
- const touch = event.changedTouches[0] ?? event.touches[0];
48845
- if (touch) {
48846
- this.touchLastX = touch.clientX;
48847
- this.touchLastY = touch.clientY;
48848
- }
48849
- }
48850
- /**
48851
- * On touchend, treat a predominantly horizontal drag past the threshold as a
48852
- * swipe: left-swipe → next, right-swipe → prev.
48853
- */
48854
- onTouchEnd(event) {
48855
- const startX = this.touchStartX;
48856
- const startY = this.touchStartY;
48857
- const lastX = this.touchLastX;
48858
- const lastY = this.touchLastY;
48859
- this.touchStartX = null;
48860
- this.touchStartY = null;
48861
- this.touchLastX = null;
48862
- this.touchLastY = null;
48863
- if (startX === null || startY === null) {
48864
- return;
48865
- }
48866
- // Prefer the touchend coordinates; fall back to the last touchmove
48867
- // position (CDP dispatches touchEnd with an empty changedTouches list).
48868
- const touch = event.changedTouches[0];
48869
- const endX = touch ? touch.clientX : lastX;
48870
- const endY = touch ? touch.clientY : lastY;
48871
- if (endX === null || endY === null) {
48872
- return;
48873
- }
48874
- const dx = endX - startX;
48875
- const dy = endY - startY;
48876
- // Require a mostly-horizontal gesture past the threshold.
48877
- if (Math.abs(dx) < PresentationOverlayComponent.SWIPE_THRESHOLD) {
48878
- return;
48879
- }
48880
- if (Math.abs(dx) <= Math.abs(dy)) {
48881
- return;
48882
- }
48883
- if (dx < 0) {
48884
- this.navigate('next');
48885
- }
48886
- else {
48887
- this.navigate('prev');
48888
- }
48889
- }
48890
- // ------------------------------------------------------------------
48891
52018
  // Navigation helpers
48892
52019
  // ------------------------------------------------------------------
48893
52020
  navigate(direction) {
@@ -48938,13 +52065,8 @@ class PresentationOverlayComponent {
48938
52065
  this.closed.emit();
48939
52066
  }
48940
52067
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: PresentationOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
48941
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: PresentationOverlayComponent, isStandalone: true, selector: "pptx-presentation-overlay", inputs: { slides: { classPropertyName: "slides", publicName: "slides", isSignal: true, isRequired: true, transformFunction: null }, canvasSize: { classPropertyName: "canvasSize", publicName: "canvasSize", isSignal: true, isRequired: true, transformFunction: null }, mediaDataUrls: { classPropertyName: "mediaDataUrls", publicName: "mediaDataUrls", isSignal: true, isRequired: false, transformFunction: null }, startIndex: { classPropertyName: "startIndex", publicName: "startIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { indexChange: "indexChange", closed: "closed" }, host: { listeners: { "window:resize": "onWindowResize()", "document:keydown": "onKeyDown($event)" } }, providers: [AnimationPlaybackService, PresentationAnnotationsService], viewQueries: [{ propertyName: "stageRef", first: true, predicate: ["stage"], descendants: true, isSignal: true }], ngImport: i0, template: `
48942
- <div
48943
- class="pptx-ng-presentation-root"
48944
- (touchstart)="onTouchStart($event)"
48945
- (touchmove)="onTouchMove($event)"
48946
- (touchend)="onTouchEnd($event)"
48947
- >
52068
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: PresentationOverlayComponent, isStandalone: true, selector: "pptx-presentation-overlay", inputs: { slides: { classPropertyName: "slides", publicName: "slides", isSignal: true, isRequired: true, transformFunction: null }, canvasSize: { classPropertyName: "canvasSize", publicName: "canvasSize", isSignal: true, isRequired: true, transformFunction: null }, mediaDataUrls: { classPropertyName: "mediaDataUrls", publicName: "mediaDataUrls", isSignal: true, isRequired: false, transformFunction: null }, startIndex: { classPropertyName: "startIndex", publicName: "startIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { indexChange: "indexChange", closed: "closed" }, host: { listeners: { "window:resize": "onWindowResize()", "document:keydown": "onKeyDown($event)" } }, providers: [AnimationPlaybackService, PresentationAnnotationsService], viewQueries: [{ propertyName: "stageRef", first: true, predicate: ["stage"], descendants: true, isSignal: true }, { propertyName: "rootRef", first: true, predicate: ["root"], descendants: true, isSignal: true }], ngImport: i0, template: `
52069
+ <div #root class="pptx-ng-presentation-root">
48948
52070
  <!--
48949
52071
  Slide counter, rendered first in DOM (before slide content) so a
48950
52072
  generic "N / M" text query resolves to it rather than to any slide-text
@@ -49080,12 +52202,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
49080
52202
  PresentationAnnotationOverlayComponent,
49081
52203
  PresentationSubtitleBarComponent,
49082
52204
  ], providers: [AnimationPlaybackService, PresentationAnnotationsService], template: `
49083
- <div
49084
- class="pptx-ng-presentation-root"
49085
- (touchstart)="onTouchStart($event)"
49086
- (touchmove)="onTouchMove($event)"
49087
- (touchend)="onTouchEnd($event)"
49088
- >
52205
+ <div #root class="pptx-ng-presentation-root">
49089
52206
  <!--
49090
52207
  Slide counter, rendered first in DOM (before slide content) so a
49091
52208
  generic "N / M" text query resolves to it rather than to any slide-text
@@ -49211,7 +52328,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
49211
52328
  </button>
49212
52329
  </div>
49213
52330
  `, styles: [":host{display:block;position:fixed;inset:0;z-index:10000;background:#000;cursor:pointer;-webkit-user-select:none;user-select:none}.pptx-ng-presentation-root{position:absolute;inset:0;touch-action:pan-y}.pptx-ng-presentation-close:hover,.pptx-ng-presentation-nav:hover{background:#000000bf}.pptx-ng-presentation-tools{position:absolute;bottom:max(1rem,env(safe-area-inset-bottom));left:1rem;display:flex;gap:.25rem;padding:.25rem;border-radius:.5rem;background:#0000008c;z-index:80}.pptx-ng-presentation-tools button{width:2rem;height:2rem;border:none;border-radius:.35rem;background:transparent;color:#fff;font-size:1rem;cursor:pointer}.pptx-ng-presentation-tools button:hover{background:#ffffff26}.pptx-ng-presentation-tools button.is-active{background:#ffffff4d}\n"] }]
49214
- }], ctorParameters: () => [], propDecorators: { slides: [{ type: i0.Input, args: [{ isSignal: true, alias: "slides", required: true }] }], canvasSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "canvasSize", required: true }] }], mediaDataUrls: [{ type: i0.Input, args: [{ isSignal: true, alias: "mediaDataUrls", required: false }] }], startIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "startIndex", required: false }] }], indexChange: [{ type: i0.Output, args: ["indexChange"] }], closed: [{ type: i0.Output, args: ["closed"] }], stageRef: [{ type: i0.ViewChild, args: ['stage', { isSignal: true }] }], onWindowResize: [{
52331
+ }], ctorParameters: () => [], propDecorators: { slides: [{ type: i0.Input, args: [{ isSignal: true, alias: "slides", required: true }] }], canvasSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "canvasSize", required: true }] }], mediaDataUrls: [{ type: i0.Input, args: [{ isSignal: true, alias: "mediaDataUrls", required: false }] }], startIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "startIndex", required: false }] }], indexChange: [{ type: i0.Output, args: ["indexChange"] }], closed: [{ type: i0.Output, args: ["closed"] }], stageRef: [{ type: i0.ViewChild, args: ['stage', { isSignal: true }] }], rootRef: [{ type: i0.ViewChild, args: ['root', { isSignal: true }] }], onWindowResize: [{
49215
52332
  type: HostListener,
49216
52333
  args: ['window:resize']
49217
52334
  }], onKeyDown: [{
@@ -54527,67 +57644,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
54527
57644
  }]
54528
57645
  }], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], activeName: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeName", required: false }] }], applyTheme: [{ type: i0.Output, args: ["applyTheme"] }], close: [{ type: i0.Output, args: ["close"] }] } });
54529
57646
 
54530
- /**
54531
- * touch-gestures.ts: thin Angular adapter over the framework-agnostic
54532
- * `createTouchGestureRecognizer` from `pptx-viewer-shared`.
54533
- *
54534
- * The gesture state machine itself (pinch-to-zoom, swipe, long-press) lives in
54535
- * shared and is identical across React / Vue / Angular. This adapter only owns
54536
- * the DOM listener attach/detach lifecycle, mirroring the React hook
54537
- * `packages/react/src/viewer/hooks/useTouchGestures.ts`:
54538
- * - `touchstart` / `touchmove` are registered with `{ passive: false }` so the
54539
- * recogniser can call `preventDefault()` to suppress the browser's native
54540
- * pinch-zoom on the canvas;
54541
- * - `touchend` / `touchcancel` are passive (they never call preventDefault).
54542
- *
54543
- * `attachTouchGestures(element, config)` returns a teardown function that
54544
- * removes every listener and cancels any pending long-press timer. Components
54545
- * call it from `afterNextRender` (when the target node is live) and run the
54546
- * teardown via `DestroyRef.onDestroy`.
54547
- *
54548
- * @module touch-gestures (angular)
54549
- */
54550
- /**
54551
- * Viewer zoom bounds. The Angular viewer clamps zoom to [0.2, 3] (see
54552
- * `ZOOM_MIN` / `ZOOM_MAX` in `power-point-viewer.component.ts`); the pinch path
54553
- * must clamp to the same range so a pinch can never exceed the buttons.
54554
- */
54555
- const MIN_ZOOM_SCALE = 0.2;
54556
- const MAX_ZOOM_SCALE = 3;
54557
- /** Clamp a scale value to the Angular viewer's allowed zoom range. */
54558
- function clampScale(value) {
54559
- return clampScale$1(value, MIN_ZOOM_SCALE, MAX_ZOOM_SCALE);
54560
- }
54561
- /**
54562
- * Attach the shared touch-gesture recogniser to `element` and return a teardown
54563
- * function. Mirrors the React hook's listener lifecycle exactly:
54564
- * `touchstart`/`touchmove` are non-passive (so pinch can `preventDefault`),
54565
- * `touchend`/`touchcancel` are passive.
54566
- */
54567
- function attachTouchGestures(element, config) {
54568
- const recognizer = createTouchGestureRecognizer({
54569
- getScale: config.getScale,
54570
- minScale: MIN_ZOOM_SCALE,
54571
- maxScale: MAX_ZOOM_SCALE,
54572
- callbacks: config.callbacks,
54573
- });
54574
- const onStart = (e) => recognizer.onTouchStart(e);
54575
- const onMove = (e) => recognizer.onTouchMove(e);
54576
- const onEnd = (e) => recognizer.onTouchEnd(e);
54577
- const onCancel = () => recognizer.onTouchCancel();
54578
- element.addEventListener('touchstart', onStart, { passive: false });
54579
- element.addEventListener('touchmove', onMove, { passive: false });
54580
- element.addEventListener('touchend', onEnd, { passive: true });
54581
- element.addEventListener('touchcancel', onCancel, { passive: true });
54582
- return () => {
54583
- element.removeEventListener('touchstart', onStart);
54584
- element.removeEventListener('touchmove', onMove);
54585
- element.removeEventListener('touchend', onEnd);
54586
- element.removeEventListener('touchcancel', onCancel);
54587
- recognizer.cancel();
54588
- };
54589
- }
54590
-
54591
57647
  const ZOOM_STEP = 0.1;
54592
57648
  const ZOOM_MIN = 0.2;
54593
57649
  const ZOOM_MAX = 3;
@@ -57392,5 +60448,5 @@ function cn(...values) {
57392
60448
  * Generated bundle index. Do not edit.
57393
60449
  */
57394
60450
 
57395
- export { AccessibilityPanelComponent, AccessibilityService, AnimationAuthorPanelComponent, AnimationPanelComponent, AnimationPlaybackService, BroadcastDialogComponent, CURSOR_PALETTE, ChartDataEditorComponent, ChartRendererComponent, CollaborationCursorsComponent, CollaborationService, CommentsPanelComponent, CommentsService, ConnectorRendererComponent, ConnectorTextOverlayComponent, DEFAULT_BROADCAST_SERVER_URL, DEFAULT_CANVAS_HEIGHT, DEFAULT_CANVAS_WIDTH, DEFAULT_FILL_COLOR, DEFAULT_PRINT_SETTINGS, DEFAULT_SLIDE_BACKGROUND, DEFAULT_STROKE_COLOR, DEFAULT_TEXT_COLOR$1 as DEFAULT_TEXT_COLOR, EMBEDDED_FONTS_STYLE_ID, EditorContextMenuComponent, EditorHistory, EditorStateService, EditorToolbarComponent, EffectsPanelComponent, ElementRendererComponent, EmbeddedFontsService, EquationRendererComponent, ExportService, FindBarComponent, FindReplaceBarComponent, GradientPickerComponent, HANDOUT_OPTIONS, HyperlinkDialogComponent, InkRendererComponent, InspectorPanelComponent, IsMobileService, LoadContentService, MobileBottomBarComponent, MobileMenuSheetComponent, MobilePresenterViewComponent, MobileSheetComponent, MobileSlidesSheetComponent, MobileToolbarComponent, ModalDialogComponent, Model3DRendererComponent, NotesPanelComponent, OleRendererComponent, PowerPointViewerComponent, PresentationAnnotationOverlayComponent, PresentationAnnotationsService, PresentationOverlayComponent, PresentationSubtitleBarComponent, PresentationTransitionOverlayComponent, PresenterViewComponent, PrintDialogComponent, PrintService, PrintSettingsPanelComponent, PropertiesDialogComponent, RESIZE_HANDLES, SEVERITY_GROUPS, SEVERITY_LABELS, SLIDE_TRANSITION_KEYFRAMES, SVG_WARP_PRESETS, ShareDialogComponent, SignaturesPanelComponent, SignaturesService, SlideCanvasComponent, SlideSorterOverlayComponent, SlidesPanelComponent, SmartArtRendererComponent, TYPE_LABELS$1 as TYPE_LABELS, TableDataEditorComponent, TableRendererComponent, TextAdvancedPanelComponent, VIEWER_THEME, WEBM_MIME_CANDIDATES, ZoomRendererComponent, addCommentToList, advanceStep, applyFindReplacements, applyFormatToElement, applyMove, applyResize, assignUserColor, bringForward, bringToFront, buildBroadcastConfig, buildBroadcastViewerUrl, buildClearHyperlinkPatch, buildClickGroups, buildCollaborationConfig, buildCssGradientFromShapeStyle, buildDuotoneFilter, buildDuotoneFilterId, buildEmbeddedFontStyles, buildFontFaceRule, buildHyperlinkPatch, buildPatternFillCss, buildPrintHtmlDocument as buildPrintDocument, buildPropertiesPatch, buildShareUrl, bulletIndentPx, canStartBroadcast, canStartShare, canUseClipboard, clampCursorPosition, clampGifDimensions, clampNotesFontSize, clampStep, cn, collectAccessibilityIssues, collectElementText, collectSlideText, computeHandoutLayout, computeIsMobile, computeIsTablet, computePageCount, computeSlideIndices, computeTimerProgress, convertOmmlToMathMl, copyFormatFromElement, countAccessibilityIssues, defaultCssVars, defaultRadius, defaultThemeColors, deleteElementsByIds, derivePresenceList, duplicateElementById, durationOf, encodeGif, estimatePageCount, findInSlides, fontMimeForFormat, formatAutoNumber, formatCursorLabel, formatElapsed, formatPropertyDate, formatTime, fpsToFrameIntervalMs, generateBroadcastRoomId, generateCommentId, getContainerStyle, getDuotoneFilterDef, getImageSrc, getPatternSvg, getResolvedShapeClipPath, getResolvedShapeClipPathFor, getShapeFillStrokeStyle, getSlideBackgroundStyle, getSlideTransitionAnimations, getTextBlockStyle, getTextWarp, getWarpCategory, getWarpPath, groupIssuesBySeverity, hasCopyableFormat, hasExistingLink, headerLabel, isInjectableUrl, isPpactionUrl, isSigned, isUrlSafe, isValidRoomId, issueTrackKey, issueTypeLabel, moveElementBy, msToFrameDelayCs, normalizeFontFormat, normalizeSlidesPerPage, ommlToMathml, overallStatus, pendingElementStyles, pickSupportedMimeType, planGifFrames, planVideoSegments, presenceToCursors, provideViewerTheme, recordWebm, removeCommentFromList, renderToCanvas, replaceInSlides, replaceMatch, resizeElement, resolveFontVariant, resolveHyperlinkHref, resolveParagraphBullet, resolvePresenterNotes, resolveTransitionDuration, revealedElementStyles, routeOrthogonalConnector, sanitizeColor, sanitizeSlideIndex, sanitizeUserName, searchSlides, seedBroadcastFields, seedHyperlinkDraft, seedPropertiesDraft, seedShareFields, segmentFrameCount, sendBackward, sendToBack, setElementPosition, shouldUseSvgWarp, signatureCountLabel, signatureKey, signatureTimestamp, signerName, statusKind, statusLabel, themeStyle, themeToCssVars, toggleCommentResolvedInList, updateElementById, validatePrintSettings, validateRoomId, waypointsToPathD, worstStatus };
60451
+ export { AccessibilityPanelComponent, AccessibilityService, AdvancedChartEditorComponent, AnimationAuthorPanelComponent, AnimationPanelComponent, AnimationPlaybackService, BroadcastDialogComponent, CURSOR_PALETTE, ChartAxisOptionsComponent, ChartAxisStyleOptionsComponent, ChartComboTypeOptionsComponent, ChartDataEditorComponent, ChartDataLabelOptionsComponent, ChartDatapointOptionsComponent, ChartDisplayOptionsComponent, ChartErrorBarOptionsComponent, ChartMarkerOptionsComponent, ChartRendererComponent, ChartTrendlineOptionsComponent, CollaborationCursorsComponent, CollaborationService, CommentsPanelComponent, CommentsService, ConnectorRendererComponent, ConnectorTextOverlayComponent, DEFAULT_BROADCAST_SERVER_URL, DEFAULT_CANVAS_HEIGHT, DEFAULT_CANVAS_WIDTH, DEFAULT_FILL_COLOR, DEFAULT_PRINT_SETTINGS, DEFAULT_SLIDE_BACKGROUND, DEFAULT_STROKE_COLOR, DEFAULT_TEXT_COLOR$1 as DEFAULT_TEXT_COLOR, EMBEDDED_FONTS_STYLE_ID, EditorContextMenuComponent, EditorHistory, EditorStateService, EditorToolbarComponent, EffectsPanelComponent, ElementRendererComponent, EmbeddedFontsService, EquationRendererComponent, ExportService, FindBarComponent, FindReplaceBarComponent, GradientPickerComponent, HANDOUT_OPTIONS, HyperlinkDialogComponent, InkRendererComponent, InspectorPanelComponent, IsMobileService, LoadContentService, MobileBottomBarComponent, MobileMenuSheetComponent, MobilePresenterViewComponent, MobileSheetComponent, MobileSlidesSheetComponent, MobileToolbarComponent, ModalDialogComponent, Model3DRendererComponent, NotesPanelComponent, OleRendererComponent, PowerPointViewerComponent, PresentationAnnotationOverlayComponent, PresentationAnnotationsService, PresentationOverlayComponent, PresentationSubtitleBarComponent, PresentationTransitionOverlayComponent, PresenterViewComponent, PrintDialogComponent, PrintService, PrintSettingsPanelComponent, PropertiesDialogComponent, RESIZE_HANDLES, SEVERITY_GROUPS, SEVERITY_LABELS, SLIDE_TRANSITION_KEYFRAMES, SVG_WARP_PRESETS, ShareDialogComponent, SignaturesPanelComponent, SignaturesService, SlideCanvasComponent, SlideSorterOverlayComponent, SlidesPanelComponent, SmartArtRendererComponent, TYPE_LABELS$1 as TYPE_LABELS, TableDataEditorComponent, TableRendererComponent, TextAdvancedPanelComponent, VIEWER_THEME, WEBM_MIME_CANDIDATES, ZoomRendererComponent, addCommentToList, advanceStep, applyFindReplacements, applyFormatToElement, applyMove, applyResize, assignUserColor, bringForward, bringToFront, buildBroadcastConfig, buildBroadcastViewerUrl, buildClearHyperlinkPatch, buildClickGroups, buildCollaborationConfig, buildCssGradientFromShapeStyle, buildDuotoneFilter, buildDuotoneFilterId, buildEmbeddedFontStyles, buildFontFaceRule, buildHyperlinkPatch, buildPatternFillCss, buildPrintHtmlDocument as buildPrintDocument, buildPropertiesPatch, buildShareUrl, bulletIndentPx, canStartBroadcast, canStartShare, canUseClipboard, clampCursorPosition, clampGifDimensions, clampNotesFontSize, clampStep, cn, collectAccessibilityIssues, collectElementText, collectSlideText, computeHandoutLayout, computeIsMobile, computeIsTablet, computePageCount, computeSlideIndices, computeTimerProgress, convertOmmlToMathMl, copyFormatFromElement, countAccessibilityIssues, defaultCssVars, defaultRadius, defaultThemeColors, deleteElementsByIds, derivePresenceList, duplicateElementById, durationOf, encodeGif, estimatePageCount, findInSlides, fontMimeForFormat, formatAutoNumber, formatCursorLabel, formatElapsed, formatPropertyDate, formatTime, fpsToFrameIntervalMs, generateBroadcastRoomId, generateCommentId, getContainerStyle, getDuotoneFilterDef, getImageSrc, getPatternSvg, getResolvedShapeClipPath, getResolvedShapeClipPathFor, getShapeFillStrokeStyle, getSlideBackgroundStyle, getSlideTransitionAnimations, getTextBlockStyle, getTextWarp, getWarpCategory, getWarpPath, groupIssuesBySeverity, hasCopyableFormat, hasExistingLink, headerLabel, isInjectableUrl, isPpactionUrl, isSigned, isUrlSafe, isValidRoomId, issueTrackKey, issueTypeLabel, moveElementBy, msToFrameDelayCs, normalizeFontFormat, normalizeSlidesPerPage, ommlToMathml, overallStatus, pendingElementStyles, pickSupportedMimeType, planGifFrames, planVideoSegments, presenceToCursors, provideViewerTheme, recordWebm, removeCommentFromList, renderToCanvas, replaceInSlides, replaceMatch, resizeElement, resolveFontVariant, resolveHyperlinkHref, resolveParagraphBullet, resolvePresenterNotes, resolveTransitionDuration, revealedElementStyles, routeOrthogonalConnector, sanitizeColor, sanitizeSlideIndex, sanitizeUserName, searchSlides, seedBroadcastFields, seedHyperlinkDraft, seedPropertiesDraft, seedShareFields, segmentFrameCount, sendBackward, sendToBack, setElementPosition, shouldUseSvgWarp, signatureCountLabel, signatureKey, signatureTimestamp, signerName, statusKind, statusLabel, themeStyle, themeToCssVars, toggleCommentResolvedInList, updateElementById, validatePrintSettings, validateRoomId, waypointsToPathD, worstStatus };
57396
60452
  //# sourceMappingURL=pptx-angular-viewer.mjs.map