pptx-angular-viewer 1.1.50 → 1.1.51
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.
- package/README.md +9 -11
- package/fesm2022/pptx-angular-viewer.mjs +879 -97
- package/fesm2022/pptx-angular-viewer.mjs.map +1 -1
- package/package.json +2 -2
- package/pptx-angular-viewer.css +1 -1
- package/types/pptx-angular-viewer.d.ts +115 -25
|
@@ -29879,6 +29879,163 @@ function createDefaultChartElement(chartType = DEFAULT_INSERT_CHART_TYPE, positi
|
|
|
29879
29879
|
});
|
|
29880
29880
|
}
|
|
29881
29881
|
|
|
29882
|
+
/**
|
|
29883
|
+
* SmartArt reflow: convert algorithmic layout results back to
|
|
29884
|
+
* PptxSmartArtDrawingShape[] so the drawing-shape renderer handles post-edit
|
|
29885
|
+
* display and shapes round-trip correctly through save.
|
|
29886
|
+
*
|
|
29887
|
+
* When structural edits (add/remove/reorder/text/style) clear `drawingShapes`,
|
|
29888
|
+
* `rebuildDrawingShapesIfCleared` calls the layout engine and converts the
|
|
29889
|
+
* geometry back to drawing shapes so the richer DrawingShapeRenderer path
|
|
29890
|
+
* stays active rather than falling back to the plain SVG family renderer.
|
|
29891
|
+
*
|
|
29892
|
+
* @module smartart-reflow-to-shapes
|
|
29893
|
+
*/
|
|
29894
|
+
// ── Polygon bounding-box helper ───────────────────────────────────────────────
|
|
29895
|
+
/**
|
|
29896
|
+
* Compute the axis-aligned bounding box of an SVG polygon points string.
|
|
29897
|
+
* Returns a zero-size box when the string is empty or unparseable.
|
|
29898
|
+
*/
|
|
29899
|
+
function polygonBounds(points) {
|
|
29900
|
+
const pairs = points.trim().split(/\s+/u);
|
|
29901
|
+
let minX = Infinity;
|
|
29902
|
+
let minY = Infinity;
|
|
29903
|
+
let maxX = -Infinity;
|
|
29904
|
+
let maxY = -Infinity;
|
|
29905
|
+
for (const pair of pairs) {
|
|
29906
|
+
const comma = pair.indexOf(',');
|
|
29907
|
+
if (comma < 0) {
|
|
29908
|
+
continue;
|
|
29909
|
+
}
|
|
29910
|
+
const x = parseFloat(pair.slice(0, comma));
|
|
29911
|
+
const y = parseFloat(pair.slice(comma + 1));
|
|
29912
|
+
if (!isFinite(x) || !isFinite(y)) {
|
|
29913
|
+
continue;
|
|
29914
|
+
}
|
|
29915
|
+
if (x < minX) {
|
|
29916
|
+
minX = x;
|
|
29917
|
+
}
|
|
29918
|
+
if (x > maxX) {
|
|
29919
|
+
maxX = x;
|
|
29920
|
+
}
|
|
29921
|
+
if (y < minY) {
|
|
29922
|
+
minY = y;
|
|
29923
|
+
}
|
|
29924
|
+
if (y > maxY) {
|
|
29925
|
+
maxY = y;
|
|
29926
|
+
}
|
|
29927
|
+
}
|
|
29928
|
+
if (!isFinite(minX)) {
|
|
29929
|
+
return { x: 0, y: 0, width: 0, height: 0 };
|
|
29930
|
+
}
|
|
29931
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
29932
|
+
}
|
|
29933
|
+
// ── Single-node mapping ────────────────────────────────────────────────────────
|
|
29934
|
+
/**
|
|
29935
|
+
* Map one RenderedNode to a PptxSmartArtDrawingShape.
|
|
29936
|
+
*
|
|
29937
|
+
* The shape ID encodes the layout family and the source node id as a suffix so
|
|
29938
|
+
* `resolveDrawingShapeNodeId` can match it back via the `reflow-` prefix rule.
|
|
29939
|
+
*/
|
|
29940
|
+
function renderedNodeToShape(rn, nodeId, family) {
|
|
29941
|
+
const id = `reflow-${family}-${nodeId}`;
|
|
29942
|
+
const text = rn.text || undefined;
|
|
29943
|
+
if (rn.kind === 'rect') {
|
|
29944
|
+
return {
|
|
29945
|
+
id,
|
|
29946
|
+
shapeType: rn.rx > 0 ? 'roundRect' : 'rect',
|
|
29947
|
+
x: rn.x,
|
|
29948
|
+
y: rn.y,
|
|
29949
|
+
width: rn.width,
|
|
29950
|
+
height: rn.height,
|
|
29951
|
+
rotation: 0,
|
|
29952
|
+
fillColor: rn.fill,
|
|
29953
|
+
strokeColor: rn.stroke,
|
|
29954
|
+
strokeWidth: rn.strokeWidth,
|
|
29955
|
+
text,
|
|
29956
|
+
fontSize: rn.fontSize,
|
|
29957
|
+
};
|
|
29958
|
+
}
|
|
29959
|
+
if (rn.kind === 'circle') {
|
|
29960
|
+
return {
|
|
29961
|
+
id,
|
|
29962
|
+
shapeType: 'ellipse',
|
|
29963
|
+
x: rn.cx - rn.r,
|
|
29964
|
+
y: rn.cy - rn.r,
|
|
29965
|
+
width: rn.r * 2,
|
|
29966
|
+
height: rn.r * 2,
|
|
29967
|
+
rotation: 0,
|
|
29968
|
+
fillColor: rn.fill,
|
|
29969
|
+
strokeColor: rn.stroke,
|
|
29970
|
+
strokeWidth: rn.strokeWidth,
|
|
29971
|
+
text,
|
|
29972
|
+
fontSize: rn.fontSize,
|
|
29973
|
+
};
|
|
29974
|
+
}
|
|
29975
|
+
// polygon -- extract a bounding box from the SVG points string
|
|
29976
|
+
const bounds = polygonBounds(rn.points);
|
|
29977
|
+
return {
|
|
29978
|
+
id,
|
|
29979
|
+
shapeType: 'chevron',
|
|
29980
|
+
x: bounds.x,
|
|
29981
|
+
y: bounds.y,
|
|
29982
|
+
width: bounds.width,
|
|
29983
|
+
height: bounds.height,
|
|
29984
|
+
rotation: 0,
|
|
29985
|
+
fillColor: rn.fill,
|
|
29986
|
+
strokeColor: rn.stroke,
|
|
29987
|
+
strokeWidth: rn.strokeWidth,
|
|
29988
|
+
text,
|
|
29989
|
+
fontSize: rn.fontSize,
|
|
29990
|
+
};
|
|
29991
|
+
}
|
|
29992
|
+
// ── Public API ─────────────────────────────────────────────────────────────────
|
|
29993
|
+
/**
|
|
29994
|
+
* Convert a SmartArt layout result to an array of PptxSmartArtDrawingShape.
|
|
29995
|
+
*
|
|
29996
|
+
* Rendered nodes are matched to source nodes by flat index when the counts
|
|
29997
|
+
* align; otherwise the rendered node's key is used as the node-id suffix.
|
|
29998
|
+
* Connector paths are omitted: PptxSmartArtDrawingShape has no SVG path field,
|
|
29999
|
+
* and connectors carry no editable content anyway.
|
|
30000
|
+
*/
|
|
30001
|
+
function reflowToDrawingShapes(layoutResult, nodes) {
|
|
30002
|
+
const flat = flattenNodes([...nodes]);
|
|
30003
|
+
const family = layoutResult.family;
|
|
30004
|
+
const shapes = [];
|
|
30005
|
+
for (let i = 0; i < layoutResult.nodes.length; i++) {
|
|
30006
|
+
const rn = layoutResult.nodes[i];
|
|
30007
|
+
const sourceNode = flat[i];
|
|
30008
|
+
const nodeId = sourceNode?.id ?? rn.key;
|
|
30009
|
+
shapes.push(renderedNodeToShape(rn, nodeId, family));
|
|
30010
|
+
}
|
|
30011
|
+
return shapes;
|
|
30012
|
+
}
|
|
30013
|
+
/**
|
|
30014
|
+
* Rebuild drawing shapes from the algorithmic layout engine when they have been
|
|
30015
|
+
* cleared by a structural edit (add/remove/reorder/text/style change on a node).
|
|
30016
|
+
*
|
|
30017
|
+
* Returns the original data unchanged when drawing shapes are already populated
|
|
30018
|
+
* or when there are no nodes to lay out.
|
|
30019
|
+
*
|
|
30020
|
+
* @param smartArtData - The updated SmartArt data (with cleared drawingShapes).
|
|
30021
|
+
* @param layout - Named layout preset from the element (may be undefined).
|
|
30022
|
+
* @param palette - Resolved colour palette (hex strings).
|
|
30023
|
+
* @param style - Resolved SmartArt style intensity.
|
|
30024
|
+
* @param elementId - Element ID used for stable SVG key generation.
|
|
30025
|
+
* @param box - Pixel bounding box of the element.
|
|
30026
|
+
*/
|
|
30027
|
+
function rebuildDrawingShapesIfCleared(smartArtData, layout, palette, style, elementId, box) {
|
|
30028
|
+
const shapes = smartArtData.drawingShapes;
|
|
30029
|
+
if ((shapes !== undefined && shapes.length > 0) || smartArtData.nodes.length === 0) {
|
|
30030
|
+
return smartArtData;
|
|
30031
|
+
}
|
|
30032
|
+
const layoutResult = computeSmartArtLayout(smartArtData.nodes, box, palette, style, elementId, smartArtData.resolvedLayoutType, layout);
|
|
30033
|
+
return {
|
|
30034
|
+
...smartArtData,
|
|
30035
|
+
drawingShapes: reflowToDrawingShapes(layoutResult, smartArtData.nodes),
|
|
30036
|
+
};
|
|
30037
|
+
}
|
|
30038
|
+
|
|
29882
30039
|
/**
|
|
29883
30040
|
* Framework-agnostic rendering & editing helpers shared by the React, Vue, and
|
|
29884
30041
|
* Angular `pptx-viewer` bindings. Pure TypeScript (no framework imports) — each
|
|
@@ -35644,7 +35801,7 @@ class EditorToolbarComponent {
|
|
|
35644
35801
|
</button>
|
|
35645
35802
|
</div>
|
|
35646
35803
|
</div>
|
|
35647
|
-
`, isInline: true, styles: [".pptx-ng-toolbar{display:flex;flex-direction:row;align-items:center;gap:0;padding:4px 8px;background:var(--pptx-toolbar-bg, #1e1e1e);color:var(--pptx-toolbar-fg, #e0e0e0);border-bottom:1px solid var(--pptx-toolbar-border, #333);min-height:36px;-webkit-user-select:none;user-select:none}.pptx-ng-toolbar__group{display:flex;flex-direction:row;align-items:center;gap:
|
|
35804
|
+
`, isInline: true, styles: [".pptx-ng-toolbar{display:flex;flex-direction:row;align-items:center;gap:0;padding:4px 8px;background:var(--pptx-toolbar-bg, #1e1e1e);color:var(--pptx-toolbar-fg, #e0e0e0);border-bottom:1px solid var(--pptx-toolbar-border, #333);min-height:36px;-webkit-user-select:none;user-select:none}.pptx-ng-toolbar__group{display:flex;flex-direction:row;align-items:center;gap:4px}.pptx-ng-toolbar__group-label{font-size:10px;color:var(--pptx-toolbar-muted, #888);text-transform:uppercase;letter-spacing:.05em;padding:0 6px 0 4px;flex-shrink:0}.pptx-ng-toolbar__divider{width:1px;height:20px;background:var(--pptx-toolbar-border, #444);margin:0 4px;flex-shrink:0}.pptx-ng-toolbar__btn{display:inline-flex;align-items:center;justify-content:center;min-width:28px;height:28px;padding:2px 8px;background:transparent;border:1px solid transparent;border-radius:4px;color:inherit;font-size:14px;cursor:pointer;transition:background .1s;flex-shrink:0}.pptx-ng-toolbar__btn:hover:not(:disabled){background:var(--pptx-toolbar-hover, #3a3a3a)}.pptx-ng-toolbar__btn:active:not(:disabled){background:var(--pptx-toolbar-active-bg, #2a2a2a);transform:scale(.95);opacity:.8}.pptx-ng-toolbar__btn:disabled{opacity:.4;cursor:not-allowed}.pptx-ng-toolbar__btn--danger:not(:disabled){color:var(--pptx-toolbar-danger, #f47c7c)}.pptx-ng-toolbar__btn--danger:hover:not(:disabled){background:var(--pptx-toolbar-danger-hover, #4a1a1a)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
35648
35805
|
}
|
|
35649
35806
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: EditorToolbarComponent, decorators: [{
|
|
35650
35807
|
type: Component,
|
|
@@ -35855,7 +36012,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
35855
36012
|
</button>
|
|
35856
36013
|
</div>
|
|
35857
36014
|
</div>
|
|
35858
|
-
`, styles: [".pptx-ng-toolbar{display:flex;flex-direction:row;align-items:center;gap:0;padding:4px 8px;background:var(--pptx-toolbar-bg, #1e1e1e);color:var(--pptx-toolbar-fg, #e0e0e0);border-bottom:1px solid var(--pptx-toolbar-border, #333);min-height:36px;-webkit-user-select:none;user-select:none}.pptx-ng-toolbar__group{display:flex;flex-direction:row;align-items:center;gap:
|
|
36015
|
+
`, styles: [".pptx-ng-toolbar{display:flex;flex-direction:row;align-items:center;gap:0;padding:4px 8px;background:var(--pptx-toolbar-bg, #1e1e1e);color:var(--pptx-toolbar-fg, #e0e0e0);border-bottom:1px solid var(--pptx-toolbar-border, #333);min-height:36px;-webkit-user-select:none;user-select:none}.pptx-ng-toolbar__group{display:flex;flex-direction:row;align-items:center;gap:4px}.pptx-ng-toolbar__group-label{font-size:10px;color:var(--pptx-toolbar-muted, #888);text-transform:uppercase;letter-spacing:.05em;padding:0 6px 0 4px;flex-shrink:0}.pptx-ng-toolbar__divider{width:1px;height:20px;background:var(--pptx-toolbar-border, #444);margin:0 4px;flex-shrink:0}.pptx-ng-toolbar__btn{display:inline-flex;align-items:center;justify-content:center;min-width:28px;height:28px;padding:2px 8px;background:transparent;border:1px solid transparent;border-radius:4px;color:inherit;font-size:14px;cursor:pointer;transition:background .1s;flex-shrink:0}.pptx-ng-toolbar__btn:hover:not(:disabled){background:var(--pptx-toolbar-hover, #3a3a3a)}.pptx-ng-toolbar__btn:active:not(:disabled){background:var(--pptx-toolbar-active-bg, #2a2a2a);transform:scale(.95);opacity:.8}.pptx-ng-toolbar__btn:disabled{opacity:.4;cursor:not-allowed}.pptx-ng-toolbar__btn--danger:not(:disabled){color:var(--pptx-toolbar-danger, #f47c7c)}.pptx-ng-toolbar__btn--danger:hover:not(:disabled){background:var(--pptx-toolbar-danger-hover, #4a1a1a)}\n"] }]
|
|
35859
36016
|
}], propDecorators: { slideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideIndex", required: true }] }] } });
|
|
35860
36017
|
|
|
35861
36018
|
/**
|
|
@@ -36410,10 +36567,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
36410
36567
|
}] });
|
|
36411
36568
|
|
|
36412
36569
|
/**
|
|
36413
|
-
*
|
|
36570
|
+
* EyeDropper colour sampler for the Angular viewer.
|
|
36414
36571
|
*
|
|
36415
|
-
*
|
|
36416
|
-
*
|
|
36572
|
+
* Prefers the native browser EyeDropper API (Chrome 95+ / Edge 95+). For
|
|
36573
|
+
* browsers without it (Firefox, Safari) a DOM-sampling fallback reads the
|
|
36574
|
+
* colour under the pointer via `elementFromPoint` + `getComputedStyle`, so the
|
|
36575
|
+
* eyedropper still works everywhere. Mirrors the React viewer's
|
|
36576
|
+
* `viewer/utils/eyedropper.ts`.
|
|
36417
36577
|
*/
|
|
36418
36578
|
/**
|
|
36419
36579
|
* Returns true when the native EyeDropper API is available in this browser.
|
|
@@ -36441,6 +36601,103 @@ async function openNativeEyeDropper() {
|
|
|
36441
36601
|
return null;
|
|
36442
36602
|
}
|
|
36443
36603
|
}
|
|
36604
|
+
// ---------------------------------------------------------------------------
|
|
36605
|
+
// Fallback sampling (Firefox / Safari)
|
|
36606
|
+
// ---------------------------------------------------------------------------
|
|
36607
|
+
function toHex(r, g, b) {
|
|
36608
|
+
const h = (n) => n.toString(16).padStart(2, '0');
|
|
36609
|
+
return `#${h(r)}${h(g)}${h(b)}`;
|
|
36610
|
+
}
|
|
36611
|
+
function parseRgbaString(str) {
|
|
36612
|
+
const match = str.match(/rgba?\(\s*(?<r>\d+)\s*,\s*(?<g>\d+)\s*,\s*(?<b>\d+)/u);
|
|
36613
|
+
if (!match?.groups) {
|
|
36614
|
+
return null;
|
|
36615
|
+
}
|
|
36616
|
+
const r = parseInt(match.groups.r, 10);
|
|
36617
|
+
const g = parseInt(match.groups.g, 10);
|
|
36618
|
+
const b = parseInt(match.groups.b, 10);
|
|
36619
|
+
return { r, g, b, hex: toHex(r, g, b) };
|
|
36620
|
+
}
|
|
36621
|
+
/**
|
|
36622
|
+
* Sample the colour at a client-space point by inspecting the topmost element's
|
|
36623
|
+
* computed paint. Tries, in order: a real `<canvas>` pixel read (sharp for
|
|
36624
|
+
* rasterised content), then the element's `background-color`, SVG `fill`, and
|
|
36625
|
+
* finally text `color`. Returns `null` when nothing paintable is found.
|
|
36626
|
+
*
|
|
36627
|
+
* This is the DOM fallback used when the native EyeDropper API is unavailable;
|
|
36628
|
+
* it needs no canvas plumbing, so it works against the live slide DOM.
|
|
36629
|
+
*/
|
|
36630
|
+
function sampleColorFromSlide(clientX, clientY) {
|
|
36631
|
+
const target = typeof document === 'undefined' ? null : document.elementFromPoint(clientX, clientY);
|
|
36632
|
+
if (!(target instanceof Element)) {
|
|
36633
|
+
return null;
|
|
36634
|
+
}
|
|
36635
|
+
// Direct pixel read when the pointer is over a <canvas> (untainted only).
|
|
36636
|
+
const canvas = target instanceof HTMLCanvasElement ? target : target.closest('canvas');
|
|
36637
|
+
if (canvas instanceof HTMLCanvasElement) {
|
|
36638
|
+
try {
|
|
36639
|
+
const ctx = canvas.getContext('2d');
|
|
36640
|
+
if (ctx) {
|
|
36641
|
+
const rect = canvas.getBoundingClientRect();
|
|
36642
|
+
const sx = Math.round((clientX - rect.left) * (canvas.width / canvas.clientWidth));
|
|
36643
|
+
const sy = Math.round((clientY - rect.top) * (canvas.height / canvas.clientHeight));
|
|
36644
|
+
const pixel = ctx.getImageData(sx, sy, 1, 1).data;
|
|
36645
|
+
return { r: pixel[0], g: pixel[1], b: pixel[2], hex: toHex(pixel[0], pixel[1], pixel[2]) };
|
|
36646
|
+
}
|
|
36647
|
+
}
|
|
36648
|
+
catch {
|
|
36649
|
+
// Cross-origin / tainted canvas: fall through to computed-style sampling.
|
|
36650
|
+
}
|
|
36651
|
+
}
|
|
36652
|
+
const computed = getComputedStyle(target);
|
|
36653
|
+
const bg = computed.backgroundColor;
|
|
36654
|
+
if (bg && bg !== 'transparent' && bg !== 'rgba(0, 0, 0, 0)') {
|
|
36655
|
+
const parsed = parseRgbaString(bg);
|
|
36656
|
+
if (parsed) {
|
|
36657
|
+
return parsed;
|
|
36658
|
+
}
|
|
36659
|
+
}
|
|
36660
|
+
const fill = computed.fill;
|
|
36661
|
+
if (fill && fill !== 'none' && fill !== 'transparent') {
|
|
36662
|
+
const parsed = parseRgbaString(fill);
|
|
36663
|
+
if (parsed) {
|
|
36664
|
+
return parsed;
|
|
36665
|
+
}
|
|
36666
|
+
}
|
|
36667
|
+
return computed.color ? parseRgbaString(computed.color) : null;
|
|
36668
|
+
}
|
|
36669
|
+
/**
|
|
36670
|
+
* Run the eyedropper fallback: arm a one-shot pointer listener and resolve with
|
|
36671
|
+
* the hex colour of the next click (or `null` if the user presses Escape). Used
|
|
36672
|
+
* only when {@link eyedropperAvailable} is false. The caller is responsible for
|
|
36673
|
+
* any "armed" UI affordance; this just manages the listeners.
|
|
36674
|
+
*/
|
|
36675
|
+
function pickColorByClickFallback() {
|
|
36676
|
+
if (typeof document === 'undefined') {
|
|
36677
|
+
return Promise.resolve(null);
|
|
36678
|
+
}
|
|
36679
|
+
return new Promise((resolve) => {
|
|
36680
|
+
const cleanup = () => {
|
|
36681
|
+
document.removeEventListener('pointerdown', onPointerDown, true);
|
|
36682
|
+
document.removeEventListener('keydown', onKeyDown, true);
|
|
36683
|
+
};
|
|
36684
|
+
const onPointerDown = (event) => {
|
|
36685
|
+
event.preventDefault();
|
|
36686
|
+
event.stopPropagation();
|
|
36687
|
+
cleanup();
|
|
36688
|
+
const sample = sampleColorFromSlide(event.clientX, event.clientY);
|
|
36689
|
+
resolve(sample ? sample.hex : null);
|
|
36690
|
+
};
|
|
36691
|
+
const onKeyDown = (event) => {
|
|
36692
|
+
if (event.key === 'Escape') {
|
|
36693
|
+
cleanup();
|
|
36694
|
+
resolve(null);
|
|
36695
|
+
}
|
|
36696
|
+
};
|
|
36697
|
+
document.addEventListener('pointerdown', onPointerDown, true);
|
|
36698
|
+
document.addEventListener('keydown', onKeyDown, true);
|
|
36699
|
+
});
|
|
36700
|
+
}
|
|
36444
36701
|
|
|
36445
36702
|
/**
|
|
36446
36703
|
* `LoadContentService`: Angular port of the React `useLoadContent` hook and
|
|
@@ -45069,6 +45326,9 @@ class InspectorPanelComponent {
|
|
|
45069
45326
|
};
|
|
45070
45327
|
}, /* @ts-ignore */
|
|
45071
45328
|
...(ngDevMode ? [{ debugName: "seed" }] : /* istanbul ignore next */ []));
|
|
45329
|
+
/** Whether the element has lock flags preventing move/select. */
|
|
45330
|
+
isLocked = computed(() => Boolean(this.el().locks?.noMove) || Boolean(this.el().locks?.noSelect), /* @ts-ignore */
|
|
45331
|
+
...(ngDevMode ? [{ debugName: "isLocked" }] : /* istanbul ignore next */ []));
|
|
45072
45332
|
/** Whether the element supports shape-style (fill/stroke) editing. */
|
|
45073
45333
|
hasShape = computed(() => hasShapeProperties(this.el()), /* @ts-ignore */
|
|
45074
45334
|
...(ngDevMode ? [{ debugName: "hasShape" }] : /* istanbul ignore next */ []));
|
|
@@ -45105,6 +45365,16 @@ class InspectorPanelComponent {
|
|
|
45105
45365
|
smartArtData,
|
|
45106
45366
|
});
|
|
45107
45367
|
}
|
|
45368
|
+
/** Toggle element lock (noMove + noResize + noSelect). */
|
|
45369
|
+
onLockToggle() {
|
|
45370
|
+
if (this.isLocked()) {
|
|
45371
|
+
this.onPatch({ locks: undefined });
|
|
45372
|
+
}
|
|
45373
|
+
else {
|
|
45374
|
+
const locks = { noMove: true, noResize: true, noSelect: true };
|
|
45375
|
+
this.onPatch({ locks });
|
|
45376
|
+
}
|
|
45377
|
+
}
|
|
45108
45378
|
/** Commit a partial-element patch from an advanced sub-panel as one history entry. */
|
|
45109
45379
|
onPatch(patch) {
|
|
45110
45380
|
this.editor.updateElement(this.slideIndex(), this.el().id, patch);
|
|
@@ -45384,7 +45654,22 @@ class InspectorPanelComponent {
|
|
|
45384
45654
|
|
|
45385
45655
|
<!-- ── Arrange ────────────────────────────────────────────────────── -->
|
|
45386
45656
|
<section class="pptx-ng-inspector__section">
|
|
45387
|
-
<
|
|
45657
|
+
<div
|
|
45658
|
+
class="pptx-ng-inspector__row"
|
|
45659
|
+
style="justify-content:space-between;margin-bottom:0.35rem"
|
|
45660
|
+
>
|
|
45661
|
+
<h3 class="pptx-ng-inspector__heading" style="margin:0">Arrange</h3>
|
|
45662
|
+
<button
|
|
45663
|
+
type="button"
|
|
45664
|
+
class="pptx-ng-inspector__btn"
|
|
45665
|
+
style="flex:0 0 auto;padding:2px 6px"
|
|
45666
|
+
[attr.aria-pressed]="isLocked()"
|
|
45667
|
+
[title]="isLocked() ? 'Unlock element' : 'Lock element'"
|
|
45668
|
+
(click)="onLockToggle()"
|
|
45669
|
+
>
|
|
45670
|
+
{{ isLocked() ? 'Locked' : 'Lock' }}
|
|
45671
|
+
</button>
|
|
45672
|
+
</div>
|
|
45388
45673
|
|
|
45389
45674
|
<div class="pptx-ng-inspector__row">
|
|
45390
45675
|
<button
|
|
@@ -45689,7 +45974,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
45689
45974
|
|
|
45690
45975
|
<!-- ── Arrange ────────────────────────────────────────────────────── -->
|
|
45691
45976
|
<section class="pptx-ng-inspector__section">
|
|
45692
|
-
<
|
|
45977
|
+
<div
|
|
45978
|
+
class="pptx-ng-inspector__row"
|
|
45979
|
+
style="justify-content:space-between;margin-bottom:0.35rem"
|
|
45980
|
+
>
|
|
45981
|
+
<h3 class="pptx-ng-inspector__heading" style="margin:0">Arrange</h3>
|
|
45982
|
+
<button
|
|
45983
|
+
type="button"
|
|
45984
|
+
class="pptx-ng-inspector__btn"
|
|
45985
|
+
style="flex:0 0 auto;padding:2px 6px"
|
|
45986
|
+
[attr.aria-pressed]="isLocked()"
|
|
45987
|
+
[title]="isLocked() ? 'Unlock element' : 'Lock element'"
|
|
45988
|
+
(click)="onLockToggle()"
|
|
45989
|
+
>
|
|
45990
|
+
{{ isLocked() ? 'Locked' : 'Lock' }}
|
|
45991
|
+
</button>
|
|
45992
|
+
</div>
|
|
45693
45993
|
|
|
45694
45994
|
<div class="pptx-ng-inspector__row">
|
|
45695
45995
|
<button
|
|
@@ -49507,20 +49807,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
49507
49807
|
`, styles: [".pptx-ng-ole-preview{position:relative;width:100%;height:100%}.pptx-ng-ole-img{width:100%;height:100%;object-fit:contain;pointer-events:none;-webkit-user-select:none;user-select:none;display:block}.pptx-ng-ole-badge{position:absolute;bottom:4px;right:4px;z-index:10}.pptx-ng-ole-placeholder{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none;box-sizing:border-box}.pptx-ng-ole-name{margin-top:8px;font-size:12px;font-weight:500;max-width:90%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-ng-ole-sublabel{margin-top:2px;font-size:10px;color:#00000073;max-width:90%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-ng-ole-actions{position:absolute;bottom:4px;left:4px;display:flex;gap:4px;z-index:11;opacity:0;transition:opacity .12s ease-in-out}.pptx-ng-ole:hover .pptx-ng-ole-actions,.pptx-ng-ole-actions:focus-within{opacity:1}.pptx-ng-ole-action{font-size:11px;line-height:1;padding:4px 8px;border-radius:4px;background-color:#000000b8;color:#fff;text-decoration:none;cursor:pointer;white-space:nowrap;pointer-events:auto}.pptx-ng-ole-action:hover{background-color:#000000d9}.pptx-ng-ole-action:focus-visible{outline:2px solid #fff;outline-offset:1px}\n"] }]
|
|
49508
49808
|
}], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }] } });
|
|
49509
49809
|
|
|
49510
|
-
/**
|
|
49511
|
-
* Thin re-export shim → vendored `pptx-viewer-shared` (`render/smartart-drawing`).
|
|
49512
|
-
*
|
|
49513
|
-
* The drawing-shape view-model helpers (palette resolution, chrome style,
|
|
49514
|
-
* viewBox fitting, `RenderedShape` projection) were extracted to shared and are
|
|
49515
|
-
* consumed by every binding. This shim preserves the historical Angular import
|
|
49516
|
-
* surface.
|
|
49517
|
-
*
|
|
49518
|
-
* Shared exports the SmartArt default palette as `SMARTART_DEFAULT_PALETTE`
|
|
49519
|
-
* (the bare `DEFAULT_PALETTE` is taken by the chart palette in shared); it is
|
|
49520
|
-
* re-aliased back to `DEFAULT_PALETTE` here so this binding's import sites and
|
|
49521
|
-
* the colocated tests are unchanged.
|
|
49522
|
-
*/
|
|
49523
|
-
|
|
49524
49810
|
/**
|
|
49525
49811
|
* smart-art-inline-edit.ts: pure logic for on-canvas SmartArt node text editing.
|
|
49526
49812
|
*
|
|
@@ -49691,20 +49977,65 @@ function containsElementId(elements, id) {
|
|
|
49691
49977
|
return false;
|
|
49692
49978
|
}
|
|
49693
49979
|
|
|
49980
|
+
/**
|
|
49981
|
+
* Thin re-export shim → vendored `pptx-viewer-shared` (`render/smartart-drawing`).
|
|
49982
|
+
*
|
|
49983
|
+
* The drawing-shape view-model helpers (palette resolution, chrome style,
|
|
49984
|
+
* viewBox fitting, `RenderedShape` projection) were extracted to shared and are
|
|
49985
|
+
* consumed by every binding. This shim preserves the historical Angular import
|
|
49986
|
+
* surface.
|
|
49987
|
+
*
|
|
49988
|
+
* Shared exports the SmartArt default palette as `SMARTART_DEFAULT_PALETTE`
|
|
49989
|
+
* (the bare `DEFAULT_PALETTE` is taken by the chart palette in shared); it is
|
|
49990
|
+
* re-aliased back to `DEFAULT_PALETTE` here so this binding's import sites and
|
|
49991
|
+
* the colocated tests are unchanged.
|
|
49992
|
+
*/
|
|
49993
|
+
|
|
49994
|
+
/** Narrow a RenderedNode to a circle, or undefined. */
|
|
49995
|
+
function narrowToCircle(node) {
|
|
49996
|
+
return node.kind === 'circle' ? node : undefined;
|
|
49997
|
+
}
|
|
49998
|
+
/** Narrow a RenderedNode to a polygon, or undefined. */
|
|
49999
|
+
function narrowToPolygon(node) {
|
|
50000
|
+
return node.kind === 'polygon' ? node : undefined;
|
|
50001
|
+
}
|
|
50002
|
+
/** Narrow a RenderedNode to a rect, or undefined. */
|
|
50003
|
+
function narrowToRect(node) {
|
|
50004
|
+
return node.kind === 'rect' ? node : undefined;
|
|
50005
|
+
}
|
|
50006
|
+
/**
|
|
50007
|
+
* Split node text on newlines and compute per-line y offsets (in SVG px)
|
|
50008
|
+
* that centre the block around the node centre y (offset 0). Single-line
|
|
50009
|
+
* text produces one entry with offsetY=0, preserving the existing
|
|
50010
|
+
* dominant-baseline="central" behaviour exactly.
|
|
50011
|
+
*/
|
|
50012
|
+
function computeTextLines(text, fontSize) {
|
|
50013
|
+
const raw = (text ?? '').split('\n').filter((l) => l.length > 0);
|
|
50014
|
+
if (raw.length === 0) {
|
|
50015
|
+
return [{ text: '', offsetY: 0 }];
|
|
50016
|
+
}
|
|
50017
|
+
const lh = fontSize * 1.2;
|
|
50018
|
+
const totalH = raw.length * lh;
|
|
50019
|
+
return raw.map((line, i) => ({
|
|
50020
|
+
text: line,
|
|
50021
|
+
offsetY: -totalH / 2 + lh / 2 + i * lh,
|
|
50022
|
+
}));
|
|
50023
|
+
}
|
|
50024
|
+
|
|
49694
50025
|
/**
|
|
49695
50026
|
* SmartArtRendererComponent: Angular SmartArt renderer.
|
|
49696
50027
|
*
|
|
49697
50028
|
* Data path mirrors the Vue `SmartArtRenderer.vue` and the React renderer:
|
|
49698
|
-
* 1. **Drawing shapes** (`smartArtData.drawingShapes`)
|
|
50029
|
+
* 1. **Drawing shapes** (`smartArtData.drawingShapes`) -- the preferred path
|
|
49699
50030
|
* when the core extracted per-shape geometry from `ppt/diagrams/drawing*.xml`.
|
|
49700
|
-
* 2. **Shared SVG-fallback engine** (`computeSmartArtLayout`)
|
|
50031
|
+
* 2. **Shared SVG-fallback engine** (`computeSmartArtLayout`) -- when no drawing
|
|
49701
50032
|
* shapes exist, the framework-agnostic engine in `pptx-viewer-shared`
|
|
49702
50033
|
* positions/styles the node tree across all 10 layout families (list /
|
|
49703
50034
|
* process / cycle / hierarchy / matrix / radial / pyramid / venn / funnel /
|
|
49704
50035
|
* target), returning `RenderedNode[]` (rect / circle / polygon) +
|
|
49705
50036
|
* `RenderedConnector[]` view-models. Every binding renders the same
|
|
49706
50037
|
* geometry; this maps those view-models to SVG exactly as Vue does.
|
|
49707
|
-
* 3. **Placeholder**
|
|
50038
|
+
* 3. **Placeholder** -- when there is neither data nor any nodes/shapes.
|
|
49708
50039
|
*/
|
|
49709
50040
|
class SmartArtRendererComponent {
|
|
49710
50041
|
/** The smartArt element to render. Must be `type === 'smartArt'`. */
|
|
@@ -49733,6 +50064,9 @@ class SmartArtRendererComponent {
|
|
|
49733
50064
|
/** The mounted `<textarea>` for the active node edit, if any. */
|
|
49734
50065
|
nodeEditor = viewChild('nodeEditor', /* @ts-ignore */
|
|
49735
50066
|
...(ngDevMode ? [{ debugName: "nodeEditor" }] : /* istanbul ignore next */ []));
|
|
50067
|
+
/** Container div ref used to project hover rects into local coordinates. */
|
|
50068
|
+
smartartContainer = viewChild('smartartContainer', /* @ts-ignore */
|
|
50069
|
+
...(ngDevMode ? [{ debugName: "smartartContainer" }] : /* istanbul ignore next */ []));
|
|
49736
50070
|
/**
|
|
49737
50071
|
* Guards against a cancel-triggered DOM-removal blur committing the edit.
|
|
49738
50072
|
* Set to true before programmatic cancellation; reset to false on each new edit.
|
|
@@ -49787,6 +50121,17 @@ class SmartArtRendererComponent {
|
|
|
49787
50121
|
...(ngDevMode ? [{ debugName: "svgViewBox" }] : /* istanbul ignore next */ []));
|
|
49788
50122
|
renderedShapes = computed(() => projectDrawingShapes(this.element().id, this.rawDrawingShapes(), this.viewBox(), this.palette(), this.artStyle()), /* @ts-ignore */
|
|
49789
50123
|
...(ngDevMode ? [{ debugName: "renderedShapes" }] : /* istanbul ignore next */ []));
|
|
50124
|
+
/**
|
|
50125
|
+
* Node id for each drawing shape (index-aligned with `renderedShapes`).
|
|
50126
|
+
* Used to tag `<g>` elements with `data-smartart-node-id` so the 3D
|
|
50127
|
+
* renderer's hit-test overlay can resolve a click to a node.
|
|
50128
|
+
*/
|
|
50129
|
+
drawingShapeNodeIds = computed(() => {
|
|
50130
|
+
const shapes = this.rawDrawingShapes();
|
|
50131
|
+
const nodes = this.nodes();
|
|
50132
|
+
return shapes.map((shape, i) => resolveDrawingShapeNodeId(shape, i, shapes, nodes));
|
|
50133
|
+
}, /* @ts-ignore */
|
|
50134
|
+
...(ngDevMode ? [{ debugName: "drawingShapeNodeIds" }] : /* istanbul ignore next */ []));
|
|
49790
50135
|
// ── Shared SVG-fallback engine (no drawing shapes) ──────────────────────
|
|
49791
50136
|
layout = computed(() => {
|
|
49792
50137
|
const el = this.element();
|
|
@@ -49816,9 +50161,17 @@ class SmartArtRendererComponent {
|
|
|
49816
50161
|
return map;
|
|
49817
50162
|
}, /* @ts-ignore */
|
|
49818
50163
|
...(ngDevMode ? [{ debugName: "a11yLabelById" }] : /* istanbul ignore next */ []));
|
|
50164
|
+
/**
|
|
50165
|
+
* Parsed data-model node id for a rendered node (or `null` when the key does
|
|
50166
|
+
* not map to one). Exposed as a method so the template can use it: Angular
|
|
50167
|
+
* AOT templates can only call component members, not imported functions.
|
|
50168
|
+
*/
|
|
50169
|
+
nodeKeyId(node) {
|
|
50170
|
+
return nodeIdFromKey(node.key, this.element().id);
|
|
50171
|
+
}
|
|
49819
50172
|
/** Resolve the accessibility label for a rendered node (by parsed node id). */
|
|
49820
50173
|
nodeAriaLabel(node) {
|
|
49821
|
-
const nodeId =
|
|
50174
|
+
const nodeId = this.nodeKeyId(node);
|
|
49822
50175
|
if (nodeId === null) {
|
|
49823
50176
|
return null;
|
|
49824
50177
|
}
|
|
@@ -49830,36 +50183,15 @@ class SmartArtRendererComponent {
|
|
|
49830
50183
|
*/
|
|
49831
50184
|
liveMessage = signal('', /* @ts-ignore */
|
|
49832
50185
|
...(ngDevMode ? [{ debugName: "liveMessage" }] : /* istanbul ignore next */ []));
|
|
49833
|
-
|
|
49834
|
-
|
|
49835
|
-
|
|
49836
|
-
}
|
|
49837
|
-
/**
|
|
49838
|
-
|
|
49839
|
-
|
|
49840
|
-
|
|
49841
|
-
|
|
49842
|
-
asRect(node) {
|
|
49843
|
-
return node.kind === 'rect' ? node : undefined;
|
|
49844
|
-
}
|
|
49845
|
-
/**
|
|
49846
|
-
* Split node text on `\n` and compute per-line y offsets (in SVG px) that
|
|
49847
|
-
* centre the block around the node centre y (offset 0). Single-line text
|
|
49848
|
-
* produces one entry with offsetY=0, preserving the existing
|
|
49849
|
-
* `dominant-baseline="central"` behaviour exactly.
|
|
49850
|
-
*/
|
|
49851
|
-
textLines(text, fontSize) {
|
|
49852
|
-
const raw = (text ?? '').split('\n').filter((l) => l.length > 0);
|
|
49853
|
-
if (raw.length === 0) {
|
|
49854
|
-
return [{ text: '', offsetY: 0 }];
|
|
49855
|
-
}
|
|
49856
|
-
const lh = fontSize * 1.2;
|
|
49857
|
-
const totalH = raw.length * lh;
|
|
49858
|
-
return raw.map((line, i) => ({
|
|
49859
|
-
text: line,
|
|
49860
|
-
offsetY: -totalH / 2 + lh / 2 + i * lh,
|
|
49861
|
-
}));
|
|
49862
|
-
}
|
|
50186
|
+
hoveredNodeId = signal(null, /* @ts-ignore */
|
|
50187
|
+
...(ngDevMode ? [{ debugName: "hoveredNodeId" }] : /* istanbul ignore next */ []));
|
|
50188
|
+
hoveredNodeRect = signal(null, /* @ts-ignore */
|
|
50189
|
+
...(ngDevMode ? [{ debugName: "hoveredNodeRect" }] : /* istanbul ignore next */ []));
|
|
50190
|
+
/** Narrowing helpers bound as class properties for template type-checking. */
|
|
50191
|
+
asCircle = narrowToCircle;
|
|
50192
|
+
asPolygon = narrowToPolygon;
|
|
50193
|
+
asRect = narrowToRect;
|
|
50194
|
+
textLines = computeTextLines;
|
|
49863
50195
|
// ── Inline node-text editing ───────────────────────────────────────────
|
|
49864
50196
|
/** Double-click a node enters inline edit mode (when editable). */
|
|
49865
50197
|
onNodeDblClick(event, node) {
|
|
@@ -49918,7 +50250,7 @@ class SmartArtRendererComponent {
|
|
|
49918
50250
|
}
|
|
49919
50251
|
/** The node's full (untruncated) data-model text, falling back to rendered text. */
|
|
49920
50252
|
rawNodeText(node) {
|
|
49921
|
-
const nodeId =
|
|
50253
|
+
const nodeId = this.nodeKeyId(node);
|
|
49922
50254
|
if (nodeId === null) {
|
|
49923
50255
|
return node.text;
|
|
49924
50256
|
}
|
|
@@ -49946,21 +50278,81 @@ class SmartArtRendererComponent {
|
|
|
49946
50278
|
// Announce the commit to assistive technology via the polite live region.
|
|
49947
50279
|
this.liveMessage.set(text.trim().length > 0 ? `Node updated to ${text.trim()}` : 'Node cleared');
|
|
49948
50280
|
}
|
|
50281
|
+
// ── Style bar & hover tracking ────────────────────────────────────────
|
|
50282
|
+
styleBarStyle = computed(() => {
|
|
50283
|
+
const rect = this.hoveredNodeRect();
|
|
50284
|
+
if (!rect) {
|
|
50285
|
+
return null;
|
|
50286
|
+
}
|
|
50287
|
+
return {
|
|
50288
|
+
position: 'absolute',
|
|
50289
|
+
left: `${Math.max(0, rect.left + rect.width - 120)}px`,
|
|
50290
|
+
top: `${Math.max(0, rect.top - 22)}px`,
|
|
50291
|
+
'z-index': '25',
|
|
50292
|
+
};
|
|
50293
|
+
}, /* @ts-ignore */
|
|
50294
|
+
...(ngDevMode ? [{ debugName: "styleBarStyle" }] : /* istanbul ignore next */ []));
|
|
50295
|
+
onMouseMove(event) {
|
|
50296
|
+
if (!this.canEditNodes()) {
|
|
50297
|
+
return;
|
|
50298
|
+
}
|
|
50299
|
+
const nodeEl = this.findNodeEl(event.target);
|
|
50300
|
+
const cnt = this.smartartContainer()?.nativeElement;
|
|
50301
|
+
const id = nodeEl?.getAttribute('data-smartart-node-id') ?? null;
|
|
50302
|
+
this.hoveredNodeId.set(id);
|
|
50303
|
+
this.hoveredNodeRect.set(id && nodeEl && cnt
|
|
50304
|
+
? computeInlineEditorRect(nodeEl.getBoundingClientRect(), cnt.getBoundingClientRect())
|
|
50305
|
+
: null);
|
|
50306
|
+
}
|
|
50307
|
+
onMouseLeave() {
|
|
50308
|
+
this.hoveredNodeId.set(null);
|
|
50309
|
+
this.hoveredNodeRect.set(null);
|
|
50310
|
+
}
|
|
50311
|
+
findNodeEl(target) {
|
|
50312
|
+
let el = target instanceof Element ? target : null;
|
|
50313
|
+
while (el) {
|
|
50314
|
+
if (el.hasAttribute('data-smartart-node-id')) {
|
|
50315
|
+
return el;
|
|
50316
|
+
}
|
|
50317
|
+
el = el.parentElement;
|
|
50318
|
+
}
|
|
50319
|
+
return null;
|
|
50320
|
+
}
|
|
50321
|
+
handleChangeNodeStyle(nodeId, fill) {
|
|
50322
|
+
const data = this.smartArtData();
|
|
50323
|
+
if (!data || !this.editor) {
|
|
50324
|
+
return;
|
|
50325
|
+
}
|
|
50326
|
+
const next = setSmartArtNodeStyle(data, nodeId, { fillColor: fill });
|
|
50327
|
+
if (next === data) {
|
|
50328
|
+
return;
|
|
50329
|
+
}
|
|
50330
|
+
const slideIndex = findSlideIndexByElementId(this.editor.slides(), this.element().id);
|
|
50331
|
+
if (slideIndex < 0) {
|
|
50332
|
+
return;
|
|
50333
|
+
}
|
|
50334
|
+
this.editor.updateElement(slideIndex, this.element().id, {
|
|
50335
|
+
smartArtData: next,
|
|
50336
|
+
});
|
|
50337
|
+
}
|
|
49949
50338
|
// ── Empty / no-data state ──────────────────────────────────────────────
|
|
49950
50339
|
isEmpty = computed(() => this.nodes().length === 0 && !this.hasDrawingShapes(), /* @ts-ignore */
|
|
49951
50340
|
...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
|
|
49952
50341
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: SmartArtRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
49953
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: SmartArtRendererComponent, isStandalone: true, selector: "pptx-smart-art-renderer", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "nodeEditor", first: true, predicate: ["nodeEditor"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
50342
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: SmartArtRendererComponent, isStandalone: true, selector: "pptx-smart-art-renderer", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "nodeEditor", first: true, predicate: ["nodeEditor"], descendants: true, isSignal: true }, { propertyName: "smartartContainer", first: true, predicate: ["smartartContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
49954
50343
|
<div
|
|
49955
50344
|
class="pptx-ng-element pptx-ng-smartart"
|
|
49956
50345
|
[ngStyle]="containerStyle()"
|
|
49957
50346
|
[attr.data-element-id]="element().id"
|
|
49958
50347
|
>
|
|
49959
50348
|
<div
|
|
50349
|
+
#smartartContainer
|
|
49960
50350
|
class="pptx-ng-smartart-chrome"
|
|
49961
50351
|
[ngStyle]="chromeStyle()"
|
|
49962
50352
|
[attr.role]="a11y() ? a11y()!.role : null"
|
|
49963
50353
|
[attr.aria-label]="a11y()?.label ?? null"
|
|
50354
|
+
(mousemove)="onMouseMove($event)"
|
|
50355
|
+
(mouseleave)="onMouseLeave()"
|
|
49964
50356
|
>
|
|
49965
50357
|
@if (isEmpty()) {
|
|
49966
50358
|
<div class="pptx-ng-smartart-placeholder">SmartArt</div>
|
|
@@ -49970,8 +50362,14 @@ class SmartArtRendererComponent {
|
|
|
49970
50362
|
[attr.viewBox]="svgViewBox()"
|
|
49971
50363
|
preserveAspectRatio="xMidYMid meet"
|
|
49972
50364
|
>
|
|
49973
|
-
@for (shape of renderedShapes(); track shape.key) {
|
|
49974
|
-
<g
|
|
50365
|
+
@for (shape of renderedShapes(); track shape.key; let i = $index) {
|
|
50366
|
+
<g
|
|
50367
|
+
[ngStyle]="shadowFilter() ? { filter: shadowFilter() } : {}"
|
|
50368
|
+
[attr.data-smartart-node-id]="drawingShapeNodeIds()[i] ?? null"
|
|
50369
|
+
[class.pptx-ng-smartart-node--editable]="
|
|
50370
|
+
canEditNodes() && !!drawingShapeNodeIds()[i]
|
|
50371
|
+
"
|
|
50372
|
+
>
|
|
49975
50373
|
@if (shape.isEllipse) {
|
|
49976
50374
|
<ellipse
|
|
49977
50375
|
[attr.cx]="shape.cx"
|
|
@@ -50037,6 +50435,7 @@ class SmartArtRendererComponent {
|
|
|
50037
50435
|
[attr.tabindex]="canEditNodes() ? 0 : null"
|
|
50038
50436
|
[attr.role]="canEditNodes() ? 'button' : 'img'"
|
|
50039
50437
|
[attr.aria-label]="nodeAriaLabel(node) ?? node.text"
|
|
50438
|
+
[attr.data-smartart-node-id]="nodeKeyId(node)"
|
|
50040
50439
|
(dblclick)="onNodeDblClick($event, node)"
|
|
50041
50440
|
(keydown)="onNodeKeydown($event, node)"
|
|
50042
50441
|
>
|
|
@@ -50118,6 +50517,25 @@ class SmartArtRendererComponent {
|
|
|
50118
50517
|
<div class="pptx-ng-smartart-placeholder">SmartArt</div>
|
|
50119
50518
|
}
|
|
50120
50519
|
|
|
50520
|
+
@if (canEditNodes() && hoveredNodeId() && !editState() && styleBarStyle()) {
|
|
50521
|
+
<div
|
|
50522
|
+
class="pptx-ng-smartart-style-bar"
|
|
50523
|
+
[ngStyle]="styleBarStyle()!"
|
|
50524
|
+
(mousedown)="$event.stopPropagation()"
|
|
50525
|
+
(click)="$event.stopPropagation()"
|
|
50526
|
+
>
|
|
50527
|
+
@for (color of palette().slice(0, 6); track color) {
|
|
50528
|
+
<button
|
|
50529
|
+
type="button"
|
|
50530
|
+
class="pptx-ng-smartart-swatch"
|
|
50531
|
+
[attr.aria-label]="'Set fill to ' + color"
|
|
50532
|
+
[style.background]="color"
|
|
50533
|
+
(click)="handleChangeNodeStyle(hoveredNodeId()!, color)"
|
|
50534
|
+
></button>
|
|
50535
|
+
}
|
|
50536
|
+
</div>
|
|
50537
|
+
}
|
|
50538
|
+
|
|
50121
50539
|
<!--
|
|
50122
50540
|
Inline node-text editor. Positioned in element-local px (== viewBox
|
|
50123
50541
|
units, since the SVG viewBox matches the element pixel size and the
|
|
@@ -50146,7 +50564,7 @@ class SmartArtRendererComponent {
|
|
|
50146
50564
|
<span class="pptx-ng-sr-only" aria-live="polite" role="status">{{ liveMessage() }}</span>
|
|
50147
50565
|
</div>
|
|
50148
50566
|
</div>
|
|
50149
|
-
`, isInline: true, styles: [".pptx-ng-smartart-chrome{box-sizing:border-box;overflow:hidden;position:relative}.pptx-ng-smartart-svg{width:100%;height:100%;pointer-events:none}.pptx-ng-smartart-node--editable{pointer-events:auto;cursor:text}.pptx-ng-smartart-node--editable:hover{filter:drop-shadow(0 0 2px rgba(96,165,250,.8))}.pptx-ng-smartart-node-editor{position:absolute;box-sizing:border-box;margin:0;padding:1px 2px;border:1px solid var(--pptx-inspector-active, #0078d4);border-radius:2px;background:#fff;color:#111;font-size:11px;line-height:1.1;text-align:center;resize:none;overflow:hidden;z-index:2}.pptx-ng-smartart-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:11px;color:#fffc;pointer-events:none}.pptx-ng-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
50567
|
+
`, isInline: true, styles: [".pptx-ng-smartart-chrome{box-sizing:border-box;overflow:hidden;position:relative}.pptx-ng-smartart-svg{width:100%;height:100%;pointer-events:none}.pptx-ng-smartart-node--editable{pointer-events:auto;cursor:text}.pptx-ng-smartart-node--editable:hover{filter:drop-shadow(0 0 2px rgba(96,165,250,.8))}.pptx-ng-smartart-node-editor{position:absolute;box-sizing:border-box;margin:0;padding:1px 2px;border:1px solid var(--pptx-inspector-active, #0078d4);border-radius:2px;background:#fff;color:#111;font-size:11px;line-height:1.1;text-align:center;resize:none;overflow:hidden;z-index:2}.pptx-ng-smartart-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:11px;color:#fffc;pointer-events:none}.pptx-ng-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.pptx-ng-smartart-style-bar{position:absolute;pointer-events:auto}.pptx-ng-smartart-swatch{width:14px;height:14px;border-radius:50%;border:1px solid rgba(0,0,0,.1);cursor:pointer;transition:transform .1s}.pptx-ng-smartart-swatch:hover{transform:scale(1.25)}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
50150
50568
|
}
|
|
50151
50569
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: SmartArtRendererComponent, decorators: [{
|
|
50152
50570
|
type: Component,
|
|
@@ -50157,10 +50575,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
50157
50575
|
[attr.data-element-id]="element().id"
|
|
50158
50576
|
>
|
|
50159
50577
|
<div
|
|
50578
|
+
#smartartContainer
|
|
50160
50579
|
class="pptx-ng-smartart-chrome"
|
|
50161
50580
|
[ngStyle]="chromeStyle()"
|
|
50162
50581
|
[attr.role]="a11y() ? a11y()!.role : null"
|
|
50163
50582
|
[attr.aria-label]="a11y()?.label ?? null"
|
|
50583
|
+
(mousemove)="onMouseMove($event)"
|
|
50584
|
+
(mouseleave)="onMouseLeave()"
|
|
50164
50585
|
>
|
|
50165
50586
|
@if (isEmpty()) {
|
|
50166
50587
|
<div class="pptx-ng-smartart-placeholder">SmartArt</div>
|
|
@@ -50170,8 +50591,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
50170
50591
|
[attr.viewBox]="svgViewBox()"
|
|
50171
50592
|
preserveAspectRatio="xMidYMid meet"
|
|
50172
50593
|
>
|
|
50173
|
-
@for (shape of renderedShapes(); track shape.key) {
|
|
50174
|
-
<g
|
|
50594
|
+
@for (shape of renderedShapes(); track shape.key; let i = $index) {
|
|
50595
|
+
<g
|
|
50596
|
+
[ngStyle]="shadowFilter() ? { filter: shadowFilter() } : {}"
|
|
50597
|
+
[attr.data-smartart-node-id]="drawingShapeNodeIds()[i] ?? null"
|
|
50598
|
+
[class.pptx-ng-smartart-node--editable]="
|
|
50599
|
+
canEditNodes() && !!drawingShapeNodeIds()[i]
|
|
50600
|
+
"
|
|
50601
|
+
>
|
|
50175
50602
|
@if (shape.isEllipse) {
|
|
50176
50603
|
<ellipse
|
|
50177
50604
|
[attr.cx]="shape.cx"
|
|
@@ -50237,6 +50664,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
50237
50664
|
[attr.tabindex]="canEditNodes() ? 0 : null"
|
|
50238
50665
|
[attr.role]="canEditNodes() ? 'button' : 'img'"
|
|
50239
50666
|
[attr.aria-label]="nodeAriaLabel(node) ?? node.text"
|
|
50667
|
+
[attr.data-smartart-node-id]="nodeKeyId(node)"
|
|
50240
50668
|
(dblclick)="onNodeDblClick($event, node)"
|
|
50241
50669
|
(keydown)="onNodeKeydown($event, node)"
|
|
50242
50670
|
>
|
|
@@ -50318,6 +50746,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
50318
50746
|
<div class="pptx-ng-smartart-placeholder">SmartArt</div>
|
|
50319
50747
|
}
|
|
50320
50748
|
|
|
50749
|
+
@if (canEditNodes() && hoveredNodeId() && !editState() && styleBarStyle()) {
|
|
50750
|
+
<div
|
|
50751
|
+
class="pptx-ng-smartart-style-bar"
|
|
50752
|
+
[ngStyle]="styleBarStyle()!"
|
|
50753
|
+
(mousedown)="$event.stopPropagation()"
|
|
50754
|
+
(click)="$event.stopPropagation()"
|
|
50755
|
+
>
|
|
50756
|
+
@for (color of palette().slice(0, 6); track color) {
|
|
50757
|
+
<button
|
|
50758
|
+
type="button"
|
|
50759
|
+
class="pptx-ng-smartart-swatch"
|
|
50760
|
+
[attr.aria-label]="'Set fill to ' + color"
|
|
50761
|
+
[style.background]="color"
|
|
50762
|
+
(click)="handleChangeNodeStyle(hoveredNodeId()!, color)"
|
|
50763
|
+
></button>
|
|
50764
|
+
}
|
|
50765
|
+
</div>
|
|
50766
|
+
}
|
|
50767
|
+
|
|
50321
50768
|
<!--
|
|
50322
50769
|
Inline node-text editor. Positioned in element-local px (== viewBox
|
|
50323
50770
|
units, since the SVG viewBox matches the element pixel size and the
|
|
@@ -50346,8 +50793,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
50346
50793
|
<span class="pptx-ng-sr-only" aria-live="polite" role="status">{{ liveMessage() }}</span>
|
|
50347
50794
|
</div>
|
|
50348
50795
|
</div>
|
|
50349
|
-
`, styles: [".pptx-ng-smartart-chrome{box-sizing:border-box;overflow:hidden;position:relative}.pptx-ng-smartart-svg{width:100%;height:100%;pointer-events:none}.pptx-ng-smartart-node--editable{pointer-events:auto;cursor:text}.pptx-ng-smartart-node--editable:hover{filter:drop-shadow(0 0 2px rgba(96,165,250,.8))}.pptx-ng-smartart-node-editor{position:absolute;box-sizing:border-box;margin:0;padding:1px 2px;border:1px solid var(--pptx-inspector-active, #0078d4);border-radius:2px;background:#fff;color:#111;font-size:11px;line-height:1.1;text-align:center;resize:none;overflow:hidden;z-index:2}.pptx-ng-smartart-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:11px;color:#fffc;pointer-events:none}.pptx-ng-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"] }]
|
|
50350
|
-
}], ctorParameters: () => [], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], nodeEditor: [{ type: i0.ViewChild, args: ['nodeEditor', { isSignal: true }] }] } });
|
|
50796
|
+
`, styles: [".pptx-ng-smartart-chrome{box-sizing:border-box;overflow:hidden;position:relative}.pptx-ng-smartart-svg{width:100%;height:100%;pointer-events:none}.pptx-ng-smartart-node--editable{pointer-events:auto;cursor:text}.pptx-ng-smartart-node--editable:hover{filter:drop-shadow(0 0 2px rgba(96,165,250,.8))}.pptx-ng-smartart-node-editor{position:absolute;box-sizing:border-box;margin:0;padding:1px 2px;border:1px solid var(--pptx-inspector-active, #0078d4);border-radius:2px;background:#fff;color:#111;font-size:11px;line-height:1.1;text-align:center;resize:none;overflow:hidden;z-index:2}.pptx-ng-smartart-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:11px;color:#fffc;pointer-events:none}.pptx-ng-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.pptx-ng-smartart-style-bar{position:absolute;pointer-events:auto}.pptx-ng-smartart-swatch{width:14px;height:14px;border-radius:50%;border:1px solid rgba(0,0,0,.1);cursor:pointer;transition:transform .1s}.pptx-ng-smartart-swatch:hover{transform:scale(1.25)}\n"] }]
|
|
50797
|
+
}], ctorParameters: () => [], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], nodeEditor: [{ type: i0.ViewChild, args: ['nodeEditor', { isSignal: true }] }], smartartContainer: [{ type: i0.ViewChild, args: ['smartartContainer', { isSignal: true }] }] } });
|
|
50351
50798
|
|
|
50352
50799
|
const PALETTES = {
|
|
50353
50800
|
colorful1: ['#3b82f6', '#22c55e', '#f97316', '#eab308', '#a855f7', '#ec4899'],
|
|
@@ -50364,20 +50811,41 @@ const PALETTES = {
|
|
|
50364
50811
|
* `pptx-viewer-shared/smartart-3d` and mounts it on a canvas. `three` is an
|
|
50365
50812
|
* optional peer dependency: when it is missing, the diagram has no geometry, or
|
|
50366
50813
|
* the scene errors, the component falls back to the SVG SmartArt renderer.
|
|
50814
|
+
*
|
|
50815
|
+
* When `canEdit` is true and the 3D scene is active, an invisible
|
|
50816
|
+
* `<pptx-smart-art-renderer>` overlay is stacked over the canvas. Double-clicking
|
|
50817
|
+
* on the overlay uses `document.elementsFromPoint` to locate the `<g>` bearing
|
|
50818
|
+
* `data-smartart-node-id`, then opens an inline textarea editor over that node
|
|
50819
|
+
* (same commit path as the SVG renderer: `EditorStateService.updateElement`).
|
|
50367
50820
|
*/
|
|
50368
50821
|
class SmartArt3DRendererComponent {
|
|
50369
50822
|
element = input.required(/* @ts-ignore */
|
|
50370
50823
|
...(ngDevMode ? [{ debugName: "element" }] : /* istanbul ignore next */ []));
|
|
50371
50824
|
zIndex = input(0, /* @ts-ignore */
|
|
50372
50825
|
...(ngDevMode ? [{ debugName: "zIndex" }] : /* istanbul ignore next */ []));
|
|
50826
|
+
/** When true and the 3D scene is active, enables inline node text editing. */
|
|
50827
|
+
canEdit = input(false, /* @ts-ignore */
|
|
50828
|
+
...(ngDevMode ? [{ debugName: "canEdit" }] : /* istanbul ignore next */ []));
|
|
50373
50829
|
canvas = viewChild('canvas', /* @ts-ignore */
|
|
50374
50830
|
...(ngDevMode ? [{ debugName: "canvas" }] : /* istanbul ignore next */ []));
|
|
50831
|
+
containerEl = viewChild('container3d', /* @ts-ignore */
|
|
50832
|
+
...(ngDevMode ? [{ debugName: "containerEl" }] : /* istanbul ignore next */ []));
|
|
50833
|
+
nodeEditor3d = viewChild('nodeEditor3d', /* @ts-ignore */
|
|
50834
|
+
...(ngDevMode ? [{ debugName: "nodeEditor3d" }] : /* istanbul ignore next */ []));
|
|
50375
50835
|
/** `true` until the 3D scene is known to be mountable; renders the SVG fallback. */
|
|
50376
50836
|
useFallback = signal(true, /* @ts-ignore */
|
|
50377
50837
|
...(ngDevMode ? [{ debugName: "useFallback" }] : /* istanbul ignore next */ []));
|
|
50378
50838
|
mountFn = signal(null, /* @ts-ignore */
|
|
50379
50839
|
...(ngDevMode ? [{ debugName: "mountFn" }] : /* istanbul ignore next */ []));
|
|
50380
50840
|
handle = null;
|
|
50841
|
+
editState = signal(null, /* @ts-ignore */
|
|
50842
|
+
...(ngDevMode ? [{ debugName: "editState" }] : /* istanbul ignore next */ []));
|
|
50843
|
+
/** Live draft text, updated on every input event. */
|
|
50844
|
+
draftText = '';
|
|
50845
|
+
/** Guards against a cancel-triggered DOM-removal blur committing the edit. */
|
|
50846
|
+
editSettled = false;
|
|
50847
|
+
editor = inject(EditorStateService, { optional: true });
|
|
50848
|
+
injector = inject(Injector);
|
|
50381
50849
|
containerStyle = computed(() => getContainerStyle(this.element(), this.zIndex()), /* @ts-ignore */
|
|
50382
50850
|
...(ngDevMode ? [{ debugName: "containerStyle" }] : /* istanbul ignore next */ []));
|
|
50383
50851
|
smartArtData = computed(() => {
|
|
@@ -50426,6 +50894,18 @@ class SmartArt3DRendererComponent {
|
|
|
50426
50894
|
const el = this.element();
|
|
50427
50895
|
this.handle?.resize(el.width, el.height);
|
|
50428
50896
|
});
|
|
50897
|
+
// Auto-focus the textarea when the editor opens.
|
|
50898
|
+
effect(() => {
|
|
50899
|
+
if (this.editState()) {
|
|
50900
|
+
afterNextRender(() => {
|
|
50901
|
+
const el = this.nodeEditor3d()?.nativeElement;
|
|
50902
|
+
if (el) {
|
|
50903
|
+
el.focus();
|
|
50904
|
+
el.select();
|
|
50905
|
+
}
|
|
50906
|
+
}, { injector: this.injector });
|
|
50907
|
+
}
|
|
50908
|
+
});
|
|
50429
50909
|
}
|
|
50430
50910
|
async loadScene() {
|
|
50431
50911
|
const m = this.model();
|
|
@@ -50441,24 +50921,144 @@ class SmartArt3DRendererComponent {
|
|
|
50441
50921
|
this.useFallback.set(true);
|
|
50442
50922
|
}
|
|
50443
50923
|
}
|
|
50924
|
+
/**
|
|
50925
|
+
* Locate the SmartArt node at the click position using `elementsFromPoint`
|
|
50926
|
+
* (which includes pointer-events:none SVG elements) and open the inline editor.
|
|
50927
|
+
*/
|
|
50928
|
+
onOverlayDblClick(event) {
|
|
50929
|
+
const container = this.containerEl()?.nativeElement;
|
|
50930
|
+
if (!container) {
|
|
50931
|
+
return;
|
|
50932
|
+
}
|
|
50933
|
+
const data = this.smartArtData();
|
|
50934
|
+
if (!data) {
|
|
50935
|
+
return;
|
|
50936
|
+
}
|
|
50937
|
+
// document.elementsFromPoint includes elements with pointer-events:none,
|
|
50938
|
+
// so we can find the <g data-smartart-node-id="..."> in the overlay SVG.
|
|
50939
|
+
const elements = document.elementsFromPoint(event.clientX, event.clientY);
|
|
50940
|
+
const nodeEl = elements.find((el) => el instanceof Element && el.hasAttribute('data-smartart-node-id'));
|
|
50941
|
+
if (!nodeEl) {
|
|
50942
|
+
return;
|
|
50943
|
+
}
|
|
50944
|
+
const nodeId = nodeEl.getAttribute('data-smartart-node-id');
|
|
50945
|
+
if (!nodeId) {
|
|
50946
|
+
return;
|
|
50947
|
+
}
|
|
50948
|
+
const currentText = data.nodes.find((n) => n.id === nodeId)?.text ?? '';
|
|
50949
|
+
const nodeRect = nodeEl.getBoundingClientRect();
|
|
50950
|
+
const containerRect = container.getBoundingClientRect();
|
|
50951
|
+
this.draftText = currentText;
|
|
50952
|
+
this.editSettled = false;
|
|
50953
|
+
this.editState.set({
|
|
50954
|
+
nodeId,
|
|
50955
|
+
box: {
|
|
50956
|
+
x: nodeRect.left - containerRect.left,
|
|
50957
|
+
y: nodeRect.top - containerRect.top,
|
|
50958
|
+
width: nodeRect.width,
|
|
50959
|
+
height: nodeRect.height,
|
|
50960
|
+
},
|
|
50961
|
+
text: currentText,
|
|
50962
|
+
});
|
|
50963
|
+
}
|
|
50964
|
+
/** Update the live draft text on each keystroke. */
|
|
50965
|
+
updateDraft(event) {
|
|
50966
|
+
this.draftText = event.target.value;
|
|
50967
|
+
}
|
|
50968
|
+
/** Enter commits (via blur); Escape cancels. Propagation always stopped. */
|
|
50969
|
+
onEditorKeydown(event) {
|
|
50970
|
+
event.stopPropagation();
|
|
50971
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
50972
|
+
event.preventDefault();
|
|
50973
|
+
// Commit via blur so the single commit path runs once.
|
|
50974
|
+
event.target.blur();
|
|
50975
|
+
}
|
|
50976
|
+
else if (event.key === 'Escape') {
|
|
50977
|
+
event.preventDefault();
|
|
50978
|
+
this.cancelEdit();
|
|
50979
|
+
}
|
|
50980
|
+
}
|
|
50981
|
+
/** Commit the current draft through EditorStateService (blur handler). */
|
|
50982
|
+
commitEdit() {
|
|
50983
|
+
if (this.editSettled) {
|
|
50984
|
+
this.editSettled = false;
|
|
50985
|
+
return;
|
|
50986
|
+
}
|
|
50987
|
+
const edit = this.editState();
|
|
50988
|
+
if (!edit) {
|
|
50989
|
+
return;
|
|
50990
|
+
}
|
|
50991
|
+
const text = this.draftText;
|
|
50992
|
+
this.editState.set(null);
|
|
50993
|
+
this.applyCommit(edit.nodeId, text);
|
|
50994
|
+
}
|
|
50995
|
+
/** Discard the current edit without committing. */
|
|
50996
|
+
cancelEdit() {
|
|
50997
|
+
// Mark as settled so the DOM-removal blur does not commit the cancelled edit.
|
|
50998
|
+
this.editSettled = true;
|
|
50999
|
+
this.editState.set(null);
|
|
51000
|
+
}
|
|
51001
|
+
applyCommit(nodeId, text) {
|
|
51002
|
+
const data = this.smartArtData();
|
|
51003
|
+
if (!data || !this.editor) {
|
|
51004
|
+
return;
|
|
51005
|
+
}
|
|
51006
|
+
const next = commitNodeText(data, nodeId, text);
|
|
51007
|
+
if (next === data) {
|
|
51008
|
+
return;
|
|
51009
|
+
} // no-op: text unchanged
|
|
51010
|
+
const slideIndex = findSlideIndexByElementId(this.editor.slides(), this.element().id);
|
|
51011
|
+
if (slideIndex < 0) {
|
|
51012
|
+
return;
|
|
51013
|
+
}
|
|
51014
|
+
this.editor.updateElement(slideIndex, this.element().id, {
|
|
51015
|
+
smartArtData: next,
|
|
51016
|
+
});
|
|
51017
|
+
}
|
|
50444
51018
|
ngOnDestroy() {
|
|
50445
51019
|
this.handle?.dispose();
|
|
50446
51020
|
this.handle = null;
|
|
50447
51021
|
}
|
|
50448
51022
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: SmartArt3DRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
50449
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: SmartArt3DRendererComponent, isStandalone: true, selector: "pptx-smart-art-3d-renderer", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "canvas", first: true, predicate: ["canvas"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
51023
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: SmartArt3DRendererComponent, isStandalone: true, selector: "pptx-smart-art-3d-renderer", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, canEdit: { classPropertyName: "canEdit", publicName: "canEdit", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "canvas", first: true, predicate: ["canvas"], descendants: true, isSignal: true }, { propertyName: "containerEl", first: true, predicate: ["container3d"], descendants: true, isSignal: true }, { propertyName: "nodeEditor3d", first: true, predicate: ["nodeEditor3d"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
50450
51024
|
@if (useFallback()) {
|
|
50451
51025
|
<pptx-smart-art-renderer [element]="element()" [zIndex]="zIndex()" />
|
|
50452
51026
|
} @else {
|
|
50453
51027
|
<div
|
|
51028
|
+
#container3d
|
|
50454
51029
|
class="pptx-ng-element pptx-ng-smartart-3d"
|
|
50455
51030
|
[ngStyle]="containerStyle()"
|
|
50456
51031
|
[attr.data-element-id]="element().id"
|
|
50457
51032
|
>
|
|
50458
51033
|
<canvas #canvas class="pptx-ng-smartart-3d-canvas"></canvas>
|
|
51034
|
+
@if (canEdit()) {
|
|
51035
|
+
<!-- Invisible SVG overlay: provides data-smartart-node-id hit targets -->
|
|
51036
|
+
<div class="pptx-ng-smartart-3d-hittest" (dblclick)="onOverlayDblClick($event)">
|
|
51037
|
+
<pptx-smart-art-renderer [element]="element()" [editable]="false" [zIndex]="0" />
|
|
51038
|
+
</div>
|
|
51039
|
+
@if (editState()) {
|
|
51040
|
+
<textarea
|
|
51041
|
+
#nodeEditor3d
|
|
51042
|
+
class="pptx-ng-smartart-3d-node-editor"
|
|
51043
|
+
[style.left.px]="editState()!.box.x"
|
|
51044
|
+
[style.top.px]="editState()!.box.y"
|
|
51045
|
+
[style.width.px]="editState()!.box.width"
|
|
51046
|
+
[style.height.px]="editState()!.box.height"
|
|
51047
|
+
[value]="editState()!.text"
|
|
51048
|
+
spellcheck="false"
|
|
51049
|
+
aria-label="Edit SmartArt node text"
|
|
51050
|
+
(input)="updateDraft($event)"
|
|
51051
|
+
(blur)="commitEdit()"
|
|
51052
|
+
(keydown)="onEditorKeydown($event)"
|
|
51053
|
+
(mousedown)="$event.stopPropagation()"
|
|
51054
|
+
(click)="$event.stopPropagation()"
|
|
51055
|
+
(dblclick)="$event.stopPropagation()"
|
|
51056
|
+
></textarea>
|
|
51057
|
+
}
|
|
51058
|
+
}
|
|
50459
51059
|
</div>
|
|
50460
51060
|
}
|
|
50461
|
-
`, isInline: true, styles: [".pptx-ng-smartart-3d-canvas{width:100%;height:100%;display:block}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: SmartArtRendererComponent, selector: "pptx-smart-art-renderer", inputs: ["element", "zIndex", "editable"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
51061
|
+
`, isInline: true, styles: [".pptx-ng-smartart-3d-canvas{width:100%;height:100%;display:block}.pptx-ng-smartart-3d-hittest{position:absolute;inset:0;opacity:0;pointer-events:auto}.pptx-ng-smartart-3d-node-editor{position:absolute;box-sizing:border-box;margin:0;padding:1px 2px;border:1px solid var(--pptx-inspector-active, #0078d4);border-radius:2px;background:#fff;color:#111;font-size:11px;line-height:1.1;text-align:center;resize:none;overflow:hidden;z-index:20;outline:none}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: SmartArtRendererComponent, selector: "pptx-smart-art-renderer", inputs: ["element", "zIndex", "editable"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
50462
51062
|
}
|
|
50463
51063
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: SmartArt3DRendererComponent, decorators: [{
|
|
50464
51064
|
type: Component,
|
|
@@ -50467,15 +51067,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
50467
51067
|
<pptx-smart-art-renderer [element]="element()" [zIndex]="zIndex()" />
|
|
50468
51068
|
} @else {
|
|
50469
51069
|
<div
|
|
51070
|
+
#container3d
|
|
50470
51071
|
class="pptx-ng-element pptx-ng-smartart-3d"
|
|
50471
51072
|
[ngStyle]="containerStyle()"
|
|
50472
51073
|
[attr.data-element-id]="element().id"
|
|
50473
51074
|
>
|
|
50474
51075
|
<canvas #canvas class="pptx-ng-smartart-3d-canvas"></canvas>
|
|
51076
|
+
@if (canEdit()) {
|
|
51077
|
+
<!-- Invisible SVG overlay: provides data-smartart-node-id hit targets -->
|
|
51078
|
+
<div class="pptx-ng-smartart-3d-hittest" (dblclick)="onOverlayDblClick($event)">
|
|
51079
|
+
<pptx-smart-art-renderer [element]="element()" [editable]="false" [zIndex]="0" />
|
|
51080
|
+
</div>
|
|
51081
|
+
@if (editState()) {
|
|
51082
|
+
<textarea
|
|
51083
|
+
#nodeEditor3d
|
|
51084
|
+
class="pptx-ng-smartart-3d-node-editor"
|
|
51085
|
+
[style.left.px]="editState()!.box.x"
|
|
51086
|
+
[style.top.px]="editState()!.box.y"
|
|
51087
|
+
[style.width.px]="editState()!.box.width"
|
|
51088
|
+
[style.height.px]="editState()!.box.height"
|
|
51089
|
+
[value]="editState()!.text"
|
|
51090
|
+
spellcheck="false"
|
|
51091
|
+
aria-label="Edit SmartArt node text"
|
|
51092
|
+
(input)="updateDraft($event)"
|
|
51093
|
+
(blur)="commitEdit()"
|
|
51094
|
+
(keydown)="onEditorKeydown($event)"
|
|
51095
|
+
(mousedown)="$event.stopPropagation()"
|
|
51096
|
+
(click)="$event.stopPropagation()"
|
|
51097
|
+
(dblclick)="$event.stopPropagation()"
|
|
51098
|
+
></textarea>
|
|
51099
|
+
}
|
|
51100
|
+
}
|
|
50475
51101
|
</div>
|
|
50476
51102
|
}
|
|
50477
|
-
`, styles: [".pptx-ng-smartart-3d-canvas{width:100%;height:100%;display:block}\n"] }]
|
|
50478
|
-
}], ctorParameters: () => [], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }], canvas: [{ type: i0.ViewChild, args: ['canvas', { isSignal: true }] }] } });
|
|
51103
|
+
`, styles: [".pptx-ng-smartart-3d-canvas{width:100%;height:100%;display:block}.pptx-ng-smartart-3d-hittest{position:absolute;inset:0;opacity:0;pointer-events:auto}.pptx-ng-smartart-3d-node-editor{position:absolute;box-sizing:border-box;margin:0;padding:1px 2px;border:1px solid var(--pptx-inspector-active, #0078d4);border-radius:2px;background:#fff;color:#111;font-size:11px;line-height:1.1;text-align:center;resize:none;overflow:hidden;z-index:20;outline:none}\n"] }]
|
|
51104
|
+
}], ctorParameters: () => [], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }], canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], canvas: [{ type: i0.ViewChild, args: ['canvas', { isSignal: true }] }], containerEl: [{ type: i0.ViewChild, args: ['container3d', { isSignal: true }] }], nodeEditor3d: [{ type: i0.ViewChild, args: ['nodeEditor3d', { isSignal: true }] }] } });
|
|
50479
51105
|
|
|
50480
51106
|
/**
|
|
50481
51107
|
* Opt-in flag for the Three.js SmartArt renderer (Angular).
|
|
@@ -51234,19 +51860,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
51234
51860
|
type: Injectable
|
|
51235
51861
|
}] });
|
|
51236
51862
|
|
|
51863
|
+
/** Fallback tile background when the target slide has no background colour. */
|
|
51864
|
+
const FALLBACK_THUMBNAIL_BG = '#f0f0f0';
|
|
51237
51865
|
/**
|
|
51238
51866
|
* Build the complete zoom view-model for a given element.
|
|
51867
|
+
*
|
|
51868
|
+
* When `targetInfo` is supplied (the target slide was resolved from the deck),
|
|
51869
|
+
* the fallback-thumbnail fields use the real target slide: its `backgroundColor`
|
|
51870
|
+
* as the tile background, `Slide ${slideNumber}` as the label, and its friendly
|
|
51871
|
+
* `sectionName` as the caption. This mirrors React's `ZoomSlideThumbnail`. When
|
|
51872
|
+
* it is absent the old fallback applies: grey background, `Slide ${index + 1}`,
|
|
51873
|
+
* and the raw `targetSectionId`.
|
|
51874
|
+
*
|
|
51239
51875
|
* Returns sensible defaults for non-zoom elements (all derived strings are
|
|
51240
51876
|
* empty/fallback values, `zoom` is `undefined`).
|
|
51241
51877
|
*/
|
|
51242
|
-
function buildZoomViewModel(element) {
|
|
51878
|
+
function buildZoomViewModel(element, targetInfo) {
|
|
51243
51879
|
const zoom = isZoomElement(element) ? element : undefined;
|
|
51244
51880
|
const previewSrc = zoom?.imageData;
|
|
51245
51881
|
const targetSlideIndex = zoom?.targetSlideIndex ?? 0;
|
|
51246
51882
|
const zoomType = zoom?.zoomType ?? 'slide';
|
|
51247
51883
|
const targetSectionId = zoom?.targetSectionId;
|
|
51248
51884
|
const badgeText = zoomType === 'section' ? 'Section Zoom' : 'Slide Zoom';
|
|
51249
|
-
const slideLabel =
|
|
51885
|
+
const slideLabel = targetInfo?.slideNumber !== undefined
|
|
51886
|
+
? `Slide ${targetInfo.slideNumber}`
|
|
51887
|
+
: `Slide ${targetSlideIndex + 1}`;
|
|
51888
|
+
const thumbnailBackground = targetInfo?.backgroundColor ?? FALLBACK_THUMBNAIL_BG;
|
|
51889
|
+
const sectionCaption = targetInfo?.sectionName ?? targetSectionId;
|
|
51250
51890
|
let ariaLabel = `Zoom to slide ${targetSlideIndex + 1}`;
|
|
51251
51891
|
if (zoomType === 'section' && targetSectionId) {
|
|
51252
51892
|
ariaLabel = `${ariaLabel} (section: ${targetSectionId})`;
|
|
@@ -51260,8 +51900,17 @@ function buildZoomViewModel(element) {
|
|
|
51260
51900
|
badgeText,
|
|
51261
51901
|
slideLabel,
|
|
51262
51902
|
ariaLabel,
|
|
51903
|
+
thumbnailBackground,
|
|
51904
|
+
sectionCaption,
|
|
51263
51905
|
};
|
|
51264
51906
|
}
|
|
51907
|
+
/**
|
|
51908
|
+
* Resolve a zoom element's zero-based target slide index, or `0` for non-zoom
|
|
51909
|
+
* elements. Used to look the target slide up before building the view model.
|
|
51910
|
+
*/
|
|
51911
|
+
function zoomTargetSlideIndex(element) {
|
|
51912
|
+
return isZoomElement(element) ? element.targetSlideIndex : 0;
|
|
51913
|
+
}
|
|
51265
51914
|
/** Wrapper `[ngStyle]`-compatible style for the zoom container `<div>`. */
|
|
51266
51915
|
function buildZoomContainerStyle(element, zIndex) {
|
|
51267
51916
|
return getContainerStyle(element, zIndex);
|
|
@@ -51274,6 +51923,49 @@ function isZoomActivationKey(key) {
|
|
|
51274
51923
|
return key === 'Enter' || key === ' ';
|
|
51275
51924
|
}
|
|
51276
51925
|
|
|
51926
|
+
/**
|
|
51927
|
+
* ZoomTargetService: viewer-scoped lookup from a zoom element's target slide
|
|
51928
|
+
* index to a {@link ZoomTargetInfo} descriptor.
|
|
51929
|
+
*
|
|
51930
|
+
* Provided by `PowerPointViewerComponent` from its loaded slides, then injected
|
|
51931
|
+
* `{ optional: true }` by `ZoomRendererComponent`. Trees that do not provide it
|
|
51932
|
+
* (e.g. isolated component tests) resolve `null`, so the renderer falls back to
|
|
51933
|
+
* the old grey/index/GUID thumbnail. This mirrors the optional-DI pattern of
|
|
51934
|
+
* {@link ZoomNavigationService}.
|
|
51935
|
+
*
|
|
51936
|
+
* Intentionally NOT `providedIn: 'root'`: it is supplied per viewer so the
|
|
51937
|
+
* lookup always reflects that viewer's deck.
|
|
51938
|
+
*/
|
|
51939
|
+
class ZoomTargetService {
|
|
51940
|
+
/** The deck the lookup resolves against; seeded by the viewer. */
|
|
51941
|
+
slides = signal([], /* @ts-ignore */
|
|
51942
|
+
...(ngDevMode ? [{ debugName: "slides" }] : /* istanbul ignore next */ []));
|
|
51943
|
+
/** Replace the deck used to resolve zoom targets. */
|
|
51944
|
+
setSlides(slides) {
|
|
51945
|
+
this.slides.set(slides);
|
|
51946
|
+
}
|
|
51947
|
+
/**
|
|
51948
|
+
* Resolve the descriptor for a zoom's target slide, or `undefined` when the
|
|
51949
|
+
* index is out of range (so the renderer keeps its index-based fallback).
|
|
51950
|
+
*/
|
|
51951
|
+
lookup(targetSlideIndex) {
|
|
51952
|
+
const slide = this.slides()[targetSlideIndex];
|
|
51953
|
+
if (!slide) {
|
|
51954
|
+
return undefined;
|
|
51955
|
+
}
|
|
51956
|
+
return {
|
|
51957
|
+
backgroundColor: slide.backgroundColor,
|
|
51958
|
+
slideNumber: slide.slideNumber,
|
|
51959
|
+
sectionName: slide.sectionName,
|
|
51960
|
+
};
|
|
51961
|
+
}
|
|
51962
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ZoomTargetService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
51963
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ZoomTargetService });
|
|
51964
|
+
}
|
|
51965
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ZoomTargetService, decorators: [{
|
|
51966
|
+
type: Injectable
|
|
51967
|
+
}] });
|
|
51968
|
+
|
|
51277
51969
|
/**
|
|
51278
51970
|
* ZoomRendererComponent: Angular port of the Vue `ZoomRenderer.vue`
|
|
51279
51971
|
* (and the React `ZoomElementRenderer`), static viewer-first subset.
|
|
@@ -51286,9 +51978,15 @@ function isZoomActivationKey(key) {
|
|
|
51286
51978
|
* In presentation mode the overlay provides a {@link ZoomNavigationService}, so
|
|
51287
51979
|
* clicking (or Enter/Space) jumps to the target slide. Outside presentation mode
|
|
51288
51980
|
* the service is not provided (optional injection yields `null`) and the tile
|
|
51289
|
-
* stays a static link, exactly as before.
|
|
51290
|
-
*
|
|
51291
|
-
*
|
|
51981
|
+
* stays a static link, exactly as before.
|
|
51982
|
+
*
|
|
51983
|
+
* The fallback thumbnail (no embedded preview image) matches React's
|
|
51984
|
+
* `ZoomSlideThumbnail`: when a {@link ZoomTargetService} is provided (by the
|
|
51985
|
+
* viewer), it looks up the target slide and uses that slide's real background
|
|
51986
|
+
* colour, its own 1-based number, and its friendly section name. A live
|
|
51987
|
+
* mini-rendering of the target slide is intentionally NOT drawn. When the
|
|
51988
|
+
* service is absent the tile keeps the neutral grey / index / section-GUID
|
|
51989
|
+
* fallback.
|
|
51292
51990
|
*
|
|
51293
51991
|
* All non-trivial pure computation lives in `zoom-renderer-helpers.ts` (no
|
|
51294
51992
|
* Angular dependency) so it can be unit-tested without TestBed.
|
|
@@ -51302,7 +52000,17 @@ class ZoomRendererComponent {
|
|
|
51302
52000
|
...(ngDevMode ? [{ debugName: "mediaDataUrls" }] : /* istanbul ignore next */ []));
|
|
51303
52001
|
containerStyle = computed(() => buildZoomContainerStyle(this.element(), this.zIndex()), /* @ts-ignore */
|
|
51304
52002
|
...(ngDevMode ? [{ debugName: "containerStyle" }] : /* istanbul ignore next */ []));
|
|
51305
|
-
|
|
52003
|
+
/**
|
|
52004
|
+
* Target-slide lookup, provided by the viewer. `null` in trees that do not
|
|
52005
|
+
* provide it (e.g. isolated component tests), where the fallback thumbnail
|
|
52006
|
+
* stays on the neutral grey / index / section-GUID placeholder.
|
|
52007
|
+
*/
|
|
52008
|
+
zoomTarget = inject(ZoomTargetService, { optional: true });
|
|
52009
|
+
vm = computed(() => {
|
|
52010
|
+
const element = this.element();
|
|
52011
|
+
const targetSlideIndex = zoomTargetSlideIndex(element);
|
|
52012
|
+
return buildZoomViewModel(element, this.zoomTarget?.lookup(targetSlideIndex));
|
|
52013
|
+
}, /* @ts-ignore */
|
|
51306
52014
|
...(ngDevMode ? [{ debugName: "vm" }] : /* istanbul ignore next */ []));
|
|
51307
52015
|
/**
|
|
51308
52016
|
* Zoom-navigation context, present only inside a running presentation (the
|
|
@@ -51364,13 +52072,14 @@ class ZoomRendererComponent {
|
|
|
51364
52072
|
/>
|
|
51365
52073
|
} @else {
|
|
51366
52074
|
<div
|
|
51367
|
-
style="width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;
|
|
52075
|
+
style="width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;border:1px solid rgba(0,0,0,0.1);box-sizing:border-box"
|
|
52076
|
+
[style.background-color]="vm().thumbnailBackground"
|
|
51368
52077
|
>
|
|
51369
52078
|
<div style="font-size:14px;font-weight:600;color:rgba(0,0,0,0.5);margin-bottom:4px">
|
|
51370
52079
|
{{ vm().slideLabel }}
|
|
51371
52080
|
</div>
|
|
51372
|
-
@if (vm().
|
|
51373
|
-
<div style="font-size:10px;color:rgba(0,0,0,0.4)">{{ vm().
|
|
52081
|
+
@if (vm().sectionCaption) {
|
|
52082
|
+
<div style="font-size:10px;color:rgba(0,0,0,0.4)">{{ vm().sectionCaption }}</div>
|
|
51374
52083
|
}
|
|
51375
52084
|
</div>
|
|
51376
52085
|
}
|
|
@@ -51412,13 +52121,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
51412
52121
|
/>
|
|
51413
52122
|
} @else {
|
|
51414
52123
|
<div
|
|
51415
|
-
style="width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;
|
|
52124
|
+
style="width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;border:1px solid rgba(0,0,0,0.1);box-sizing:border-box"
|
|
52125
|
+
[style.background-color]="vm().thumbnailBackground"
|
|
51416
52126
|
>
|
|
51417
52127
|
<div style="font-size:14px;font-weight:600;color:rgba(0,0,0,0.5);margin-bottom:4px">
|
|
51418
52128
|
{{ vm().slideLabel }}
|
|
51419
52129
|
</div>
|
|
51420
|
-
@if (vm().
|
|
51421
|
-
<div style="font-size:10px;color:rgba(0,0,0,0.4)">{{ vm().
|
|
52130
|
+
@if (vm().sectionCaption) {
|
|
52131
|
+
<div style="font-size:10px;color:rgba(0,0,0,0.4)">{{ vm().sectionCaption }}</div>
|
|
51422
52132
|
}
|
|
51423
52133
|
</div>
|
|
51424
52134
|
}
|
|
@@ -51741,7 +52451,11 @@ class ElementRendererComponent {
|
|
|
51741
52451
|
/>
|
|
51742
52452
|
}
|
|
51743
52453
|
@case (element().type === 'smartArt' && smartArt3D()) {
|
|
51744
|
-
<pptx-smart-art-3d-renderer
|
|
52454
|
+
<pptx-smart-art-3d-renderer
|
|
52455
|
+
[element]="element()"
|
|
52456
|
+
[zIndex]="zIndex()"
|
|
52457
|
+
[canEdit]="interactive() && editable()"
|
|
52458
|
+
/>
|
|
51745
52459
|
}
|
|
51746
52460
|
@case (element().type === 'smartArt') {
|
|
51747
52461
|
<div
|
|
@@ -51982,7 +52696,7 @@ class ElementRendererComponent {
|
|
|
51982
52696
|
</defs>
|
|
51983
52697
|
</svg>
|
|
51984
52698
|
}
|
|
51985
|
-
`, isInline: true, dependencies: [{ kind: "component", type: ElementRendererComponent, selector: "pptx-element-renderer", inputs: ["element", "mediaDataUrls", "zIndex", "obstacles", "canvasWidth", "canvasHeight", "interactive", "editable", "fieldContext", "editTemplateMode"], outputs: ["cellCommit"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ConnectorRendererComponent, selector: "pptx-connector-renderer", inputs: ["element", "zIndex", "obstacles", "canvasWidth", "canvasHeight", "interactive"] }, { kind: "component", type: TableRendererComponent, selector: "pptx-table-renderer", inputs: ["element", "editable"], outputs: ["cellCommit"] }, { kind: "component", type: ChartRendererComponent, selector: "pptx-chart-renderer", inputs: ["element"] }, { kind: "component", type: SmartArtRendererComponent, selector: "pptx-smart-art-renderer", inputs: ["element", "zIndex", "editable"] }, { kind: "component", type: SmartArt3DRendererComponent, selector: "pptx-smart-art-3d-renderer", inputs: ["element", "zIndex"] }, { kind: "component", type: InkRendererComponent, selector: "pptx-ink-renderer", inputs: ["element", "zIndex", "mediaDataUrls"] }, { kind: "component", type: OleRendererComponent, selector: "pptx-ole-renderer", inputs: ["element", "zIndex"] }, { kind: "component", type: Model3DRendererComponent, selector: "pptx-model3d-renderer", inputs: ["element", "zIndex", "mediaDataUrls", "interactive"] }, { kind: "component", type: ZoomRendererComponent, selector: "pptx-zoom-renderer", inputs: ["element", "zIndex", "mediaDataUrls"] }, { kind: "component", type: EquationRendererComponent, selector: "pptx-equation-renderer", inputs: ["equationXml", "equationNumber"] }, { kind: "component", type: ColorChangedImageComponent, selector: "pptx-color-changed-image", inputs: ["src", "clrChange", "alt", "imgClass", "imgStyle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
52699
|
+
`, isInline: true, dependencies: [{ kind: "component", type: ElementRendererComponent, selector: "pptx-element-renderer", inputs: ["element", "mediaDataUrls", "zIndex", "obstacles", "canvasWidth", "canvasHeight", "interactive", "editable", "fieldContext", "editTemplateMode"], outputs: ["cellCommit"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ConnectorRendererComponent, selector: "pptx-connector-renderer", inputs: ["element", "zIndex", "obstacles", "canvasWidth", "canvasHeight", "interactive"] }, { kind: "component", type: TableRendererComponent, selector: "pptx-table-renderer", inputs: ["element", "editable"], outputs: ["cellCommit"] }, { kind: "component", type: ChartRendererComponent, selector: "pptx-chart-renderer", inputs: ["element"] }, { kind: "component", type: SmartArtRendererComponent, selector: "pptx-smart-art-renderer", inputs: ["element", "zIndex", "editable"] }, { kind: "component", type: SmartArt3DRendererComponent, selector: "pptx-smart-art-3d-renderer", inputs: ["element", "zIndex", "canEdit"] }, { kind: "component", type: InkRendererComponent, selector: "pptx-ink-renderer", inputs: ["element", "zIndex", "mediaDataUrls"] }, { kind: "component", type: OleRendererComponent, selector: "pptx-ole-renderer", inputs: ["element", "zIndex"] }, { kind: "component", type: Model3DRendererComponent, selector: "pptx-model3d-renderer", inputs: ["element", "zIndex", "mediaDataUrls", "interactive"] }, { kind: "component", type: ZoomRendererComponent, selector: "pptx-zoom-renderer", inputs: ["element", "zIndex", "mediaDataUrls"] }, { kind: "component", type: EquationRendererComponent, selector: "pptx-equation-renderer", inputs: ["equationXml", "equationNumber"] }, { kind: "component", type: ColorChangedImageComponent, selector: "pptx-color-changed-image", inputs: ["src", "clrChange", "alt", "imgClass", "imgStyle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
51986
52700
|
}
|
|
51987
52701
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ElementRendererComponent, decorators: [{
|
|
51988
52702
|
type: Component,
|
|
@@ -52038,7 +52752,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
52038
52752
|
/>
|
|
52039
52753
|
}
|
|
52040
52754
|
@case (element().type === 'smartArt' && smartArt3D()) {
|
|
52041
|
-
<pptx-smart-art-3d-renderer
|
|
52755
|
+
<pptx-smart-art-3d-renderer
|
|
52756
|
+
[element]="element()"
|
|
52757
|
+
[zIndex]="zIndex()"
|
|
52758
|
+
[canEdit]="interactive() && editable()"
|
|
52759
|
+
/>
|
|
52042
52760
|
}
|
|
52043
52761
|
@case (element().type === 'smartArt') {
|
|
52044
52762
|
<div
|
|
@@ -58974,6 +59692,9 @@ class RibbonComponent {
|
|
|
58974
59692
|
hasSel() {
|
|
58975
59693
|
return this.editor.selectedIds().length > 0;
|
|
58976
59694
|
}
|
|
59695
|
+
canDistribute() {
|
|
59696
|
+
return this.editor.selectedIds().length >= 3;
|
|
59697
|
+
}
|
|
58977
59698
|
isText() {
|
|
58978
59699
|
const el = this.selectedElement();
|
|
58979
59700
|
return el !== null && hasTextProperties(el);
|
|
@@ -59577,6 +60298,28 @@ class RibbonComponent {
|
|
|
59577
60298
|
</button>
|
|
59578
60299
|
</div>
|
|
59579
60300
|
<span class="pptx-rb-sep"></span>
|
|
60301
|
+
<!-- Distribute -->
|
|
60302
|
+
<div class="pptx-rb-grp">
|
|
60303
|
+
<button
|
|
60304
|
+
type="button"
|
|
60305
|
+
class="pptx-rb-gb"
|
|
60306
|
+
[disabled]="!canDistribute()"
|
|
60307
|
+
title="Distribute horizontally"
|
|
60308
|
+
(click)="editor.distributeSelected(slideIndex(), 'horizontal')"
|
|
60309
|
+
>
|
|
60310
|
+
↔ H
|
|
60311
|
+
</button>
|
|
60312
|
+
<button
|
|
60313
|
+
type="button"
|
|
60314
|
+
class="pptx-rb-gl"
|
|
60315
|
+
[disabled]="!canDistribute()"
|
|
60316
|
+
title="Distribute vertically"
|
|
60317
|
+
(click)="editor.distributeSelected(slideIndex(), 'vertical')"
|
|
60318
|
+
>
|
|
60319
|
+
↕ V
|
|
60320
|
+
</button>
|
|
60321
|
+
</div>
|
|
60322
|
+
<span class="pptx-rb-sep"></span>
|
|
59580
60323
|
<!-- Group / edit -->
|
|
59581
60324
|
<div class="pptx-rb-grp">
|
|
59582
60325
|
<button
|
|
@@ -60557,6 +61300,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
60557
61300
|
</button>
|
|
60558
61301
|
</div>
|
|
60559
61302
|
<span class="pptx-rb-sep"></span>
|
|
61303
|
+
<!-- Distribute -->
|
|
61304
|
+
<div class="pptx-rb-grp">
|
|
61305
|
+
<button
|
|
61306
|
+
type="button"
|
|
61307
|
+
class="pptx-rb-gb"
|
|
61308
|
+
[disabled]="!canDistribute()"
|
|
61309
|
+
title="Distribute horizontally"
|
|
61310
|
+
(click)="editor.distributeSelected(slideIndex(), 'horizontal')"
|
|
61311
|
+
>
|
|
61312
|
+
↔ H
|
|
61313
|
+
</button>
|
|
61314
|
+
<button
|
|
61315
|
+
type="button"
|
|
61316
|
+
class="pptx-rb-gl"
|
|
61317
|
+
[disabled]="!canDistribute()"
|
|
61318
|
+
title="Distribute vertically"
|
|
61319
|
+
(click)="editor.distributeSelected(slideIndex(), 'vertical')"
|
|
61320
|
+
>
|
|
61321
|
+
↕ V
|
|
61322
|
+
</button>
|
|
61323
|
+
</div>
|
|
61324
|
+
<span class="pptx-rb-sep"></span>
|
|
60560
61325
|
<!-- Group / edit -->
|
|
60561
61326
|
<div class="pptx-rb-grp">
|
|
60562
61327
|
<button
|
|
@@ -62743,6 +63508,7 @@ class PowerPointViewerComponent {
|
|
|
62743
63508
|
print = inject(PrintService);
|
|
62744
63509
|
mobile = inject(IsMobileService);
|
|
62745
63510
|
smartArt3DSvc = inject(SmartArt3DService);
|
|
63511
|
+
zoomTarget = inject(ZoomTargetService);
|
|
62746
63512
|
/** The `<main>` host; used to locate the live `.pptx-ng-canvas-stage`. */
|
|
62747
63513
|
mainEl = viewChild('mainEl', /* @ts-ignore */
|
|
62748
63514
|
...(ngDevMode ? [{ debugName: "mainEl" }] : /* istanbul ignore next */ []));
|
|
@@ -63121,6 +63887,12 @@ class PowerPointViewerComponent {
|
|
|
63121
63887
|
effect(() => {
|
|
63122
63888
|
this.accessibility.setSlides([...this.mergedSlides()]);
|
|
63123
63889
|
});
|
|
63890
|
+
// Feed the deck to the zoom-target lookup so a zoom tile's fallback
|
|
63891
|
+
// thumbnail can resolve its target slide's background / number / section
|
|
63892
|
+
// name (mirrors React's ZoomSlideThumbnail).
|
|
63893
|
+
effect(() => {
|
|
63894
|
+
this.zoomTarget.setSlides(this.mergedSlides());
|
|
63895
|
+
});
|
|
63124
63896
|
// Connect / disconnect real-time collaboration when the host config changes.
|
|
63125
63897
|
effect(() => {
|
|
63126
63898
|
const config = this.collaboration();
|
|
@@ -63526,32 +64298,40 @@ class PowerPointViewerComponent {
|
|
|
63526
64298
|
this.editor.deleteSelected(this.activeSlideIndex());
|
|
63527
64299
|
}
|
|
63528
64300
|
/**
|
|
63529
|
-
* Activate the
|
|
64301
|
+
* Activate the eyedropper to pick a colour from the screen. Uses the native
|
|
64302
|
+
* EyeDropper API where available (Chrome/Edge); on Firefox/Safari it falls
|
|
64303
|
+
* back to a one-shot click that samples the slide DOM under the pointer.
|
|
63530
64304
|
* When a shape/text/connector/image element is selected, applies the colour
|
|
63531
|
-
* to its fill
|
|
63532
|
-
*
|
|
64305
|
+
* to its fill; otherwise copies it to the clipboard. No-ops when the user
|
|
64306
|
+
* cancels (Escape) or nothing paintable is under the pointer.
|
|
63533
64307
|
*/
|
|
63534
64308
|
async onToggleEyedropper() {
|
|
63535
64309
|
this.eyedropperActive.set(true);
|
|
63536
64310
|
try {
|
|
63537
|
-
const color =
|
|
64311
|
+
const color = eyedropperAvailable()
|
|
64312
|
+
? await openNativeEyeDropper()
|
|
64313
|
+
: await pickColorByClickFallback();
|
|
63538
64314
|
if (color) {
|
|
63539
|
-
|
|
63540
|
-
const idx = this.activeSlideIndex();
|
|
63541
|
-
if (sel !== null && hasShapeProperties(sel)) {
|
|
63542
|
-
this.editor.updateElement(idx, sel.id, {
|
|
63543
|
-
shapeStyle: { ...sel.shapeStyle, fillColor: color },
|
|
63544
|
-
});
|
|
63545
|
-
}
|
|
63546
|
-
else {
|
|
63547
|
-
await navigator.clipboard.writeText(color).catch(() => undefined);
|
|
63548
|
-
}
|
|
64315
|
+
await this.applyEyedropperColor(color);
|
|
63549
64316
|
}
|
|
63550
64317
|
}
|
|
63551
64318
|
finally {
|
|
63552
64319
|
this.eyedropperActive.set(false);
|
|
63553
64320
|
}
|
|
63554
64321
|
}
|
|
64322
|
+
/** Apply a picked colour to the selected shape's fill, else copy to clipboard. */
|
|
64323
|
+
async applyEyedropperColor(color) {
|
|
64324
|
+
const sel = this.selectedElement();
|
|
64325
|
+
const idx = this.activeSlideIndex();
|
|
64326
|
+
if (sel !== null && hasShapeProperties(sel)) {
|
|
64327
|
+
this.editor.updateElement(idx, sel.id, {
|
|
64328
|
+
shapeStyle: { ...sel.shapeStyle, fillColor: color },
|
|
64329
|
+
});
|
|
64330
|
+
}
|
|
64331
|
+
else {
|
|
64332
|
+
await navigator.clipboard.writeText(color).catch(() => undefined);
|
|
64333
|
+
}
|
|
64334
|
+
}
|
|
63555
64335
|
/** Append a comment to the active slide (one history entry). */
|
|
63556
64336
|
onCommentAdd(text) {
|
|
63557
64337
|
const next = addCommentToList(this.activeComments(), text, 'You');
|
|
@@ -64034,6 +64814,7 @@ class PowerPointViewerComponent {
|
|
|
64034
64814
|
IsMobileService,
|
|
64035
64815
|
SmartArt3DService,
|
|
64036
64816
|
FieldContextService,
|
|
64817
|
+
ZoomTargetService,
|
|
64037
64818
|
], viewQueries: [{ propertyName: "mainEl", first: true, predicate: ["mainEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
64038
64819
|
<div class="pptx-ng-viewer" [ngClass]="class()" [ngStyle]="rootStyle()">
|
|
64039
64820
|
@if (loader.loading()) {
|
|
@@ -64576,6 +65357,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
64576
65357
|
IsMobileService,
|
|
64577
65358
|
SmartArt3DService,
|
|
64578
65359
|
FieldContextService,
|
|
65360
|
+
ZoomTargetService,
|
|
64579
65361
|
],
|
|
64580
65362
|
imports: [
|
|
64581
65363
|
NgClass,
|