pptx-angular-viewer 1.1.41 → 1.1.43
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,6 +1,6 @@
|
|
|
1
1
|
import { NgStyle, NgClass, NgTemplateOutlet } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { InjectionToken, input, output, computed, ChangeDetectionStrategy, Component, signal, Injectable,
|
|
3
|
+
import { InjectionToken, input, output, computed, ChangeDetectionStrategy, Component, signal, Injectable, inject, DestroyRef, HostListener, effect, ElementRef, viewChild, afterNextRender, Injector } from '@angular/core';
|
|
4
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';
|
|
@@ -23966,6 +23966,516 @@ function buildSmartArt3DModel(layout, options = {}) {
|
|
|
23966
23966
|
return options.spatial ? applySpatialLayout(model) : model;
|
|
23967
23967
|
}
|
|
23968
23968
|
|
|
23969
|
+
/**
|
|
23970
|
+
* Vanilla three.js GLTF/GLB model scene controller (framework-agnostic).
|
|
23971
|
+
*
|
|
23972
|
+
* Mounts an interactive 3D model into a caller-provided container element:
|
|
23973
|
+
* dynamically imports `three` plus its `GLTFLoader` and `OrbitControls` addons,
|
|
23974
|
+
* builds a scene (perspective camera, lights), auto-centres and fits the model
|
|
23975
|
+
* to the view via a `Box3`, renders with a RAF loop, and exposes `resize()` /
|
|
23976
|
+
* `dispose()` for deterministic teardown of GPU resources.
|
|
23977
|
+
*
|
|
23978
|
+
* `three` is an OPTIONAL peer dependency: every import is dynamic and guarded.
|
|
23979
|
+
* When `three` is not installed, {@link mountModel3D} resolves to a sentinel
|
|
23980
|
+
* handle ({@link THREE_UNAVAILABLE}) so the caller can fall back to a poster.
|
|
23981
|
+
* No framework imports - the React, Vue, and Angular bindings can all mount it.
|
|
23982
|
+
*/
|
|
23983
|
+
/**
|
|
23984
|
+
* Sentinel handle returned when `three` is unavailable or the model fails to
|
|
23985
|
+
* load. All methods are no-ops; `ok` is `false` so callers can show a poster.
|
|
23986
|
+
*/
|
|
23987
|
+
const THREE_UNAVAILABLE = {
|
|
23988
|
+
ok: false,
|
|
23989
|
+
resize: () => { },
|
|
23990
|
+
setInteractive: () => { },
|
|
23991
|
+
dispose: () => { },
|
|
23992
|
+
};
|
|
23993
|
+
const FOV$1 = 50;
|
|
23994
|
+
/** Dynamically load `three`; returns `null` when the package is not installed. */
|
|
23995
|
+
async function loadThree$1() {
|
|
23996
|
+
try {
|
|
23997
|
+
return (await import('three'));
|
|
23998
|
+
}
|
|
23999
|
+
catch {
|
|
24000
|
+
return null;
|
|
24001
|
+
}
|
|
24002
|
+
}
|
|
24003
|
+
/** Dynamically load the GLTFLoader addon; returns `null` when unavailable. */
|
|
24004
|
+
async function loadGltfLoaderCtor() {
|
|
24005
|
+
try {
|
|
24006
|
+
const mod = await import('three/examples/jsm/loaders/GLTFLoader.js');
|
|
24007
|
+
return mod.GLTFLoader;
|
|
24008
|
+
}
|
|
24009
|
+
catch {
|
|
24010
|
+
return null;
|
|
24011
|
+
}
|
|
24012
|
+
}
|
|
24013
|
+
/** Dynamically load the OrbitControls addon; returns `null` when unavailable. */
|
|
24014
|
+
async function loadOrbitControlsCtor$1() {
|
|
24015
|
+
try {
|
|
24016
|
+
const mod = await import('three/examples/jsm/controls/OrbitControls.js');
|
|
24017
|
+
return mod.OrbitControls;
|
|
24018
|
+
}
|
|
24019
|
+
catch {
|
|
24020
|
+
return null;
|
|
24021
|
+
}
|
|
24022
|
+
}
|
|
24023
|
+
/** Promisified `GLTFLoader.load`. */
|
|
24024
|
+
function loadGltf(loader, url) {
|
|
24025
|
+
return new Promise((resolve, reject) => {
|
|
24026
|
+
loader.load(url, resolve, undefined, reject);
|
|
24027
|
+
});
|
|
24028
|
+
}
|
|
24029
|
+
/**
|
|
24030
|
+
* Auto-centre the model at the origin and uniformly scale it so its largest
|
|
24031
|
+
* dimension fills a 2-unit cube, mirroring the previous react-three-fiber fit.
|
|
24032
|
+
*/
|
|
24033
|
+
function centerAndFit(three, root) {
|
|
24034
|
+
const box = new three.Box3().setFromObject(root);
|
|
24035
|
+
const size = new three.Vector3();
|
|
24036
|
+
box.getSize(size);
|
|
24037
|
+
const maxDim = Math.max(size.x, size.y, size.z);
|
|
24038
|
+
if (maxDim > 0) {
|
|
24039
|
+
root.scale.setScalar(2 / maxDim);
|
|
24040
|
+
}
|
|
24041
|
+
const center = new three.Vector3();
|
|
24042
|
+
box.getCenter(center);
|
|
24043
|
+
root.position.sub(center.multiplyScalar(root.scale.x));
|
|
24044
|
+
}
|
|
24045
|
+
/**
|
|
24046
|
+
* Mount an interactive GLTF/GLB model into `container` and start rendering.
|
|
24047
|
+
*
|
|
24048
|
+
* Resolves to {@link THREE_UNAVAILABLE} (a no-op handle with `ok === false`)
|
|
24049
|
+
* when `three` or its addons cannot be loaded, or when the model fails to parse.
|
|
24050
|
+
*/
|
|
24051
|
+
async function mountModel3D(container, modelUrl, options) {
|
|
24052
|
+
const three = await loadThree$1();
|
|
24053
|
+
const GltfLoaderCtor = three ? await loadGltfLoaderCtor() : null;
|
|
24054
|
+
if (!three || !GltfLoaderCtor) {
|
|
24055
|
+
return THREE_UNAVAILABLE;
|
|
24056
|
+
}
|
|
24057
|
+
const width = Math.max(1, options.width);
|
|
24058
|
+
const height = Math.max(1, options.height);
|
|
24059
|
+
const renderer = new three.WebGLRenderer({ antialias: true, alpha: !options.background });
|
|
24060
|
+
renderer.setPixelRatio(Math.min(typeof window === 'undefined' ? 1 : window.devicePixelRatio || 1, options.maxPixelRatio ?? 2));
|
|
24061
|
+
renderer.setSize(width, height, false);
|
|
24062
|
+
const canvas = renderer.domElement;
|
|
24063
|
+
canvas.style.width = `${width}px`;
|
|
24064
|
+
canvas.style.height = `${height}px`;
|
|
24065
|
+
canvas.style.display = 'block';
|
|
24066
|
+
canvas.style.willChange = 'transform';
|
|
24067
|
+
container.appendChild(canvas);
|
|
24068
|
+
const scene = new three.Scene();
|
|
24069
|
+
if (options.background) {
|
|
24070
|
+
scene.background = new three.Color(options.background);
|
|
24071
|
+
}
|
|
24072
|
+
const camera = new three.PerspectiveCamera(FOV$1, width / height, 0.1, 1000);
|
|
24073
|
+
camera.position.set(0, 0, 5);
|
|
24074
|
+
camera.lookAt(0, 0, 0);
|
|
24075
|
+
scene.add(new three.AmbientLight(0xffffff, 0.5));
|
|
24076
|
+
const key = new three.DirectionalLight(0xffffff, 1);
|
|
24077
|
+
key.position.set(5, 5, 5);
|
|
24078
|
+
scene.add(key);
|
|
24079
|
+
const fill = new three.DirectionalLight(0xffffff, 0.3);
|
|
24080
|
+
fill.position.set(-3, -3, 2);
|
|
24081
|
+
scene.add(fill);
|
|
24082
|
+
let gltf;
|
|
24083
|
+
try {
|
|
24084
|
+
gltf = await loadGltf(new GltfLoaderCtor(), modelUrl);
|
|
24085
|
+
}
|
|
24086
|
+
catch {
|
|
24087
|
+
renderer.dispose();
|
|
24088
|
+
canvas.remove();
|
|
24089
|
+
return THREE_UNAVAILABLE;
|
|
24090
|
+
}
|
|
24091
|
+
const model = gltf.scene;
|
|
24092
|
+
centerAndFit(three, model);
|
|
24093
|
+
scene.add(model);
|
|
24094
|
+
let controls = null;
|
|
24095
|
+
const OrbitCtrlCtor = await loadOrbitControlsCtor$1();
|
|
24096
|
+
const enableControls = (on) => {
|
|
24097
|
+
if (on && !controls && OrbitCtrlCtor) {
|
|
24098
|
+
controls = new OrbitCtrlCtor(camera, canvas);
|
|
24099
|
+
controls.enablePan = false;
|
|
24100
|
+
controls.enableZoom = true;
|
|
24101
|
+
controls.enableRotate = true;
|
|
24102
|
+
controls.minDistance = 2;
|
|
24103
|
+
controls.maxDistance = 20;
|
|
24104
|
+
controls.update();
|
|
24105
|
+
}
|
|
24106
|
+
else if (!on && controls) {
|
|
24107
|
+
controls.dispose();
|
|
24108
|
+
controls = null;
|
|
24109
|
+
}
|
|
24110
|
+
};
|
|
24111
|
+
enableControls(options.interactive ?? true);
|
|
24112
|
+
let frame = 0;
|
|
24113
|
+
let disposed = false;
|
|
24114
|
+
const renderLoop = () => {
|
|
24115
|
+
if (disposed) {
|
|
24116
|
+
return;
|
|
24117
|
+
}
|
|
24118
|
+
frame = requestAnimationFrame(renderLoop);
|
|
24119
|
+
controls?.update();
|
|
24120
|
+
renderer.render(scene, camera);
|
|
24121
|
+
};
|
|
24122
|
+
frame = requestAnimationFrame(renderLoop);
|
|
24123
|
+
/** Recursively dispose every geometry/material/texture under the model. */
|
|
24124
|
+
const disposeModel = () => {
|
|
24125
|
+
model.traverse((obj) => {
|
|
24126
|
+
const mesh = obj;
|
|
24127
|
+
mesh.geometry?.dispose();
|
|
24128
|
+
const material = mesh.material;
|
|
24129
|
+
if (Array.isArray(material)) {
|
|
24130
|
+
for (const m of material) {
|
|
24131
|
+
m.dispose();
|
|
24132
|
+
}
|
|
24133
|
+
}
|
|
24134
|
+
else {
|
|
24135
|
+
material?.dispose();
|
|
24136
|
+
}
|
|
24137
|
+
});
|
|
24138
|
+
};
|
|
24139
|
+
return {
|
|
24140
|
+
ok: true,
|
|
24141
|
+
resize(w, h) {
|
|
24142
|
+
const nextW = Math.max(1, w);
|
|
24143
|
+
const nextH = Math.max(1, h);
|
|
24144
|
+
camera.aspect = nextW / nextH;
|
|
24145
|
+
camera.updateProjectionMatrix();
|
|
24146
|
+
renderer.setSize(nextW, nextH, false);
|
|
24147
|
+
canvas.style.width = `${nextW}px`;
|
|
24148
|
+
canvas.style.height = `${nextH}px`;
|
|
24149
|
+
},
|
|
24150
|
+
setInteractive(on) {
|
|
24151
|
+
enableControls(on);
|
|
24152
|
+
},
|
|
24153
|
+
dispose() {
|
|
24154
|
+
if (disposed) {
|
|
24155
|
+
return;
|
|
24156
|
+
}
|
|
24157
|
+
disposed = true;
|
|
24158
|
+
cancelAnimationFrame(frame);
|
|
24159
|
+
controls?.dispose();
|
|
24160
|
+
disposeModel();
|
|
24161
|
+
scene.clear();
|
|
24162
|
+
renderer.dispose();
|
|
24163
|
+
canvas.remove();
|
|
24164
|
+
},
|
|
24165
|
+
};
|
|
24166
|
+
}
|
|
24167
|
+
|
|
24168
|
+
/**
|
|
24169
|
+
* Pure surface-chart 3D geometry helpers (framework- and three-agnostic).
|
|
24170
|
+
*
|
|
24171
|
+
* The vanilla three scene controller ({@link mountSurfaceChart3D}) feeds these
|
|
24172
|
+
* the loaded `three` module so the heavy library stays a dynamic, optional
|
|
24173
|
+
* import while the grid maths (plane subdivision -> height/colour displacement,
|
|
24174
|
+
* world-space label anchors) lives here as small, separately testable units.
|
|
24175
|
+
*/
|
|
24176
|
+
/** Half-unit spacing between adjacent grid points, in world units. */
|
|
24177
|
+
const GRID_SPACING = 0.5;
|
|
24178
|
+
/** Maximum height displacement (world units) for a normalised value of 1. */
|
|
24179
|
+
const MAX_HEIGHT = 1.5;
|
|
24180
|
+
/** Compute the world-space width/depth of the data grid. */
|
|
24181
|
+
function computeGridExtent(cols, rows) {
|
|
24182
|
+
return {
|
|
24183
|
+
gridWidth: Math.max(cols - 1, 1) * GRID_SPACING,
|
|
24184
|
+
gridDepth: Math.max(rows - 1, 1) * GRID_SPACING,
|
|
24185
|
+
};
|
|
24186
|
+
}
|
|
24187
|
+
/**
|
|
24188
|
+
* Build the surface mesh + wireframe geometries from the normalised height and
|
|
24189
|
+
* colour maps. The plane is subdivided to match the data grid, rotated into the
|
|
24190
|
+
* XZ plane, then each vertex is displaced in Y by its height and tinted by its
|
|
24191
|
+
* colour. Returns disposable geometries the caller adds to the scene.
|
|
24192
|
+
*/
|
|
24193
|
+
function buildSurfaceGeometry(three, cols, rows, heightMap, colorMap) {
|
|
24194
|
+
const widthSegs = Math.max(cols - 1, 0);
|
|
24195
|
+
const depthSegs = Math.max(rows - 1, 0);
|
|
24196
|
+
const { gridWidth, gridDepth } = computeGridExtent(cols, rows);
|
|
24197
|
+
const geo = new three.PlaneGeometry(gridWidth, gridDepth, widthSegs, depthSegs);
|
|
24198
|
+
// PlaneGeometry lies in the XY plane by default; rotate it flat into XZ.
|
|
24199
|
+
geo.rotateX(-Math.PI / 2);
|
|
24200
|
+
const pos = geo.attributes.position;
|
|
24201
|
+
const vertexCount = pos.count;
|
|
24202
|
+
const colors = new Float32Array(vertexCount * 3);
|
|
24203
|
+
for (let i = 0; i < vertexCount; i++) {
|
|
24204
|
+
// After the rotation, the (widthSegs+1) x (depthSegs+1) vertices stay in
|
|
24205
|
+
// row-major order, so col = i % cols and row = floor(i / cols) map onto the
|
|
24206
|
+
// data grid directly.
|
|
24207
|
+
const row = Math.floor(i / cols);
|
|
24208
|
+
const col = i % cols;
|
|
24209
|
+
const idx = row * cols + col;
|
|
24210
|
+
const h = idx < heightMap.length ? heightMap[idx] : 0;
|
|
24211
|
+
pos.setY(i, h * MAX_HEIGHT);
|
|
24212
|
+
const ci = idx * 3;
|
|
24213
|
+
colors[i * 3] = ci < colorMap.length ? colorMap[ci] : 0.5;
|
|
24214
|
+
colors[i * 3 + 1] = ci + 1 < colorMap.length ? colorMap[ci + 1] : 0.5;
|
|
24215
|
+
colors[i * 3 + 2] = ci + 2 < colorMap.length ? colorMap[ci + 2] : 0.5;
|
|
24216
|
+
}
|
|
24217
|
+
geo.setAttribute('color', new three.BufferAttribute(colors, 3));
|
|
24218
|
+
geo.computeVertexNormals();
|
|
24219
|
+
pos.needsUpdate = true;
|
|
24220
|
+
const wireGeometry = new three.WireframeGeometry(geo);
|
|
24221
|
+
return { geometry: geo, wireGeometry };
|
|
24222
|
+
}
|
|
24223
|
+
/**
|
|
24224
|
+
* Build the axis labels (category along the front edge, series along the right
|
|
24225
|
+
* edge, plus a single "Value" Y-axis label) as world-anchored {@link
|
|
24226
|
+
* SurfaceLabel}s. Category/series labels are thinned to at most `maxCat`/`maxSer`
|
|
24227
|
+
* entries to avoid clutter, mirroring the prior drei `Html` layout.
|
|
24228
|
+
*/
|
|
24229
|
+
function buildSurfaceLabels(cols, rows, categoryLabels, seriesNames, maxCat = 8, maxSer = 6) {
|
|
24230
|
+
const { gridWidth, gridDepth } = computeGridExtent(cols, rows);
|
|
24231
|
+
const labels = [];
|
|
24232
|
+
const catStep = Math.max(1, Math.ceil(categoryLabels.length / maxCat));
|
|
24233
|
+
for (let i = 0; i < categoryLabels.length; i += catStep) {
|
|
24234
|
+
const x = -gridWidth / 2 + (i / Math.max(cols - 1, 1)) * gridWidth;
|
|
24235
|
+
labels.push({
|
|
24236
|
+
key: `cat-${i}`,
|
|
24237
|
+
text: categoryLabels[i],
|
|
24238
|
+
anchor: [x, -0.15, gridDepth / 2 + 0.25],
|
|
24239
|
+
axis: 'category',
|
|
24240
|
+
});
|
|
24241
|
+
}
|
|
24242
|
+
const serStep = Math.max(1, Math.ceil(seriesNames.length / maxSer));
|
|
24243
|
+
for (let i = 0; i < seriesNames.length; i += serStep) {
|
|
24244
|
+
const z = -gridDepth / 2 + (i / Math.max(rows - 1, 1)) * gridDepth;
|
|
24245
|
+
labels.push({
|
|
24246
|
+
key: `ser-${i}`,
|
|
24247
|
+
text: seriesNames[i],
|
|
24248
|
+
anchor: [gridWidth / 2 + 0.3, -0.15, z],
|
|
24249
|
+
axis: 'series',
|
|
24250
|
+
});
|
|
24251
|
+
}
|
|
24252
|
+
labels.push({
|
|
24253
|
+
key: 'value-axis',
|
|
24254
|
+
text: 'Value',
|
|
24255
|
+
anchor: [-gridWidth / 2 - 0.35, 0.75, -gridDepth / 2],
|
|
24256
|
+
axis: 'value',
|
|
24257
|
+
});
|
|
24258
|
+
return labels;
|
|
24259
|
+
}
|
|
24260
|
+
/** Isometric-like camera placement that frames the whole grid. */
|
|
24261
|
+
function computeCameraPlacement(cols, rows) {
|
|
24262
|
+
const { gridWidth, gridDepth } = computeGridExtent(cols, rows);
|
|
24263
|
+
const maxExtent = Math.max(gridWidth, gridDepth, MAX_HEIGHT);
|
|
24264
|
+
const dist = maxExtent * 1.8;
|
|
24265
|
+
return {
|
|
24266
|
+
position: [dist * 0.8, dist * 0.7, dist * 0.8],
|
|
24267
|
+
target: [0, 0.3, 0],
|
|
24268
|
+
};
|
|
24269
|
+
}
|
|
24270
|
+
|
|
24271
|
+
/**
|
|
24272
|
+
* Vanilla three.js 3D surface-chart scene controller (framework-agnostic).
|
|
24273
|
+
*
|
|
24274
|
+
* Mounts an interactive surface chart into a caller-provided container element:
|
|
24275
|
+
* dynamically imports `three` plus its `OrbitControls` addon, builds a colour-
|
|
24276
|
+
* displaced surface mesh (with optional wireframe), grid floor, lights, and an
|
|
24277
|
+
* isometric camera, renders with a RAF loop, and overlays axis labels as DOM
|
|
24278
|
+
* nodes that are re-projected to screen each frame. Exposes `dispose()` for
|
|
24279
|
+
* deterministic teardown of GPU resources, listeners, and overlay nodes.
|
|
24280
|
+
*
|
|
24281
|
+
* `three` is an OPTIONAL peer dependency: every import is dynamic and guarded.
|
|
24282
|
+
* When `three` (or its OrbitControls addon) is missing, {@link mountSurfaceChart3D}
|
|
24283
|
+
* resolves to a sentinel handle ({@link SURFACE_THREE_UNAVAILABLE}) so the caller
|
|
24284
|
+
* can fall back to the 2D renderer. No framework imports - React, Vue, and
|
|
24285
|
+
* Angular bindings can all mount it.
|
|
24286
|
+
*/
|
|
24287
|
+
/** No-op sentinel returned when `three` or its OrbitControls addon is missing. */
|
|
24288
|
+
const SURFACE_THREE_UNAVAILABLE = {
|
|
24289
|
+
ok: false,
|
|
24290
|
+
resize: () => { },
|
|
24291
|
+
dispose: () => { },
|
|
24292
|
+
};
|
|
24293
|
+
const FOV = 45;
|
|
24294
|
+
/** Dynamically load `three`; returns `null` when the package is not installed. */
|
|
24295
|
+
async function loadThree() {
|
|
24296
|
+
try {
|
|
24297
|
+
return (await import('three'));
|
|
24298
|
+
}
|
|
24299
|
+
catch {
|
|
24300
|
+
return null;
|
|
24301
|
+
}
|
|
24302
|
+
}
|
|
24303
|
+
/** Dynamically load the OrbitControls addon; returns `null` when unavailable. */
|
|
24304
|
+
async function loadOrbitControlsCtor() {
|
|
24305
|
+
try {
|
|
24306
|
+
const mod = await import('three/examples/jsm/controls/OrbitControls.js');
|
|
24307
|
+
return mod.OrbitControls;
|
|
24308
|
+
}
|
|
24309
|
+
catch {
|
|
24310
|
+
return null;
|
|
24311
|
+
}
|
|
24312
|
+
}
|
|
24313
|
+
/** Create the DOM overlay nodes for the axis labels, returned with the layer. */
|
|
24314
|
+
function createLabelOverlay(doc, labels) {
|
|
24315
|
+
const layer = doc.createElement('div');
|
|
24316
|
+
Object.assign(layer.style, {
|
|
24317
|
+
position: 'absolute',
|
|
24318
|
+
inset: '0',
|
|
24319
|
+
pointerEvents: 'none',
|
|
24320
|
+
overflow: 'hidden',
|
|
24321
|
+
});
|
|
24322
|
+
const nodes = labels.map((label) => {
|
|
24323
|
+
const node = doc.createElement('div');
|
|
24324
|
+
node.textContent = label.text;
|
|
24325
|
+
const color = label.axis === 'value' ? '#999' : '#666';
|
|
24326
|
+
Object.assign(node.style, {
|
|
24327
|
+
position: 'absolute',
|
|
24328
|
+
fontSize: '9px',
|
|
24329
|
+
color,
|
|
24330
|
+
whiteSpace: 'nowrap',
|
|
24331
|
+
userSelect: 'none',
|
|
24332
|
+
transform: 'translate(-50%, -50%)',
|
|
24333
|
+
willChange: 'left, top',
|
|
24334
|
+
});
|
|
24335
|
+
if (label.axis === 'value') {
|
|
24336
|
+
node.style.writingMode = 'vertical-rl';
|
|
24337
|
+
}
|
|
24338
|
+
layer.appendChild(node);
|
|
24339
|
+
return node;
|
|
24340
|
+
});
|
|
24341
|
+
return { layer, nodes };
|
|
24342
|
+
}
|
|
24343
|
+
/**
|
|
24344
|
+
* Mount an interactive 3D surface chart into `container` and start rendering.
|
|
24345
|
+
*
|
|
24346
|
+
* Resolves to {@link SURFACE_THREE_UNAVAILABLE} when `three` or its OrbitControls
|
|
24347
|
+
* addon cannot be loaded, so the caller can fall back to a 2D surface renderer.
|
|
24348
|
+
*/
|
|
24349
|
+
async function mountSurfaceChart3D(container, options) {
|
|
24350
|
+
const three = await loadThree();
|
|
24351
|
+
const OrbitCtrlCtor = three ? await loadOrbitControlsCtor() : null;
|
|
24352
|
+
if (!three || !OrbitCtrlCtor) {
|
|
24353
|
+
return SURFACE_THREE_UNAVAILABLE;
|
|
24354
|
+
}
|
|
24355
|
+
const { cols, rows } = options;
|
|
24356
|
+
let width = Math.max(1, options.width);
|
|
24357
|
+
let height = Math.max(1, options.height);
|
|
24358
|
+
const renderer = new three.WebGLRenderer({ antialias: true, alpha: true });
|
|
24359
|
+
renderer.setPixelRatio(Math.min(typeof window === 'undefined' ? 1 : window.devicePixelRatio || 1, options.maxPixelRatio ?? 2));
|
|
24360
|
+
renderer.setSize(width, height, false);
|
|
24361
|
+
const canvas = renderer.domElement;
|
|
24362
|
+
canvas.style.width = `${width}px`;
|
|
24363
|
+
canvas.style.height = `${height}px`;
|
|
24364
|
+
canvas.style.display = 'block';
|
|
24365
|
+
canvas.style.willChange = 'transform';
|
|
24366
|
+
container.appendChild(canvas);
|
|
24367
|
+
const scene = new three.Scene();
|
|
24368
|
+
scene.add(new three.AmbientLight(0xffffff, 0.6));
|
|
24369
|
+
const key = new three.DirectionalLight(0xffffff, 0.8);
|
|
24370
|
+
key.position.set(5, 8, 5);
|
|
24371
|
+
scene.add(key);
|
|
24372
|
+
const fill = new three.DirectionalLight(0xffffff, 0.3);
|
|
24373
|
+
fill.position.set(-3, 4, -2);
|
|
24374
|
+
scene.add(fill);
|
|
24375
|
+
const camera = new three.PerspectiveCamera(FOV, width / height, 0.1, 1000);
|
|
24376
|
+
const placement = computeCameraPlacement(cols, rows);
|
|
24377
|
+
camera.position.set(...placement.position);
|
|
24378
|
+
const target = new three.Vector3(...placement.target);
|
|
24379
|
+
camera.lookAt(target);
|
|
24380
|
+
// Grid floor under the surface.
|
|
24381
|
+
const { gridWidth, gridDepth } = computeGridExtent(cols, rows);
|
|
24382
|
+
const floorSize = Math.max(gridWidth, gridDepth) * 1.2;
|
|
24383
|
+
const gridFloor = new three.GridHelper(floorSize, Math.max(cols, rows), 0xcccccc, 0xe8e8e8);
|
|
24384
|
+
gridFloor.position.y = -0.02;
|
|
24385
|
+
scene.add(gridFloor);
|
|
24386
|
+
// Surface mesh + optional wireframe.
|
|
24387
|
+
const { geometry, wireGeometry } = buildSurfaceGeometry(three, cols, rows, options.heightMap, options.colorMap);
|
|
24388
|
+
const surfaceMaterial = new three.MeshPhongMaterial({
|
|
24389
|
+
vertexColors: true,
|
|
24390
|
+
side: three.DoubleSide,
|
|
24391
|
+
shininess: 30,
|
|
24392
|
+
transparent: true,
|
|
24393
|
+
opacity: 0.92,
|
|
24394
|
+
});
|
|
24395
|
+
const surfaceMesh = new three.Mesh(geometry, surfaceMaterial);
|
|
24396
|
+
scene.add(surfaceMesh);
|
|
24397
|
+
let wireMaterial = null;
|
|
24398
|
+
if (options.wireframe) {
|
|
24399
|
+
wireMaterial = new three.LineBasicMaterial({
|
|
24400
|
+
color: 0x333333,
|
|
24401
|
+
transparent: true,
|
|
24402
|
+
opacity: 0.25,
|
|
24403
|
+
});
|
|
24404
|
+
scene.add(new three.LineSegments(wireGeometry, wireMaterial));
|
|
24405
|
+
}
|
|
24406
|
+
const controls = new OrbitCtrlCtor(camera, canvas);
|
|
24407
|
+
controls.enablePan = true;
|
|
24408
|
+
controls.enableZoom = true;
|
|
24409
|
+
controls.enableRotate = true;
|
|
24410
|
+
controls.minDistance = 1;
|
|
24411
|
+
controls.maxDistance = 20;
|
|
24412
|
+
controls.maxPolarAngle = Math.PI / 2 + 0.3;
|
|
24413
|
+
controls.target.copy(target);
|
|
24414
|
+
controls.update();
|
|
24415
|
+
// Axis-label DOM overlay, re-projected to screen each frame.
|
|
24416
|
+
const doc = container.ownerDocument ?? document;
|
|
24417
|
+
const labels = buildSurfaceLabels(cols, rows, options.categoryLabels, options.seriesNames);
|
|
24418
|
+
const { layer, nodes } = createLabelOverlay(doc, labels);
|
|
24419
|
+
container.appendChild(layer);
|
|
24420
|
+
const anchors = labels.map((l) => new three.Vector3(...l.anchor));
|
|
24421
|
+
const projected = new three.Vector3();
|
|
24422
|
+
const updateLabels = () => {
|
|
24423
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
24424
|
+
projected.copy(anchors[i]).project(camera);
|
|
24425
|
+
const node = nodes[i];
|
|
24426
|
+
// Hide labels behind the camera.
|
|
24427
|
+
if (projected.z > 1) {
|
|
24428
|
+
node.style.display = 'none';
|
|
24429
|
+
continue;
|
|
24430
|
+
}
|
|
24431
|
+
node.style.display = '';
|
|
24432
|
+
node.style.left = `${((projected.x + 1) / 2) * width}px`;
|
|
24433
|
+
node.style.top = `${((-projected.y + 1) / 2) * height}px`;
|
|
24434
|
+
}
|
|
24435
|
+
};
|
|
24436
|
+
let frame = 0;
|
|
24437
|
+
let disposed = false;
|
|
24438
|
+
const renderLoop = () => {
|
|
24439
|
+
if (disposed) {
|
|
24440
|
+
return;
|
|
24441
|
+
}
|
|
24442
|
+
frame = requestAnimationFrame(renderLoop);
|
|
24443
|
+
controls.update();
|
|
24444
|
+
renderer.render(scene, camera);
|
|
24445
|
+
updateLabels();
|
|
24446
|
+
};
|
|
24447
|
+
frame = requestAnimationFrame(renderLoop);
|
|
24448
|
+
return {
|
|
24449
|
+
ok: true,
|
|
24450
|
+
resize(w, h) {
|
|
24451
|
+
width = Math.max(1, w);
|
|
24452
|
+
height = Math.max(1, h);
|
|
24453
|
+
camera.aspect = width / height;
|
|
24454
|
+
camera.updateProjectionMatrix();
|
|
24455
|
+
renderer.setSize(width, height, false);
|
|
24456
|
+
canvas.style.width = `${width}px`;
|
|
24457
|
+
canvas.style.height = `${height}px`;
|
|
24458
|
+
},
|
|
24459
|
+
dispose() {
|
|
24460
|
+
if (disposed) {
|
|
24461
|
+
return;
|
|
24462
|
+
}
|
|
24463
|
+
disposed = true;
|
|
24464
|
+
cancelAnimationFrame(frame);
|
|
24465
|
+
controls.dispose();
|
|
24466
|
+
geometry.dispose();
|
|
24467
|
+
wireGeometry.dispose();
|
|
24468
|
+
surfaceMaterial.dispose();
|
|
24469
|
+
wireMaterial?.dispose();
|
|
24470
|
+
gridFloor.dispose();
|
|
24471
|
+
scene.clear();
|
|
24472
|
+
renderer.dispose();
|
|
24473
|
+
canvas.remove();
|
|
24474
|
+
layer.remove();
|
|
24475
|
+
},
|
|
24476
|
+
};
|
|
24477
|
+
}
|
|
24478
|
+
|
|
23969
24479
|
/**
|
|
23970
24480
|
* smartart-drawing.ts: Drawing-shape view-model helpers for the SmartArt
|
|
23971
24481
|
* renderer, shared across the React, Vue, and Angular bindings.
|
|
@@ -26142,6 +26652,298 @@ function issueTrackKey(issue, index) {
|
|
|
26142
26652
|
return `${issue.slideIndex}-${issue.type}-${issue.elementId ?? 'slide'}-${index}`;
|
|
26143
26653
|
}
|
|
26144
26654
|
|
|
26655
|
+
/**
|
|
26656
|
+
* Accessibility utilities for the PowerPoint viewer.
|
|
26657
|
+
*
|
|
26658
|
+
* Provides functions for computing reading order, generating ARIA labels,
|
|
26659
|
+
* determining ARIA roles, and detecting reduced-motion preferences.
|
|
26660
|
+
*
|
|
26661
|
+
* Pure (framework-agnostic): consumed by every binding's element renderer.
|
|
26662
|
+
*
|
|
26663
|
+
* @module render/accessibility
|
|
26664
|
+
*/
|
|
26665
|
+
// ---------------------------------------------------------------------------
|
|
26666
|
+
// Reading order
|
|
26667
|
+
// ---------------------------------------------------------------------------
|
|
26668
|
+
/**
|
|
26669
|
+
* Computes a reading-order index for each element on a slide.
|
|
26670
|
+
*
|
|
26671
|
+
* Elements are sorted top-to-bottom first, then left-to-right for elements
|
|
26672
|
+
* at roughly the same vertical position (within a tolerance band).
|
|
26673
|
+
* Returns a `Map<elementId, tabIndex>` with 1-based indices.
|
|
26674
|
+
*
|
|
26675
|
+
* @param elements - The flat list of elements on the current slide.
|
|
26676
|
+
* @param tolerancePx - Vertical tolerance for grouping elements into the
|
|
26677
|
+
* same "row" (default 20px).
|
|
26678
|
+
* @returns Map from element ID to 1-based reading order index.
|
|
26679
|
+
*/
|
|
26680
|
+
function computeReadingOrder(elements, tolerancePx = 20) {
|
|
26681
|
+
if (elements.length === 0) {
|
|
26682
|
+
return new Map();
|
|
26683
|
+
}
|
|
26684
|
+
const sorted = [...elements]
|
|
26685
|
+
.filter((el) => !el.hidden)
|
|
26686
|
+
.sort((a, b) => {
|
|
26687
|
+
const dy = a.y - b.y;
|
|
26688
|
+
if (Math.abs(dy) > tolerancePx) {
|
|
26689
|
+
return dy;
|
|
26690
|
+
}
|
|
26691
|
+
return a.x - b.x;
|
|
26692
|
+
});
|
|
26693
|
+
const result = new Map();
|
|
26694
|
+
sorted.forEach((el, idx) => {
|
|
26695
|
+
result.set(el.id, idx + 1);
|
|
26696
|
+
});
|
|
26697
|
+
return result;
|
|
26698
|
+
}
|
|
26699
|
+
// ---------------------------------------------------------------------------
|
|
26700
|
+
// ARIA roles
|
|
26701
|
+
// ---------------------------------------------------------------------------
|
|
26702
|
+
/**
|
|
26703
|
+
* Maps a PptxElement to its appropriate ARIA role.
|
|
26704
|
+
*
|
|
26705
|
+
* - image / picture => "img"
|
|
26706
|
+
* - table => "table"
|
|
26707
|
+
* - group => "group"
|
|
26708
|
+
* - chart => "img" (complex visualisation treated as image)
|
|
26709
|
+
* - text / shape with text => "text" is not valid; use undefined and rely on
|
|
26710
|
+
* aria-label alone. However, shapes without text act as decorative images.
|
|
26711
|
+
* - connector => "img"
|
|
26712
|
+
* - media => "application"
|
|
26713
|
+
*
|
|
26714
|
+
* @param element - The element to determine a role for.
|
|
26715
|
+
* @returns The ARIA role string, or undefined when none is needed.
|
|
26716
|
+
*/
|
|
26717
|
+
function getAriaRole(element) {
|
|
26718
|
+
switch (element.type) {
|
|
26719
|
+
case 'image':
|
|
26720
|
+
case 'picture':
|
|
26721
|
+
return 'img';
|
|
26722
|
+
case 'table':
|
|
26723
|
+
return 'table';
|
|
26724
|
+
case 'group':
|
|
26725
|
+
return 'group';
|
|
26726
|
+
case 'chart':
|
|
26727
|
+
return 'img';
|
|
26728
|
+
case 'smartArt':
|
|
26729
|
+
return 'img';
|
|
26730
|
+
case 'connector':
|
|
26731
|
+
return 'img';
|
|
26732
|
+
case 'media':
|
|
26733
|
+
return 'application';
|
|
26734
|
+
case 'ink':
|
|
26735
|
+
return 'img';
|
|
26736
|
+
case 'model3d':
|
|
26737
|
+
return 'img';
|
|
26738
|
+
case 'text':
|
|
26739
|
+
return undefined;
|
|
26740
|
+
case 'shape': {
|
|
26741
|
+
// Shapes with text act as text containers; shapes without text are decorative
|
|
26742
|
+
if (hasTextProperties(element) && element.text) {
|
|
26743
|
+
return undefined;
|
|
26744
|
+
}
|
|
26745
|
+
return 'img';
|
|
26746
|
+
}
|
|
26747
|
+
default:
|
|
26748
|
+
return undefined;
|
|
26749
|
+
}
|
|
26750
|
+
}
|
|
26751
|
+
// ---------------------------------------------------------------------------
|
|
26752
|
+
// ARIA labels
|
|
26753
|
+
// ---------------------------------------------------------------------------
|
|
26754
|
+
/**
|
|
26755
|
+
* Generates a human-readable ARIA label for an element.
|
|
26756
|
+
*
|
|
26757
|
+
* Priority:
|
|
26758
|
+
* 1. Image altText (for image/picture elements)
|
|
26759
|
+
* 2. Text content (for text-bearing elements)
|
|
26760
|
+
* 3. Chart title (from chartData)
|
|
26761
|
+
* 4. Element type fallback label
|
|
26762
|
+
*
|
|
26763
|
+
* @param element - The element to generate a label for.
|
|
26764
|
+
* @returns A descriptive string for the `aria-label` attribute.
|
|
26765
|
+
*/
|
|
26766
|
+
function getAriaLabel(element) {
|
|
26767
|
+
// Image alt text
|
|
26768
|
+
if ((element.type === 'image' || element.type === 'picture') &&
|
|
26769
|
+
'altText' in element &&
|
|
26770
|
+
typeof element.altText === 'string' &&
|
|
26771
|
+
element.altText.trim()) {
|
|
26772
|
+
return element.altText.trim();
|
|
26773
|
+
}
|
|
26774
|
+
// Text content
|
|
26775
|
+
if (hasTextProperties(element) && element.text) {
|
|
26776
|
+
const text = element.text.trim();
|
|
26777
|
+
if (text) {
|
|
26778
|
+
// Truncate long text for the label
|
|
26779
|
+
return text.length > 120 ? `${text.slice(0, 117)}...` : text;
|
|
26780
|
+
}
|
|
26781
|
+
}
|
|
26782
|
+
// Chart title
|
|
26783
|
+
if (element.type === 'chart' && 'chartData' in element && element.chartData) {
|
|
26784
|
+
const cd = element.chartData;
|
|
26785
|
+
if (cd.title) {
|
|
26786
|
+
return `Chart: ${cd.title}`;
|
|
26787
|
+
}
|
|
26788
|
+
return 'Chart';
|
|
26789
|
+
}
|
|
26790
|
+
// SmartArt
|
|
26791
|
+
if (element.type === 'smartArt') {
|
|
26792
|
+
return 'SmartArt diagram';
|
|
26793
|
+
}
|
|
26794
|
+
// Table
|
|
26795
|
+
if (element.type === 'table') {
|
|
26796
|
+
return 'Table';
|
|
26797
|
+
}
|
|
26798
|
+
// Media
|
|
26799
|
+
if (element.type === 'media') {
|
|
26800
|
+
return 'Media element';
|
|
26801
|
+
}
|
|
26802
|
+
// Group
|
|
26803
|
+
if (element.type === 'group') {
|
|
26804
|
+
return 'Group of elements';
|
|
26805
|
+
}
|
|
26806
|
+
// Connector
|
|
26807
|
+
if (element.type === 'connector') {
|
|
26808
|
+
return 'Connector line';
|
|
26809
|
+
}
|
|
26810
|
+
// Ink
|
|
26811
|
+
if (element.type === 'ink') {
|
|
26812
|
+
return 'Ink drawing';
|
|
26813
|
+
}
|
|
26814
|
+
// 3D Model
|
|
26815
|
+
if (element.type === 'model3d') {
|
|
26816
|
+
return '3D Model';
|
|
26817
|
+
}
|
|
26818
|
+
// Shape fallback
|
|
26819
|
+
if (element.type === 'shape') {
|
|
26820
|
+
if ('shapeType' in element && element.shapeType) {
|
|
26821
|
+
return `Shape: ${element.shapeType}`;
|
|
26822
|
+
}
|
|
26823
|
+
return 'Shape';
|
|
26824
|
+
}
|
|
26825
|
+
// Generic fallback
|
|
26826
|
+
return getTypeFallbackLabel(element.type);
|
|
26827
|
+
}
|
|
26828
|
+
/**
|
|
26829
|
+
* Returns a user-friendly fallback label based on element type.
|
|
26830
|
+
*/
|
|
26831
|
+
function getTypeFallbackLabel(type) {
|
|
26832
|
+
switch (type) {
|
|
26833
|
+
case 'text':
|
|
26834
|
+
return 'Text box';
|
|
26835
|
+
case 'shape':
|
|
26836
|
+
return 'Shape';
|
|
26837
|
+
case 'image':
|
|
26838
|
+
case 'picture':
|
|
26839
|
+
return 'Image';
|
|
26840
|
+
case 'table':
|
|
26841
|
+
return 'Table';
|
|
26842
|
+
case 'chart':
|
|
26843
|
+
return 'Chart';
|
|
26844
|
+
case 'connector':
|
|
26845
|
+
return 'Connector';
|
|
26846
|
+
case 'group':
|
|
26847
|
+
return 'Group';
|
|
26848
|
+
case 'smartArt':
|
|
26849
|
+
return 'SmartArt';
|
|
26850
|
+
case 'media':
|
|
26851
|
+
return 'Media';
|
|
26852
|
+
case 'ink':
|
|
26853
|
+
return 'Drawing';
|
|
26854
|
+
case 'ole':
|
|
26855
|
+
return 'Embedded object';
|
|
26856
|
+
case 'contentPart':
|
|
26857
|
+
return 'Content part';
|
|
26858
|
+
case 'model3d':
|
|
26859
|
+
return '3D Model';
|
|
26860
|
+
default:
|
|
26861
|
+
return 'Element';
|
|
26862
|
+
}
|
|
26863
|
+
}
|
|
26864
|
+
// ---------------------------------------------------------------------------
|
|
26865
|
+
// ARIA role descriptions
|
|
26866
|
+
// ---------------------------------------------------------------------------
|
|
26867
|
+
/**
|
|
26868
|
+
* Returns an `aria-roledescription` string for an element, providing
|
|
26869
|
+
* assistive-technology users with a more specific description of the
|
|
26870
|
+
* element's purpose than the generic ARIA role alone.
|
|
26871
|
+
*
|
|
26872
|
+
* @param element - The element to describe.
|
|
26873
|
+
* @returns A human-readable role description, or undefined when none is needed.
|
|
26874
|
+
*/
|
|
26875
|
+
function getAriaRoleDescription(element) {
|
|
26876
|
+
switch (element.type) {
|
|
26877
|
+
case 'shape': {
|
|
26878
|
+
const shapeType = 'shapeType' in element && typeof element.shapeType === 'string'
|
|
26879
|
+
? element.shapeType
|
|
26880
|
+
: undefined;
|
|
26881
|
+
if (shapeType) {
|
|
26882
|
+
return `shape: ${shapeType}`;
|
|
26883
|
+
}
|
|
26884
|
+
return 'shape';
|
|
26885
|
+
}
|
|
26886
|
+
case 'chart':
|
|
26887
|
+
return 'chart';
|
|
26888
|
+
case 'connector':
|
|
26889
|
+
return 'connector line';
|
|
26890
|
+
case 'smartArt':
|
|
26891
|
+
return 'diagram';
|
|
26892
|
+
case 'image':
|
|
26893
|
+
case 'picture':
|
|
26894
|
+
return 'image';
|
|
26895
|
+
case 'table':
|
|
26896
|
+
return 'data table';
|
|
26897
|
+
case 'group':
|
|
26898
|
+
return 'grouped elements';
|
|
26899
|
+
case 'media':
|
|
26900
|
+
return 'media player';
|
|
26901
|
+
case 'ink':
|
|
26902
|
+
return 'ink drawing';
|
|
26903
|
+
case 'model3d':
|
|
26904
|
+
return '3D model';
|
|
26905
|
+
default:
|
|
26906
|
+
return undefined;
|
|
26907
|
+
}
|
|
26908
|
+
}
|
|
26909
|
+
// ---------------------------------------------------------------------------
|
|
26910
|
+
// Reduced motion detection
|
|
26911
|
+
// ---------------------------------------------------------------------------
|
|
26912
|
+
/**
|
|
26913
|
+
* Queries the `prefers-reduced-motion: reduce` media query.
|
|
26914
|
+
*
|
|
26915
|
+
* @returns `true` if the user prefers reduced motion, `false` otherwise.
|
|
26916
|
+
*/
|
|
26917
|
+
function prefersReducedMotion() {
|
|
26918
|
+
if (typeof window === 'undefined') {
|
|
26919
|
+
return false;
|
|
26920
|
+
}
|
|
26921
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
26922
|
+
}
|
|
26923
|
+
/**
|
|
26924
|
+
* Returns a CSS-property object (framework-agnostic; spreadable into any
|
|
26925
|
+
* binding's style prop) that disables or tones
|
|
26926
|
+
* down animations and transitions when the user's OS-level
|
|
26927
|
+
* `prefers-reduced-motion: reduce` setting is active.
|
|
26928
|
+
*
|
|
26929
|
+
* When reduced motion is **not** preferred the function returns an empty
|
|
26930
|
+
* object so it can always be spread into a style prop safely.
|
|
26931
|
+
*
|
|
26932
|
+
* @returns CSS properties that suppress animations when appropriate.
|
|
26933
|
+
*/
|
|
26934
|
+
function getReducedMotionStyles() {
|
|
26935
|
+
if (!prefersReducedMotion()) {
|
|
26936
|
+
return {};
|
|
26937
|
+
}
|
|
26938
|
+
return {
|
|
26939
|
+
animationDuration: '0.001ms',
|
|
26940
|
+
animationIterationCount: 1,
|
|
26941
|
+
transitionDuration: '0.001ms',
|
|
26942
|
+
animationDelay: '0ms',
|
|
26943
|
+
transitionDelay: '0ms',
|
|
26944
|
+
};
|
|
26945
|
+
}
|
|
26946
|
+
|
|
26145
26947
|
/**
|
|
26146
26948
|
* Convert an array of points into an SVG path `d` attribute string.
|
|
26147
26949
|
* - 0 points -> `''`
|
|
@@ -29380,6 +30182,229 @@ function canUseClipboard(nav) {
|
|
|
29380
30182
|
typeof nav.clipboard.writeText === 'function');
|
|
29381
30183
|
}
|
|
29382
30184
|
|
|
30185
|
+
/**
|
|
30186
|
+
* is-mobile.ts: Injectable signal-based mobile-detection service plus a
|
|
30187
|
+
* pure `computeIsMobile` helper for unit-testing.
|
|
30188
|
+
*
|
|
30189
|
+
* Ported from: packages/react/src/viewer/hooks/useIsMobile.ts
|
|
30190
|
+
*
|
|
30191
|
+
* The service tracks two independent media conditions:
|
|
30192
|
+
* - `(pointer: coarse)`: primary pointer is a touch screen / stylus
|
|
30193
|
+
* - viewport width below {@link MOBILE_BREAKPOINT}
|
|
30194
|
+
*
|
|
30195
|
+
* Both conditions are evaluated once at construction time and then kept live
|
|
30196
|
+
* via `MediaQueryList.addEventListener`. The service is SSR-safe: when
|
|
30197
|
+
* `matchMedia` is not available (server / test environment without a DOM) all
|
|
30198
|
+
* signals default to `false` and no listeners are registered.
|
|
30199
|
+
*/
|
|
30200
|
+
// ---------------------------------------------------------------------------
|
|
30201
|
+
// Constants
|
|
30202
|
+
// ---------------------------------------------------------------------------
|
|
30203
|
+
/** Viewport width (px) below which the UI switches to mobile layout. */
|
|
30204
|
+
const MOBILE_BREAKPOINT = 768;
|
|
30205
|
+
/** Tablet breakpoint: below this width (but >= MOBILE) is tablet. */
|
|
30206
|
+
const TABLET_BREAKPOINT = 1024;
|
|
30207
|
+
/**
|
|
30208
|
+
* Max viewport height (px) at which a *touch* device is treated as mobile
|
|
30209
|
+
* regardless of width. Catches landscape phones (e.g. 915×412), which are wide
|
|
30210
|
+
* enough to fall in the "tablet" width band but far too short for the desktop
|
|
30211
|
+
* ribbon + side panels, so they need the mobile chrome. Tablets in landscape are
|
|
30212
|
+
* taller (~760px+) so they stay on the desktop layout. Mirrors React's
|
|
30213
|
+
* `MOBILE_LANDSCAPE_MAX_HEIGHT` in useIsMobile.ts.
|
|
30214
|
+
*/
|
|
30215
|
+
const MOBILE_LANDSCAPE_MAX_HEIGHT = 500;
|
|
30216
|
+
// ---------------------------------------------------------------------------
|
|
30217
|
+
// Pure helpers (no Angular deps, safe in vitest without a DOM)
|
|
30218
|
+
// ---------------------------------------------------------------------------
|
|
30219
|
+
/**
|
|
30220
|
+
* Decide whether the current environment should use the mobile layout:
|
|
30221
|
+
* - a narrow viewport (`width < MOBILE_BREAKPOINT`), OR
|
|
30222
|
+
* - a short *touch* viewport below the tablet width: a landscape phone, which
|
|
30223
|
+
* is wide enough to look like a tablet but far too short for the desktop
|
|
30224
|
+
* ribbon + side panels.
|
|
30225
|
+
*
|
|
30226
|
+
* This mirrors React's `isMobileViewport(width, height, isTouch)` so the three
|
|
30227
|
+
* frameworks switch chrome at the same breakpoints (and the shared mobile e2e
|
|
30228
|
+
* specs pass identically). A tall touch tablet (e.g. 820×1180) is NOT mobile.
|
|
30229
|
+
*
|
|
30230
|
+
* @pure: no side effects, fully testable without a DOM.
|
|
30231
|
+
*/
|
|
30232
|
+
function computeIsMobile(width, height, isTouch) {
|
|
30233
|
+
if (width < MOBILE_BREAKPOINT) {
|
|
30234
|
+
return true;
|
|
30235
|
+
}
|
|
30236
|
+
return isTouch && height > 0 && height < MOBILE_LANDSCAPE_MAX_HEIGHT && width < TABLET_BREAKPOINT;
|
|
30237
|
+
}
|
|
30238
|
+
/**
|
|
30239
|
+
* Decide whether the current environment is "tablet" (desktop chrome, but in
|
|
30240
|
+
* the 768–1023px width band). A short landscape-phone touch viewport is mobile,
|
|
30241
|
+
* not tablet (handled by {@link computeIsMobile}).
|
|
30242
|
+
*
|
|
30243
|
+
* @pure
|
|
30244
|
+
*/
|
|
30245
|
+
function computeIsTablet(width, height, isTouch) {
|
|
30246
|
+
if (computeIsMobile(width, height, isTouch)) {
|
|
30247
|
+
return false;
|
|
30248
|
+
}
|
|
30249
|
+
return width >= MOBILE_BREAKPOINT && width < TABLET_BREAKPOINT;
|
|
30250
|
+
}
|
|
30251
|
+
// ---------------------------------------------------------------------------
|
|
30252
|
+
// IsMobileService
|
|
30253
|
+
// ---------------------------------------------------------------------------
|
|
30254
|
+
/**
|
|
30255
|
+
* `IsMobileService`: provides reactive signals for the current viewport /
|
|
30256
|
+
* pointer kind so components can switch between mobile and desktop chrome
|
|
30257
|
+
* without subscribing to resize events themselves.
|
|
30258
|
+
*
|
|
30259
|
+
* Inject at the component level (or provide at root); the service cleans up
|
|
30260
|
+
* its `MediaQueryList` listeners automatically via `DestroyRef`.
|
|
30261
|
+
*
|
|
30262
|
+
* ```ts
|
|
30263
|
+
* providers: [IsMobileService]
|
|
30264
|
+
* // or globally:
|
|
30265
|
+
* // provideIsMobile() (see factory below)
|
|
30266
|
+
* ```
|
|
30267
|
+
*
|
|
30268
|
+
* ```ts
|
|
30269
|
+
* protected readonly mobile = inject(IsMobileService);
|
|
30270
|
+
* // in template: @if (mobile.isMobile()) { … }
|
|
30271
|
+
* ```
|
|
30272
|
+
*/
|
|
30273
|
+
class IsMobileService {
|
|
30274
|
+
/** True when the primary pointer is coarse (touch / stylus). */
|
|
30275
|
+
isCoarsePointer = signal(false, /* @ts-ignore */
|
|
30276
|
+
...(ngDevMode ? [{ debugName: "isCoarsePointer" }] : /* istanbul ignore next */ []));
|
|
30277
|
+
/** True when the viewport width is below {@link MOBILE_BREAKPOINT}. */
|
|
30278
|
+
isNarrowViewport = signal(false, /* @ts-ignore */
|
|
30279
|
+
...(ngDevMode ? [{ debugName: "isNarrowViewport" }] : /* istanbul ignore next */ []));
|
|
30280
|
+
/**
|
|
30281
|
+
* True when either the pointer is coarse OR the viewport is narrow.
|
|
30282
|
+
* Use this as the single gate for showing mobile chrome.
|
|
30283
|
+
*/
|
|
30284
|
+
isMobile = signal(false, /* @ts-ignore */
|
|
30285
|
+
...(ngDevMode ? [{ debugName: "isMobile" }] : /* istanbul ignore next */ []));
|
|
30286
|
+
/** True when the viewport is in the tablet range (desktop pointer only). */
|
|
30287
|
+
isTablet = signal(false, /* @ts-ignore */
|
|
30288
|
+
...(ngDevMode ? [{ debugName: "isTablet" }] : /* istanbul ignore next */ []));
|
|
30289
|
+
/**
|
|
30290
|
+
* CSS pixels the on-screen keyboard currently covers at the bottom of the
|
|
30291
|
+
* layout viewport (0 when no keyboard is open). The orchestrator lifts the
|
|
30292
|
+
* fixed mobile bottom bar by this amount so it stays above the keyboard.
|
|
30293
|
+
*/
|
|
30294
|
+
keyboardInset = signal(0, /* @ts-ignore */
|
|
30295
|
+
...(ngDevMode ? [{ debugName: "keyboardInset" }] : /* istanbul ignore next */ []));
|
|
30296
|
+
/** True while {@link keyboardInset} is large enough to count as "open". */
|
|
30297
|
+
isKeyboardOpen = signal(false, /* @ts-ignore */
|
|
30298
|
+
...(ngDevMode ? [{ debugName: "isKeyboardOpen" }] : /* istanbul ignore next */ []));
|
|
30299
|
+
constructor() {
|
|
30300
|
+
const destroyRef = inject(DestroyRef);
|
|
30301
|
+
// Guard: matchMedia / window may not be available in SSR / test envs.
|
|
30302
|
+
if (typeof matchMedia !== 'function' || typeof window === 'undefined') {
|
|
30303
|
+
return;
|
|
30304
|
+
}
|
|
30305
|
+
this._wireKeyboardInset(destroyRef);
|
|
30306
|
+
// ── Coarse-pointer media query (drives the touch flag) ───────────────────
|
|
30307
|
+
const coarseMql = matchMedia('(pointer: coarse)');
|
|
30308
|
+
this.isCoarsePointer.set(coarseMql.matches);
|
|
30309
|
+
const onCoarseChange = (evt) => {
|
|
30310
|
+
this.isCoarsePointer.set(evt.matches);
|
|
30311
|
+
this._recompute();
|
|
30312
|
+
};
|
|
30313
|
+
coarseMql.addEventListener('change', onCoarseChange);
|
|
30314
|
+
// ── Viewport size tracking ───────────────────────────────────────────────
|
|
30315
|
+
// Width-only media queries cannot express the "short landscape phone"
|
|
30316
|
+
// rule (which depends on height + touch), so track the live viewport size
|
|
30317
|
+
// on resize and recompute the derived flags from width/height/touch.
|
|
30318
|
+
const onResize = () => this._recompute();
|
|
30319
|
+
window.addEventListener('resize', onResize);
|
|
30320
|
+
if (typeof screen !== 'undefined' && screen.orientation) {
|
|
30321
|
+
screen.orientation.addEventListener('change', onResize);
|
|
30322
|
+
}
|
|
30323
|
+
this._recompute();
|
|
30324
|
+
// ── Cleanup on destroy ───────────────────────────────────────────────────
|
|
30325
|
+
destroyRef.onDestroy(() => {
|
|
30326
|
+
coarseMql.removeEventListener('change', onCoarseChange);
|
|
30327
|
+
window.removeEventListener('resize', onResize);
|
|
30328
|
+
if (typeof screen !== 'undefined' && screen.orientation) {
|
|
30329
|
+
screen.orientation.removeEventListener('change', onResize);
|
|
30330
|
+
}
|
|
30331
|
+
});
|
|
30332
|
+
}
|
|
30333
|
+
/** Recompute all derived flags from the live viewport size + pointer kind. */
|
|
30334
|
+
_recompute() {
|
|
30335
|
+
const width = window.innerWidth;
|
|
30336
|
+
const height = window.innerHeight;
|
|
30337
|
+
const touch = this.isCoarsePointer();
|
|
30338
|
+
this.isNarrowViewport.set(width < MOBILE_BREAKPOINT);
|
|
30339
|
+
this.isMobile.set(computeIsMobile(width, height, touch));
|
|
30340
|
+
this.isTablet.set(computeIsTablet(width, height, touch));
|
|
30341
|
+
}
|
|
30342
|
+
/**
|
|
30343
|
+
* Track the on-screen-keyboard inset via the `VisualViewport` API and keep the
|
|
30344
|
+
* focused editable visible: when the keyboard shrinks the visual viewport,
|
|
30345
|
+
* update {@link keyboardInset} / {@link isKeyboardOpen} and scroll the active
|
|
30346
|
+
* input / textarea / contenteditable into the area above the keyboard. No-op
|
|
30347
|
+
* when `visualViewport` is unavailable (desktop / SSR), so desktop is unchanged.
|
|
30348
|
+
*/
|
|
30349
|
+
_wireKeyboardInset(destroyRef) {
|
|
30350
|
+
const vv = window.visualViewport;
|
|
30351
|
+
if (!vv) {
|
|
30352
|
+
return;
|
|
30353
|
+
}
|
|
30354
|
+
const update = () => {
|
|
30355
|
+
const metrics = readViewportMetrics(window);
|
|
30356
|
+
const inset = metrics ? computeKeyboardInset(metrics) : 0;
|
|
30357
|
+
this.keyboardInset.set(inset);
|
|
30358
|
+
this.isKeyboardOpen.set(isKeyboardOpen(inset));
|
|
30359
|
+
if (inset > 0) {
|
|
30360
|
+
window.requestAnimationFrame(() => this._scrollFocusedIntoView(inset));
|
|
30361
|
+
}
|
|
30362
|
+
};
|
|
30363
|
+
const onFocusIn = () => {
|
|
30364
|
+
window.requestAnimationFrame(() => {
|
|
30365
|
+
const metrics = readViewportMetrics(window);
|
|
30366
|
+
const inset = metrics ? computeKeyboardInset(metrics) : 0;
|
|
30367
|
+
if (inset > 0) {
|
|
30368
|
+
this._scrollFocusedIntoView(inset);
|
|
30369
|
+
}
|
|
30370
|
+
});
|
|
30371
|
+
};
|
|
30372
|
+
update();
|
|
30373
|
+
vv.addEventListener('resize', update);
|
|
30374
|
+
vv.addEventListener('scroll', update);
|
|
30375
|
+
document.addEventListener('focusin', onFocusIn);
|
|
30376
|
+
destroyRef.onDestroy(() => {
|
|
30377
|
+
vv.removeEventListener('resize', update);
|
|
30378
|
+
vv.removeEventListener('scroll', update);
|
|
30379
|
+
document.removeEventListener('focusin', onFocusIn);
|
|
30380
|
+
});
|
|
30381
|
+
}
|
|
30382
|
+
/** Scroll the focused editable into the area above the keyboard, if needed. */
|
|
30383
|
+
_scrollFocusedIntoView(keyboardInset) {
|
|
30384
|
+
if (keyboardInset <= 0 || typeof document === 'undefined') {
|
|
30385
|
+
return;
|
|
30386
|
+
}
|
|
30387
|
+
const active = document.activeElement;
|
|
30388
|
+
if (!(active instanceof HTMLElement)) {
|
|
30389
|
+
return;
|
|
30390
|
+
}
|
|
30391
|
+
const tag = active.tagName;
|
|
30392
|
+
if (tag !== 'INPUT' && tag !== 'TEXTAREA' && !active.isContentEditable) {
|
|
30393
|
+
return;
|
|
30394
|
+
}
|
|
30395
|
+
const rect = active.getBoundingClientRect();
|
|
30396
|
+
const delta = computeScrollDelta({ top: rect.top, bottom: rect.bottom }, window.innerHeight, keyboardInset);
|
|
30397
|
+
if (delta !== 0) {
|
|
30398
|
+
window.scrollBy({ top: delta, behavior: 'smooth' });
|
|
30399
|
+
}
|
|
30400
|
+
}
|
|
30401
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: IsMobileService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
30402
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: IsMobileService });
|
|
30403
|
+
}
|
|
30404
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: IsMobileService, decorators: [{
|
|
30405
|
+
type: Injectable
|
|
30406
|
+
}], ctorParameters: () => [] });
|
|
30407
|
+
|
|
29383
30408
|
/**
|
|
29384
30409
|
* modal-dialog.component.ts: Reusable, accessible modal dialog shell.
|
|
29385
30410
|
*
|
|
@@ -29406,6 +30431,16 @@ function canUseClipboard(nav) {
|
|
|
29406
30431
|
* </pptx-modal-dialog>
|
|
29407
30432
|
* ```
|
|
29408
30433
|
*/
|
|
30434
|
+
/**
|
|
30435
|
+
* Map the mobile flag to the modal-panel class list. Pure (no Angular / DOM) so
|
|
30436
|
+
* it can be unit-tested without TestBed: on mobile the panel gets the
|
|
30437
|
+
* `is-mobile` modifier that turns it into a bottom sheet (matching the CSS media
|
|
30438
|
+
* query, but driven by the {@link IsMobileService} 768px breakpoint so the three
|
|
30439
|
+
* frameworks switch chrome at the same width).
|
|
30440
|
+
*/
|
|
30441
|
+
function modalPanelClass(isMobile) {
|
|
30442
|
+
return isMobile ? 'pptx-ng-modal-panel is-mobile' : 'pptx-ng-modal-panel';
|
|
30443
|
+
}
|
|
29409
30444
|
class ModalDialogComponent {
|
|
29410
30445
|
/** Whether the dialog is visible. */
|
|
29411
30446
|
open = input(false, /* @ts-ignore */
|
|
@@ -29415,6 +30450,14 @@ class ModalDialogComponent {
|
|
|
29415
30450
|
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
29416
30451
|
/** Fired on backdrop click, the `×` button, and `Escape`. */
|
|
29417
30452
|
close = output();
|
|
30453
|
+
/** Reactive viewport / pointer flags (drives the bottom-sheet layout). */
|
|
30454
|
+
mobile = inject(IsMobileService);
|
|
30455
|
+
/**
|
|
30456
|
+
* Panel class list: gains the `is-mobile` bottom-sheet modifier under the
|
|
30457
|
+
* mobile breakpoint. See {@link modalPanelClass}.
|
|
30458
|
+
*/
|
|
30459
|
+
panelClass = computed(() => modalPanelClass(this.mobile.isMobile()), /* @ts-ignore */
|
|
30460
|
+
...(ngDevMode ? [{ debugName: "panelClass" }] : /* istanbul ignore next */ []));
|
|
29418
30461
|
/** Close on `Escape`, regardless of where focus currently sits. */
|
|
29419
30462
|
onDocumentKeydown(event) {
|
|
29420
30463
|
if (!this.open()) {
|
|
@@ -29477,11 +30520,15 @@ class ModalDialogComponent {
|
|
|
29477
30520
|
this.dragY.set(0);
|
|
29478
30521
|
}
|
|
29479
30522
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ModalDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
29480
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ModalDialogComponent, isStandalone: true, selector: "pptx-modal-dialog", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { close: "close" }, host: { listeners: { "document:keydown": "onDocumentKeydown($event)" } }, ngImport: i0, template: `
|
|
30523
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ModalDialogComponent, isStandalone: true, selector: "pptx-modal-dialog", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { close: "close" }, host: { listeners: { "document:keydown": "onDocumentKeydown($event)" } }, providers: [IsMobileService], ngImport: i0, template: `
|
|
29481
30524
|
@if (open()) {
|
|
29482
|
-
<div
|
|
30525
|
+
<div
|
|
30526
|
+
class="pptx-ng-modal-backdrop"
|
|
30527
|
+
[class.is-mobile]="mobile.isMobile()"
|
|
30528
|
+
(click)="onBackdropClick()"
|
|
30529
|
+
>
|
|
29483
30530
|
<div
|
|
29484
|
-
class="
|
|
30531
|
+
[class]="panelClass()"
|
|
29485
30532
|
role="dialog"
|
|
29486
30533
|
aria-modal="true"
|
|
29487
30534
|
[attr.aria-label]="title() || null"
|
|
@@ -29521,15 +30568,19 @@ class ModalDialogComponent {
|
|
|
29521
30568
|
</div>
|
|
29522
30569
|
</div>
|
|
29523
30570
|
}
|
|
29524
|
-
`, isInline: true, styles: [".pptx-ng-modal-backdrop{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;background:#00000073}.pptx-ng-modal-panel{display:flex;flex-direction:column;min-width:320px;max-width:min(92vw,480px);max-height:88vh;overflow:hidden;background:var(--pptx-popover, #ffffff);color:var(--pptx-foreground, #111827);border:1px solid var(--pptx-border, #e5e7eb);border-radius:var(--pptx-radius, 8px);box-shadow:0 10px 40px #00000059}.pptx-ng-modal-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--pptx-border, #e5e7eb);touch-action:none}.pptx-ng-modal-title{margin:0;font-size:14px;font-weight:600;line-height:1.4}.pptx-ng-modal-close{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;font-size:18px;line-height:1;color:var(--pptx-muted-foreground, #6b7280);background:transparent;border:none;border-radius:4px;cursor:pointer}.pptx-ng-modal-close:hover{color:var(--pptx-foreground, #111827);background:var(--pptx-muted, #f3f4f6)}.pptx-ng-modal-body{padding:16px;overflow-y:auto}.pptx-ng-modal-footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--pptx-border, #e5e7eb)}.pptx-ng-modal-footer:empty{display:none}@media(max-width:
|
|
30571
|
+
`, isInline: true, styles: [".pptx-ng-modal-backdrop{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;background:#00000073}.pptx-ng-modal-panel{display:flex;flex-direction:column;min-width:320px;max-width:min(92vw,480px);max-height:88vh;overflow:hidden;background:var(--pptx-popover, #ffffff);color:var(--pptx-foreground, #111827);border:1px solid var(--pptx-border, #e5e7eb);border-radius:var(--pptx-radius, 8px);box-shadow:0 10px 40px #00000059}.pptx-ng-modal-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--pptx-border, #e5e7eb);touch-action:none}.pptx-ng-modal-title{margin:0;font-size:14px;font-weight:600;line-height:1.4}.pptx-ng-modal-close{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;font-size:18px;line-height:1;color:var(--pptx-muted-foreground, #6b7280);background:transparent;border:none;border-radius:4px;cursor:pointer}.pptx-ng-modal-close:hover{color:var(--pptx-foreground, #111827);background:var(--pptx-muted, #f3f4f6)}.pptx-ng-modal-body{padding:16px;overflow-y:auto}.pptx-ng-modal-footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--pptx-border, #e5e7eb)}.pptx-ng-modal-footer:empty{display:none}.pptx-ng-modal-backdrop.is-mobile{align-items:flex-end;justify-content:stretch}.pptx-ng-modal-panel.is-mobile{min-width:0;max-width:none;width:100%;max-height:88dvh;border-left:none;border-right:none;border-bottom:none;border-radius:16px 16px 0 0;padding-bottom:max(env(safe-area-inset-bottom),0px)}@media(max-width:767px),(pointer:coarse){.pptx-ng-modal-backdrop{align-items:flex-end;justify-content:stretch}.pptx-ng-modal-panel{min-width:0;max-width:none;width:100%;max-height:88dvh;border-left:none;border-right:none;border-bottom:none;border-radius:16px 16px 0 0;padding-bottom:max(env(safe-area-inset-bottom),0px)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
29525
30572
|
}
|
|
29526
30573
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ModalDialogComponent, decorators: [{
|
|
29527
30574
|
type: Component,
|
|
29528
|
-
args: [{ selector: 'pptx-modal-dialog', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
30575
|
+
args: [{ selector: 'pptx-modal-dialog', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, providers: [IsMobileService], template: `
|
|
29529
30576
|
@if (open()) {
|
|
29530
|
-
<div
|
|
30577
|
+
<div
|
|
30578
|
+
class="pptx-ng-modal-backdrop"
|
|
30579
|
+
[class.is-mobile]="mobile.isMobile()"
|
|
30580
|
+
(click)="onBackdropClick()"
|
|
30581
|
+
>
|
|
29531
30582
|
<div
|
|
29532
|
-
class="
|
|
30583
|
+
[class]="panelClass()"
|
|
29533
30584
|
role="dialog"
|
|
29534
30585
|
aria-modal="true"
|
|
29535
30586
|
[attr.aria-label]="title() || null"
|
|
@@ -29569,7 +30620,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
29569
30620
|
</div>
|
|
29570
30621
|
</div>
|
|
29571
30622
|
}
|
|
29572
|
-
`, styles: [".pptx-ng-modal-backdrop{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;background:#00000073}.pptx-ng-modal-panel{display:flex;flex-direction:column;min-width:320px;max-width:min(92vw,480px);max-height:88vh;overflow:hidden;background:var(--pptx-popover, #ffffff);color:var(--pptx-foreground, #111827);border:1px solid var(--pptx-border, #e5e7eb);border-radius:var(--pptx-radius, 8px);box-shadow:0 10px 40px #00000059}.pptx-ng-modal-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--pptx-border, #e5e7eb);touch-action:none}.pptx-ng-modal-title{margin:0;font-size:14px;font-weight:600;line-height:1.4}.pptx-ng-modal-close{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;font-size:18px;line-height:1;color:var(--pptx-muted-foreground, #6b7280);background:transparent;border:none;border-radius:4px;cursor:pointer}.pptx-ng-modal-close:hover{color:var(--pptx-foreground, #111827);background:var(--pptx-muted, #f3f4f6)}.pptx-ng-modal-body{padding:16px;overflow-y:auto}.pptx-ng-modal-footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--pptx-border, #e5e7eb)}.pptx-ng-modal-footer:empty{display:none}@media(max-width:
|
|
30623
|
+
`, styles: [".pptx-ng-modal-backdrop{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;background:#00000073}.pptx-ng-modal-panel{display:flex;flex-direction:column;min-width:320px;max-width:min(92vw,480px);max-height:88vh;overflow:hidden;background:var(--pptx-popover, #ffffff);color:var(--pptx-foreground, #111827);border:1px solid var(--pptx-border, #e5e7eb);border-radius:var(--pptx-radius, 8px);box-shadow:0 10px 40px #00000059}.pptx-ng-modal-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--pptx-border, #e5e7eb);touch-action:none}.pptx-ng-modal-title{margin:0;font-size:14px;font-weight:600;line-height:1.4}.pptx-ng-modal-close{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;font-size:18px;line-height:1;color:var(--pptx-muted-foreground, #6b7280);background:transparent;border:none;border-radius:4px;cursor:pointer}.pptx-ng-modal-close:hover{color:var(--pptx-foreground, #111827);background:var(--pptx-muted, #f3f4f6)}.pptx-ng-modal-body{padding:16px;overflow-y:auto}.pptx-ng-modal-footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--pptx-border, #e5e7eb)}.pptx-ng-modal-footer:empty{display:none}.pptx-ng-modal-backdrop.is-mobile{align-items:flex-end;justify-content:stretch}.pptx-ng-modal-panel.is-mobile{min-width:0;max-width:none;width:100%;max-height:88dvh;border-left:none;border-right:none;border-bottom:none;border-radius:16px 16px 0 0;padding-bottom:max(env(safe-area-inset-bottom),0px)}@media(max-width:767px),(pointer:coarse){.pptx-ng-modal-backdrop{align-items:flex-end;justify-content:stretch}.pptx-ng-modal-panel{min-width:0;max-width:none;width:100%;max-height:88dvh;border-left:none;border-right:none;border-bottom:none;border-radius:16px 16px 0 0;padding-bottom:max(env(safe-area-inset-bottom),0px)}}\n"] }]
|
|
29573
30624
|
}], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], close: [{ type: i0.Output, args: ["close"] }], onDocumentKeydown: [{
|
|
29574
30625
|
type: HostListener,
|
|
29575
30626
|
args: ['document:keydown', ['$event']]
|
|
@@ -40197,6 +41248,15 @@ class InspectorPanelComponent {
|
|
|
40197
41248
|
slideIndex = input.required(/* @ts-ignore */
|
|
40198
41249
|
...(ngDevMode ? [{ debugName: "slideIndex" }] : /* istanbul ignore next */ []));
|
|
40199
41250
|
editor = inject(EditorStateService);
|
|
41251
|
+
/** Reactive viewport / pointer flags (drives the bottom-sheet layout). */
|
|
41252
|
+
mobile = inject(IsMobileService);
|
|
41253
|
+
/**
|
|
41254
|
+
* Root class list: gains the `is-mobile` modifier under the mobile
|
|
41255
|
+
* breakpoint so the panel becomes a full-width, touch-sized bottom sheet.
|
|
41256
|
+
* See {@link inspectorRootClass}.
|
|
41257
|
+
*/
|
|
41258
|
+
inspectorClass = computed(() => inspectorRootClass(this.mobile.isMobile()), /* @ts-ignore */
|
|
41259
|
+
...(ngDevMode ? [{ debugName: "inspectorClass" }] : /* istanbul ignore next */ []));
|
|
40200
41260
|
/** Alias so the template can call el() without conflicting with Angular internals. */
|
|
40201
41261
|
el = computed(() => this.element(), /* @ts-ignore */
|
|
40202
41262
|
...(ngDevMode ? [{ debugName: "el" }] : /* istanbul ignore next */ []));
|
|
@@ -40372,7 +41432,7 @@ class InspectorPanelComponent {
|
|
|
40372
41432
|
this.editor.updateElement(this.slideIndex(), cur.id, textStylePatch(cur, { underline: !this.currentUnderline() }));
|
|
40373
41433
|
}
|
|
40374
41434
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: InspectorPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
40375
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: InspectorPanelComponent, isStandalone: true, selector: "pptx-inspector-panel", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, slideIndex: { classPropertyName: "slideIndex", publicName: "slideIndex", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
41435
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: InspectorPanelComponent, isStandalone: true, selector: "pptx-inspector-panel", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, slideIndex: { classPropertyName: "slideIndex", publicName: "slideIndex", isSignal: true, isRequired: true, transformFunction: null } }, providers: [IsMobileService], ngImport: i0, template: `
|
|
40376
41436
|
<!--
|
|
40377
41437
|
NOTE (mobile-safe inputs): every numeric / colour input is keyed on the
|
|
40378
41438
|
selected element's id via @if blocks. Angular destroys and recreates the
|
|
@@ -40382,7 +41442,7 @@ class InspectorPanelComponent {
|
|
|
40382
41442
|
mid-edit: the caret stays put and the on-screen keyboard does not dismiss.
|
|
40383
41443
|
All commits happen on (change) (blur), reading event.target.value.
|
|
40384
41444
|
-->
|
|
40385
|
-
<aside class="
|
|
41445
|
+
<aside [class]="inspectorClass()" aria-label="Element properties">
|
|
40386
41446
|
<!-- ── Transform: Position & Size ─────────────────────────────────── -->
|
|
40387
41447
|
<section class="pptx-ng-inspector__section">
|
|
40388
41448
|
<h3 class="pptx-ng-inspector__heading">Transform</h3>
|
|
@@ -40665,7 +41725,7 @@ class InspectorPanelComponent {
|
|
|
40665
41725
|
</div>
|
|
40666
41726
|
</section>
|
|
40667
41727
|
</aside>
|
|
40668
|
-
`, isInline: true, styles: [".pptx-ng-inspector{display:flex;flex-direction:column;gap:0;padding:.5rem;background:var(--pptx-inspector-bg, #1e1e1e);color:var(--pptx-inspector-fg, #e0e0e0);font-size:12px;min-width:220px;overflow-y:auto}.pptx-ng-inspector__section{padding:.5rem 0;border-bottom:1px solid var(--pptx-inspector-border, #333)}.pptx-ng-inspector__section:last-child{border-bottom:none}.pptx-ng-inspector__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0 0 .35rem}.pptx-ng-inspector__details{border-bottom:1px solid var(--pptx-inspector-border, #333)}.pptx-ng-inspector__summary{padding:.5rem 0;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);cursor:pointer;-webkit-user-select:none;user-select:none}.pptx-ng-inspector__row{display:flex;align-items:center;gap:.35rem;margin-bottom:.35rem}.pptx-ng-inspector__row:last-child{margin-bottom:0}.pptx-ng-inspector__row--toggles{gap:.25rem}.pptx-ng-inspector__label{font-size:10px;color:var(--pptx-inspector-muted, #888);min-width:32px;text-align:right;flex-shrink:0}.pptx-ng-inspector__input{background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);color:inherit;border-radius:3px;padding:2px 4px;font-size:12px}.pptx-ng-inspector__input--number{width:62px;text-align:right}.pptx-ng-inspector__color{width:32px;height:22px;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;padding:1px;cursor:pointer;background:transparent;flex-shrink:0}.pptx-ng-inspector__toggle{width:26px;height:22px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);color:inherit;border-radius:3px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px}@media(pointer:coarse),(max-width:
|
|
41728
|
+
`, isInline: true, styles: [".pptx-ng-inspector{display:flex;flex-direction:column;gap:0;padding:.5rem;background:var(--pptx-inspector-bg, #1e1e1e);color:var(--pptx-inspector-fg, #e0e0e0);font-size:12px;min-width:220px;overflow-y:auto}.pptx-ng-inspector__section{padding:.5rem 0;border-bottom:1px solid var(--pptx-inspector-border, #333)}.pptx-ng-inspector__section:last-child{border-bottom:none}.pptx-ng-inspector__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0 0 .35rem}.pptx-ng-inspector__details{border-bottom:1px solid var(--pptx-inspector-border, #333)}.pptx-ng-inspector__summary{padding:.5rem 0;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);cursor:pointer;-webkit-user-select:none;user-select:none}.pptx-ng-inspector__row{display:flex;align-items:center;gap:.35rem;margin-bottom:.35rem}.pptx-ng-inspector__row:last-child{margin-bottom:0}.pptx-ng-inspector__row--toggles{gap:.25rem}.pptx-ng-inspector__label{font-size:10px;color:var(--pptx-inspector-muted, #888);min-width:32px;text-align:right;flex-shrink:0}.pptx-ng-inspector__input{background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);color:inherit;border-radius:3px;padding:2px 4px;font-size:12px}.pptx-ng-inspector__input--number{width:62px;text-align:right}.pptx-ng-inspector__color{width:32px;height:22px;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;padding:1px;cursor:pointer;background:transparent;flex-shrink:0}.pptx-ng-inspector__toggle{width:26px;height:22px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);color:inherit;border-radius:3px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px}.pptx-ng-inspector.is-mobile{width:100%;min-width:0;box-sizing:border-box;font-size:14px}.pptx-ng-inspector.is-mobile .pptx-ng-inspector__row{flex-wrap:wrap;gap:.5rem}.pptx-ng-inspector.is-mobile .pptx-ng-inspector__label{min-width:28px}.pptx-ng-inspector.is-mobile .pptx-ng-inspector__input{flex:1 1 auto;min-height:40px;font-size:16px;padding:6px 8px}.pptx-ng-inspector.is-mobile .pptx-ng-inspector__input--number{width:auto;min-width:72px}.pptx-ng-inspector.is-mobile .pptx-ng-inspector__color{width:44px;height:40px}.pptx-ng-inspector.is-mobile .pptx-ng-inspector__toggle{min-width:44px;width:auto;flex:1 1 auto;height:40px;font-size:15px}.pptx-ng-inspector.is-mobile .pptx-ng-inspector__btn{min-height:40px;padding:8px 10px;font-size:13px}@media(pointer:coarse),(max-width:767px){.pptx-ng-inspector{width:100%;min-width:0;box-sizing:border-box;font-size:14px}.pptx-ng-inspector__row{flex-wrap:wrap;gap:.5rem}.pptx-ng-inspector__label{min-width:28px}.pptx-ng-inspector__input{flex:1 1 auto;min-height:40px;font-size:16px;padding:6px 8px}.pptx-ng-inspector__input--number{width:auto;min-width:72px}.pptx-ng-inspector__color{width:44px;height:40px}.pptx-ng-inspector__toggle{min-width:44px;width:auto;flex:1 1 auto;height:40px;font-size:15px}.pptx-ng-inspector__btn{min-height:40px;padding:8px 10px;font-size:13px}}.pptx-ng-inspector__toggle.is-active{background:var(--pptx-inspector-active, #0078d4);border-color:var(--pptx-inspector-active, #0078d4);color:#fff}.pptx-ng-inspector__btn{flex:1;padding:3px 6px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);color:inherit;border-radius:3px;cursor:pointer;font-size:11px;white-space:nowrap}.pptx-ng-inspector__btn:hover{background:var(--pptx-inspector-hover, #3a3a3a)}.pptx-ng-inspector__btn--danger{color:var(--pptx-inspector-danger, #f47c7c);border-color:var(--pptx-inspector-danger-border, #6b2a2a)}.pptx-ng-inspector__btn--danger:hover{background:var(--pptx-inspector-danger-hover, #4a1a1a)}\n"], dependencies: [{ kind: "component", type: GradientPickerComponent, selector: "pptx-gradient-picker", inputs: ["element"], outputs: ["patch"] }, { kind: "component", type: EffectsPanelComponent, selector: "pptx-effects-panel", inputs: ["element"], outputs: ["patch"] }, { kind: "component", type: TextAdvancedPanelComponent, selector: "pptx-text-advanced-panel", inputs: ["element"], outputs: ["patch"] }, { kind: "component", type: TableDataEditorComponent, selector: "pptx-table-data-editor", inputs: ["element", "canEdit"], outputs: ["elementChange"] }, { kind: "component", type: ChartDataEditorComponent, selector: "pptx-chart-data-editor", inputs: ["element", "canEdit"], outputs: ["elementChange"] }, { kind: "component", type: SmartArtPropertiesComponent, selector: "pptx-smart-art-properties", inputs: ["smartArtData", "canEdit"], outputs: ["smartArtDataChange"] }, { kind: "component", type: AnimationAuthorPanelComponent, selector: "pptx-animation-author-panel", inputs: ["element", "slideIndex", "animations", "canEdit"], outputs: ["animationsChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
40669
41729
|
}
|
|
40670
41730
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: InspectorPanelComponent, decorators: [{
|
|
40671
41731
|
type: Component,
|
|
@@ -40677,7 +41737,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
40677
41737
|
ChartDataEditorComponent,
|
|
40678
41738
|
SmartArtPropertiesComponent,
|
|
40679
41739
|
AnimationAuthorPanelComponent,
|
|
40680
|
-
], template: `
|
|
41740
|
+
], providers: [IsMobileService], template: `
|
|
40681
41741
|
<!--
|
|
40682
41742
|
NOTE (mobile-safe inputs): every numeric / colour input is keyed on the
|
|
40683
41743
|
selected element's id via @if blocks. Angular destroys and recreates the
|
|
@@ -40687,7 +41747,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
40687
41747
|
mid-edit: the caret stays put and the on-screen keyboard does not dismiss.
|
|
40688
41748
|
All commits happen on (change) (blur), reading event.target.value.
|
|
40689
41749
|
-->
|
|
40690
|
-
<aside class="
|
|
41750
|
+
<aside [class]="inspectorClass()" aria-label="Element properties">
|
|
40691
41751
|
<!-- ── Transform: Position & Size ─────────────────────────────────── -->
|
|
40692
41752
|
<section class="pptx-ng-inspector__section">
|
|
40693
41753
|
<h3 class="pptx-ng-inspector__heading">Transform</h3>
|
|
@@ -40970,8 +42030,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
40970
42030
|
</div>
|
|
40971
42031
|
</section>
|
|
40972
42032
|
</aside>
|
|
40973
|
-
`, styles: [".pptx-ng-inspector{display:flex;flex-direction:column;gap:0;padding:.5rem;background:var(--pptx-inspector-bg, #1e1e1e);color:var(--pptx-inspector-fg, #e0e0e0);font-size:12px;min-width:220px;overflow-y:auto}.pptx-ng-inspector__section{padding:.5rem 0;border-bottom:1px solid var(--pptx-inspector-border, #333)}.pptx-ng-inspector__section:last-child{border-bottom:none}.pptx-ng-inspector__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0 0 .35rem}.pptx-ng-inspector__details{border-bottom:1px solid var(--pptx-inspector-border, #333)}.pptx-ng-inspector__summary{padding:.5rem 0;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);cursor:pointer;-webkit-user-select:none;user-select:none}.pptx-ng-inspector__row{display:flex;align-items:center;gap:.35rem;margin-bottom:.35rem}.pptx-ng-inspector__row:last-child{margin-bottom:0}.pptx-ng-inspector__row--toggles{gap:.25rem}.pptx-ng-inspector__label{font-size:10px;color:var(--pptx-inspector-muted, #888);min-width:32px;text-align:right;flex-shrink:0}.pptx-ng-inspector__input{background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);color:inherit;border-radius:3px;padding:2px 4px;font-size:12px}.pptx-ng-inspector__input--number{width:62px;text-align:right}.pptx-ng-inspector__color{width:32px;height:22px;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;padding:1px;cursor:pointer;background:transparent;flex-shrink:0}.pptx-ng-inspector__toggle{width:26px;height:22px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);color:inherit;border-radius:3px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px}@media(pointer:coarse),(max-width:
|
|
42033
|
+
`, styles: [".pptx-ng-inspector{display:flex;flex-direction:column;gap:0;padding:.5rem;background:var(--pptx-inspector-bg, #1e1e1e);color:var(--pptx-inspector-fg, #e0e0e0);font-size:12px;min-width:220px;overflow-y:auto}.pptx-ng-inspector__section{padding:.5rem 0;border-bottom:1px solid var(--pptx-inspector-border, #333)}.pptx-ng-inspector__section:last-child{border-bottom:none}.pptx-ng-inspector__heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);margin:0 0 .35rem}.pptx-ng-inspector__details{border-bottom:1px solid var(--pptx-inspector-border, #333)}.pptx-ng-inspector__summary{padding:.5rem 0;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--pptx-inspector-muted, #888);cursor:pointer;-webkit-user-select:none;user-select:none}.pptx-ng-inspector__row{display:flex;align-items:center;gap:.35rem;margin-bottom:.35rem}.pptx-ng-inspector__row:last-child{margin-bottom:0}.pptx-ng-inspector__row--toggles{gap:.25rem}.pptx-ng-inspector__label{font-size:10px;color:var(--pptx-inspector-muted, #888);min-width:32px;text-align:right;flex-shrink:0}.pptx-ng-inspector__input{background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);color:inherit;border-radius:3px;padding:2px 4px;font-size:12px}.pptx-ng-inspector__input--number{width:62px;text-align:right}.pptx-ng-inspector__color{width:32px;height:22px;border:1px solid var(--pptx-inspector-border, #444);border-radius:3px;padding:1px;cursor:pointer;background:transparent;flex-shrink:0}.pptx-ng-inspector__toggle{width:26px;height:22px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);color:inherit;border-radius:3px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:12px}.pptx-ng-inspector.is-mobile{width:100%;min-width:0;box-sizing:border-box;font-size:14px}.pptx-ng-inspector.is-mobile .pptx-ng-inspector__row{flex-wrap:wrap;gap:.5rem}.pptx-ng-inspector.is-mobile .pptx-ng-inspector__label{min-width:28px}.pptx-ng-inspector.is-mobile .pptx-ng-inspector__input{flex:1 1 auto;min-height:40px;font-size:16px;padding:6px 8px}.pptx-ng-inspector.is-mobile .pptx-ng-inspector__input--number{width:auto;min-width:72px}.pptx-ng-inspector.is-mobile .pptx-ng-inspector__color{width:44px;height:40px}.pptx-ng-inspector.is-mobile .pptx-ng-inspector__toggle{min-width:44px;width:auto;flex:1 1 auto;height:40px;font-size:15px}.pptx-ng-inspector.is-mobile .pptx-ng-inspector__btn{min-height:40px;padding:8px 10px;font-size:13px}@media(pointer:coarse),(max-width:767px){.pptx-ng-inspector{width:100%;min-width:0;box-sizing:border-box;font-size:14px}.pptx-ng-inspector__row{flex-wrap:wrap;gap:.5rem}.pptx-ng-inspector__label{min-width:28px}.pptx-ng-inspector__input{flex:1 1 auto;min-height:40px;font-size:16px;padding:6px 8px}.pptx-ng-inspector__input--number{width:auto;min-width:72px}.pptx-ng-inspector__color{width:44px;height:40px}.pptx-ng-inspector__toggle{min-width:44px;width:auto;flex:1 1 auto;height:40px;font-size:15px}.pptx-ng-inspector__btn{min-height:40px;padding:8px 10px;font-size:13px}}.pptx-ng-inspector__toggle.is-active{background:var(--pptx-inspector-active, #0078d4);border-color:var(--pptx-inspector-active, #0078d4);color:#fff}.pptx-ng-inspector__btn{flex:1;padding:3px 6px;background:var(--pptx-inspector-input-bg, #2d2d2d);border:1px solid var(--pptx-inspector-border, #444);color:inherit;border-radius:3px;cursor:pointer;font-size:11px;white-space:nowrap}.pptx-ng-inspector__btn:hover{background:var(--pptx-inspector-hover, #3a3a3a)}.pptx-ng-inspector__btn--danger{color:var(--pptx-inspector-danger, #f47c7c);border-color:var(--pptx-inspector-danger-border, #6b2a2a)}.pptx-ng-inspector__btn--danger:hover{background:var(--pptx-inspector-danger-hover, #4a1a1a)}\n"] }]
|
|
40974
42034
|
}], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], slideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideIndex", required: true }] }] } });
|
|
42035
|
+
// ── Pure helpers (no Angular / DOM; unit-testable without TestBed) ────────────
|
|
42036
|
+
/**
|
|
42037
|
+
* Map the mobile flag to the inspector root class list. On mobile the panel
|
|
42038
|
+
* gains the `is-mobile` modifier that makes it a full-width, touch-sized
|
|
42039
|
+
* bottom-sheet body (the orchestrator host wraps it with the drawer chrome).
|
|
42040
|
+
*/
|
|
42041
|
+
function inspectorRootClass(isMobile) {
|
|
42042
|
+
return isMobile ? 'pptx-ng-inspector is-mobile' : 'pptx-ng-inspector';
|
|
42043
|
+
}
|
|
40975
42044
|
// ── Module-private helpers ───────────────────────────────────────────────────
|
|
40976
42045
|
/** Extract a finite number from an input change event, or null if invalid. */
|
|
40977
42046
|
function numberFromEvent(event) {
|
|
@@ -40992,229 +42061,6 @@ function stringFromEvent(event) {
|
|
|
40992
42061
|
return val.length > 0 ? val : null;
|
|
40993
42062
|
}
|
|
40994
42063
|
|
|
40995
|
-
/**
|
|
40996
|
-
* is-mobile.ts: Injectable signal-based mobile-detection service plus a
|
|
40997
|
-
* pure `computeIsMobile` helper for unit-testing.
|
|
40998
|
-
*
|
|
40999
|
-
* Ported from: packages/react/src/viewer/hooks/useIsMobile.ts
|
|
41000
|
-
*
|
|
41001
|
-
* The service tracks two independent media conditions:
|
|
41002
|
-
* - `(pointer: coarse)`: primary pointer is a touch screen / stylus
|
|
41003
|
-
* - viewport width below {@link MOBILE_BREAKPOINT}
|
|
41004
|
-
*
|
|
41005
|
-
* Both conditions are evaluated once at construction time and then kept live
|
|
41006
|
-
* via `MediaQueryList.addEventListener`. The service is SSR-safe: when
|
|
41007
|
-
* `matchMedia` is not available (server / test environment without a DOM) all
|
|
41008
|
-
* signals default to `false` and no listeners are registered.
|
|
41009
|
-
*/
|
|
41010
|
-
// ---------------------------------------------------------------------------
|
|
41011
|
-
// Constants
|
|
41012
|
-
// ---------------------------------------------------------------------------
|
|
41013
|
-
/** Viewport width (px) below which the UI switches to mobile layout. */
|
|
41014
|
-
const MOBILE_BREAKPOINT = 768;
|
|
41015
|
-
/** Tablet breakpoint: below this width (but >= MOBILE) is tablet. */
|
|
41016
|
-
const TABLET_BREAKPOINT = 1024;
|
|
41017
|
-
/**
|
|
41018
|
-
* Max viewport height (px) at which a *touch* device is treated as mobile
|
|
41019
|
-
* regardless of width. Catches landscape phones (e.g. 915×412), which are wide
|
|
41020
|
-
* enough to fall in the "tablet" width band but far too short for the desktop
|
|
41021
|
-
* ribbon + side panels, so they need the mobile chrome. Tablets in landscape are
|
|
41022
|
-
* taller (~760px+) so they stay on the desktop layout. Mirrors React's
|
|
41023
|
-
* `MOBILE_LANDSCAPE_MAX_HEIGHT` in useIsMobile.ts.
|
|
41024
|
-
*/
|
|
41025
|
-
const MOBILE_LANDSCAPE_MAX_HEIGHT = 500;
|
|
41026
|
-
// ---------------------------------------------------------------------------
|
|
41027
|
-
// Pure helpers (no Angular deps, safe in vitest without a DOM)
|
|
41028
|
-
// ---------------------------------------------------------------------------
|
|
41029
|
-
/**
|
|
41030
|
-
* Decide whether the current environment should use the mobile layout:
|
|
41031
|
-
* - a narrow viewport (`width < MOBILE_BREAKPOINT`), OR
|
|
41032
|
-
* - a short *touch* viewport below the tablet width: a landscape phone, which
|
|
41033
|
-
* is wide enough to look like a tablet but far too short for the desktop
|
|
41034
|
-
* ribbon + side panels.
|
|
41035
|
-
*
|
|
41036
|
-
* This mirrors React's `isMobileViewport(width, height, isTouch)` so the three
|
|
41037
|
-
* frameworks switch chrome at the same breakpoints (and the shared mobile e2e
|
|
41038
|
-
* specs pass identically). A tall touch tablet (e.g. 820×1180) is NOT mobile.
|
|
41039
|
-
*
|
|
41040
|
-
* @pure: no side effects, fully testable without a DOM.
|
|
41041
|
-
*/
|
|
41042
|
-
function computeIsMobile(width, height, isTouch) {
|
|
41043
|
-
if (width < MOBILE_BREAKPOINT) {
|
|
41044
|
-
return true;
|
|
41045
|
-
}
|
|
41046
|
-
return isTouch && height > 0 && height < MOBILE_LANDSCAPE_MAX_HEIGHT && width < TABLET_BREAKPOINT;
|
|
41047
|
-
}
|
|
41048
|
-
/**
|
|
41049
|
-
* Decide whether the current environment is "tablet" (desktop chrome, but in
|
|
41050
|
-
* the 768–1023px width band). A short landscape-phone touch viewport is mobile,
|
|
41051
|
-
* not tablet (handled by {@link computeIsMobile}).
|
|
41052
|
-
*
|
|
41053
|
-
* @pure
|
|
41054
|
-
*/
|
|
41055
|
-
function computeIsTablet(width, height, isTouch) {
|
|
41056
|
-
if (computeIsMobile(width, height, isTouch)) {
|
|
41057
|
-
return false;
|
|
41058
|
-
}
|
|
41059
|
-
return width >= MOBILE_BREAKPOINT && width < TABLET_BREAKPOINT;
|
|
41060
|
-
}
|
|
41061
|
-
// ---------------------------------------------------------------------------
|
|
41062
|
-
// IsMobileService
|
|
41063
|
-
// ---------------------------------------------------------------------------
|
|
41064
|
-
/**
|
|
41065
|
-
* `IsMobileService`: provides reactive signals for the current viewport /
|
|
41066
|
-
* pointer kind so components can switch between mobile and desktop chrome
|
|
41067
|
-
* without subscribing to resize events themselves.
|
|
41068
|
-
*
|
|
41069
|
-
* Inject at the component level (or provide at root); the service cleans up
|
|
41070
|
-
* its `MediaQueryList` listeners automatically via `DestroyRef`.
|
|
41071
|
-
*
|
|
41072
|
-
* ```ts
|
|
41073
|
-
* providers: [IsMobileService]
|
|
41074
|
-
* // or globally:
|
|
41075
|
-
* // provideIsMobile() (see factory below)
|
|
41076
|
-
* ```
|
|
41077
|
-
*
|
|
41078
|
-
* ```ts
|
|
41079
|
-
* protected readonly mobile = inject(IsMobileService);
|
|
41080
|
-
* // in template: @if (mobile.isMobile()) { … }
|
|
41081
|
-
* ```
|
|
41082
|
-
*/
|
|
41083
|
-
class IsMobileService {
|
|
41084
|
-
/** True when the primary pointer is coarse (touch / stylus). */
|
|
41085
|
-
isCoarsePointer = signal(false, /* @ts-ignore */
|
|
41086
|
-
...(ngDevMode ? [{ debugName: "isCoarsePointer" }] : /* istanbul ignore next */ []));
|
|
41087
|
-
/** True when the viewport width is below {@link MOBILE_BREAKPOINT}. */
|
|
41088
|
-
isNarrowViewport = signal(false, /* @ts-ignore */
|
|
41089
|
-
...(ngDevMode ? [{ debugName: "isNarrowViewport" }] : /* istanbul ignore next */ []));
|
|
41090
|
-
/**
|
|
41091
|
-
* True when either the pointer is coarse OR the viewport is narrow.
|
|
41092
|
-
* Use this as the single gate for showing mobile chrome.
|
|
41093
|
-
*/
|
|
41094
|
-
isMobile = signal(false, /* @ts-ignore */
|
|
41095
|
-
...(ngDevMode ? [{ debugName: "isMobile" }] : /* istanbul ignore next */ []));
|
|
41096
|
-
/** True when the viewport is in the tablet range (desktop pointer only). */
|
|
41097
|
-
isTablet = signal(false, /* @ts-ignore */
|
|
41098
|
-
...(ngDevMode ? [{ debugName: "isTablet" }] : /* istanbul ignore next */ []));
|
|
41099
|
-
/**
|
|
41100
|
-
* CSS pixels the on-screen keyboard currently covers at the bottom of the
|
|
41101
|
-
* layout viewport (0 when no keyboard is open). The orchestrator lifts the
|
|
41102
|
-
* fixed mobile bottom bar by this amount so it stays above the keyboard.
|
|
41103
|
-
*/
|
|
41104
|
-
keyboardInset = signal(0, /* @ts-ignore */
|
|
41105
|
-
...(ngDevMode ? [{ debugName: "keyboardInset" }] : /* istanbul ignore next */ []));
|
|
41106
|
-
/** True while {@link keyboardInset} is large enough to count as "open". */
|
|
41107
|
-
isKeyboardOpen = signal(false, /* @ts-ignore */
|
|
41108
|
-
...(ngDevMode ? [{ debugName: "isKeyboardOpen" }] : /* istanbul ignore next */ []));
|
|
41109
|
-
constructor() {
|
|
41110
|
-
const destroyRef = inject(DestroyRef);
|
|
41111
|
-
// Guard: matchMedia / window may not be available in SSR / test envs.
|
|
41112
|
-
if (typeof matchMedia !== 'function' || typeof window === 'undefined') {
|
|
41113
|
-
return;
|
|
41114
|
-
}
|
|
41115
|
-
this._wireKeyboardInset(destroyRef);
|
|
41116
|
-
// ── Coarse-pointer media query (drives the touch flag) ───────────────────
|
|
41117
|
-
const coarseMql = matchMedia('(pointer: coarse)');
|
|
41118
|
-
this.isCoarsePointer.set(coarseMql.matches);
|
|
41119
|
-
const onCoarseChange = (evt) => {
|
|
41120
|
-
this.isCoarsePointer.set(evt.matches);
|
|
41121
|
-
this._recompute();
|
|
41122
|
-
};
|
|
41123
|
-
coarseMql.addEventListener('change', onCoarseChange);
|
|
41124
|
-
// ── Viewport size tracking ───────────────────────────────────────────────
|
|
41125
|
-
// Width-only media queries cannot express the "short landscape phone"
|
|
41126
|
-
// rule (which depends on height + touch), so track the live viewport size
|
|
41127
|
-
// on resize and recompute the derived flags from width/height/touch.
|
|
41128
|
-
const onResize = () => this._recompute();
|
|
41129
|
-
window.addEventListener('resize', onResize);
|
|
41130
|
-
if (typeof screen !== 'undefined' && screen.orientation) {
|
|
41131
|
-
screen.orientation.addEventListener('change', onResize);
|
|
41132
|
-
}
|
|
41133
|
-
this._recompute();
|
|
41134
|
-
// ── Cleanup on destroy ───────────────────────────────────────────────────
|
|
41135
|
-
destroyRef.onDestroy(() => {
|
|
41136
|
-
coarseMql.removeEventListener('change', onCoarseChange);
|
|
41137
|
-
window.removeEventListener('resize', onResize);
|
|
41138
|
-
if (typeof screen !== 'undefined' && screen.orientation) {
|
|
41139
|
-
screen.orientation.removeEventListener('change', onResize);
|
|
41140
|
-
}
|
|
41141
|
-
});
|
|
41142
|
-
}
|
|
41143
|
-
/** Recompute all derived flags from the live viewport size + pointer kind. */
|
|
41144
|
-
_recompute() {
|
|
41145
|
-
const width = window.innerWidth;
|
|
41146
|
-
const height = window.innerHeight;
|
|
41147
|
-
const touch = this.isCoarsePointer();
|
|
41148
|
-
this.isNarrowViewport.set(width < MOBILE_BREAKPOINT);
|
|
41149
|
-
this.isMobile.set(computeIsMobile(width, height, touch));
|
|
41150
|
-
this.isTablet.set(computeIsTablet(width, height, touch));
|
|
41151
|
-
}
|
|
41152
|
-
/**
|
|
41153
|
-
* Track the on-screen-keyboard inset via the `VisualViewport` API and keep the
|
|
41154
|
-
* focused editable visible: when the keyboard shrinks the visual viewport,
|
|
41155
|
-
* update {@link keyboardInset} / {@link isKeyboardOpen} and scroll the active
|
|
41156
|
-
* input / textarea / contenteditable into the area above the keyboard. No-op
|
|
41157
|
-
* when `visualViewport` is unavailable (desktop / SSR), so desktop is unchanged.
|
|
41158
|
-
*/
|
|
41159
|
-
_wireKeyboardInset(destroyRef) {
|
|
41160
|
-
const vv = window.visualViewport;
|
|
41161
|
-
if (!vv) {
|
|
41162
|
-
return;
|
|
41163
|
-
}
|
|
41164
|
-
const update = () => {
|
|
41165
|
-
const metrics = readViewportMetrics(window);
|
|
41166
|
-
const inset = metrics ? computeKeyboardInset(metrics) : 0;
|
|
41167
|
-
this.keyboardInset.set(inset);
|
|
41168
|
-
this.isKeyboardOpen.set(isKeyboardOpen(inset));
|
|
41169
|
-
if (inset > 0) {
|
|
41170
|
-
window.requestAnimationFrame(() => this._scrollFocusedIntoView(inset));
|
|
41171
|
-
}
|
|
41172
|
-
};
|
|
41173
|
-
const onFocusIn = () => {
|
|
41174
|
-
window.requestAnimationFrame(() => {
|
|
41175
|
-
const metrics = readViewportMetrics(window);
|
|
41176
|
-
const inset = metrics ? computeKeyboardInset(metrics) : 0;
|
|
41177
|
-
if (inset > 0) {
|
|
41178
|
-
this._scrollFocusedIntoView(inset);
|
|
41179
|
-
}
|
|
41180
|
-
});
|
|
41181
|
-
};
|
|
41182
|
-
update();
|
|
41183
|
-
vv.addEventListener('resize', update);
|
|
41184
|
-
vv.addEventListener('scroll', update);
|
|
41185
|
-
document.addEventListener('focusin', onFocusIn);
|
|
41186
|
-
destroyRef.onDestroy(() => {
|
|
41187
|
-
vv.removeEventListener('resize', update);
|
|
41188
|
-
vv.removeEventListener('scroll', update);
|
|
41189
|
-
document.removeEventListener('focusin', onFocusIn);
|
|
41190
|
-
});
|
|
41191
|
-
}
|
|
41192
|
-
/** Scroll the focused editable into the area above the keyboard, if needed. */
|
|
41193
|
-
_scrollFocusedIntoView(keyboardInset) {
|
|
41194
|
-
if (keyboardInset <= 0 || typeof document === 'undefined') {
|
|
41195
|
-
return;
|
|
41196
|
-
}
|
|
41197
|
-
const active = document.activeElement;
|
|
41198
|
-
if (!(active instanceof HTMLElement)) {
|
|
41199
|
-
return;
|
|
41200
|
-
}
|
|
41201
|
-
const tag = active.tagName;
|
|
41202
|
-
if (tag !== 'INPUT' && tag !== 'TEXTAREA' && !active.isContentEditable) {
|
|
41203
|
-
return;
|
|
41204
|
-
}
|
|
41205
|
-
const rect = active.getBoundingClientRect();
|
|
41206
|
-
const delta = computeScrollDelta({ top: rect.top, bottom: rect.bottom }, window.innerHeight, keyboardInset);
|
|
41207
|
-
if (delta !== 0) {
|
|
41208
|
-
window.scrollBy({ top: delta, behavior: 'smooth' });
|
|
41209
|
-
}
|
|
41210
|
-
}
|
|
41211
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: IsMobileService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
41212
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: IsMobileService });
|
|
41213
|
-
}
|
|
41214
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: IsMobileService, decorators: [{
|
|
41215
|
-
type: Injectable
|
|
41216
|
-
}], ctorParameters: () => [] });
|
|
41217
|
-
|
|
41218
42064
|
/**
|
|
41219
42065
|
* `LoadContentService`: Angular port of the React `useLoadContent` hook and
|
|
41220
42066
|
* the Vue `useLoadContent` composable.
|
|
@@ -53172,6 +54018,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
53172
54018
|
* }
|
|
53173
54019
|
* ```
|
|
53174
54020
|
*/
|
|
54021
|
+
/**
|
|
54022
|
+
* Map the mobile flag to the print-dialog panel class list. Pure (no Angular /
|
|
54023
|
+
* DOM) so it can be unit-tested without TestBed. On mobile the dialog gains the
|
|
54024
|
+
* `is-mobile` modifier that turns it into a full-width bottom sheet.
|
|
54025
|
+
*/
|
|
54026
|
+
function printDialogClass(isMobile) {
|
|
54027
|
+
return isMobile ? 'pptx-ng-print-dialog is-mobile' : 'pptx-ng-print-dialog';
|
|
54028
|
+
}
|
|
53175
54029
|
class PrintDialogComponent {
|
|
53176
54030
|
// -------------------------------------------------------------------------
|
|
53177
54031
|
// Inputs / outputs
|
|
@@ -53192,6 +54046,14 @@ class PrintDialogComponent {
|
|
|
53192
54046
|
print = output();
|
|
53193
54047
|
/** Emits when the dialog is dismissed (Cancel / Escape / backdrop). */
|
|
53194
54048
|
cancel = output();
|
|
54049
|
+
/** Reactive viewport / pointer flags (drives the bottom-sheet layout). */
|
|
54050
|
+
mobile = inject(IsMobileService);
|
|
54051
|
+
/**
|
|
54052
|
+
* Dialog class list: gains the `is-mobile` bottom-sheet modifier under the
|
|
54053
|
+
* mobile breakpoint. See {@link printDialogClass}.
|
|
54054
|
+
*/
|
|
54055
|
+
dialogClass = computed(() => printDialogClass(this.mobile.isMobile()), /* @ts-ignore */
|
|
54056
|
+
...(ngDevMode ? [{ debugName: "dialogClass" }] : /* istanbul ignore next */ []));
|
|
53195
54057
|
// -------------------------------------------------------------------------
|
|
53196
54058
|
// State
|
|
53197
54059
|
// -------------------------------------------------------------------------
|
|
@@ -53259,15 +54121,16 @@ class PrintDialogComponent {
|
|
|
53259
54121
|
}
|
|
53260
54122
|
}
|
|
53261
54123
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: PrintDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
53262
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.2", type: PrintDialogComponent, isStandalone: true, selector: "pptx-print-dialog", inputs: { slides: { classPropertyName: "slides", publicName: "slides", isSignal: true, isRequired: true, transformFunction: null }, activeSlideIndex: { classPropertyName: "activeSlideIndex", publicName: "activeSlideIndex", isSignal: true, isRequired: true, transformFunction: null }, defaultSlidesPerPage: { classPropertyName: "defaultSlidesPerPage", publicName: "defaultSlidesPerPage", isSignal: true, isRequired: false, transformFunction: null }, defaultFrameSlides: { classPropertyName: "defaultFrameSlides", publicName: "defaultFrameSlides", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { print: "print", cancel: "cancel" }, host: { listeners: { "document:keydown": "onKeydown($event)" } }, ngImport: i0, template: `
|
|
54124
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.2", type: PrintDialogComponent, isStandalone: true, selector: "pptx-print-dialog", inputs: { slides: { classPropertyName: "slides", publicName: "slides", isSignal: true, isRequired: true, transformFunction: null }, activeSlideIndex: { classPropertyName: "activeSlideIndex", publicName: "activeSlideIndex", isSignal: true, isRequired: true, transformFunction: null }, defaultSlidesPerPage: { classPropertyName: "defaultSlidesPerPage", publicName: "defaultSlidesPerPage", isSignal: true, isRequired: false, transformFunction: null }, defaultFrameSlides: { classPropertyName: "defaultFrameSlides", publicName: "defaultFrameSlides", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { print: "print", cancel: "cancel" }, host: { listeners: { "document:keydown": "onKeydown($event)" } }, providers: [IsMobileService], ngImport: i0, template: `
|
|
53263
54125
|
<div
|
|
53264
54126
|
class="pptx-ng-print-dialog__backdrop"
|
|
54127
|
+
[class.is-mobile]="mobile.isMobile()"
|
|
53265
54128
|
role="dialog"
|
|
53266
54129
|
aria-modal="true"
|
|
53267
54130
|
aria-label="Print"
|
|
53268
54131
|
(click)="onBackdropClick($event)"
|
|
53269
54132
|
>
|
|
53270
|
-
<div class="
|
|
54133
|
+
<div [class]="dialogClass()">
|
|
53271
54134
|
<!-- Header -->
|
|
53272
54135
|
<div class="pptx-ng-print-dialog__header">
|
|
53273
54136
|
<h2 class="pptx-ng-print-dialog__title">Print</h2>
|
|
@@ -53313,19 +54176,20 @@ class PrintDialogComponent {
|
|
|
53313
54176
|
</div>
|
|
53314
54177
|
</div>
|
|
53315
54178
|
</div>
|
|
53316
|
-
`, isInline: true, styles: [".pptx-ng-print-dialog__backdrop{position:fixed;inset:0;z-index:1200;display:flex;align-items:center;justify-content:center;background:#0009;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.pptx-ng-print-dialog{display:flex;flex-direction:column;width:780px;max-width:calc(100vw - 2rem);max-height:90vh;border:1px solid rgba(255,255,255,.12);border-radius:.75rem;background:#1e1e1e;color:#e5e5e5;box-shadow:0 20px 60px #0009}.pptx-ng-print-dialog__header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid rgba(255,255,255,.1)}.pptx-ng-print-dialog__title{margin:0;font-size:.875rem;font-weight:600;color:#fff}.pptx-ng-print-dialog__icon-btn{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;padding:0;border:0;border-radius:.25rem;background:transparent;color:#fff9;font-size:.75rem;cursor:pointer;transition:background .12s,color .12s}.pptx-ng-print-dialog__icon-btn:hover{background:#ffffff1a;color:#fff}.pptx-ng-print-dialog__body{flex:1;overflow-y:auto;padding:1rem 1.25rem}.pptx-ng-print-dialog__footer{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1.25rem;border-top:1px solid rgba(255,255,255,.1)}.pptx-ng-print-dialog__estimate{font-size:.75rem;color:#ffffff80}.pptx-ng-print-dialog__actions{display:flex;gap:.5rem}.pptx-ng-print-dialog__btn{padding:.5rem 1rem;border:1px solid rgba(255,255,255,.15);border-radius:.5rem;background:#ffffff0a;color:#ffffffb3;font-size:.8125rem;cursor:pointer;transition:background .12s,color .12s,border-color .12s}.pptx-ng-print-dialog__btn:hover{background:#ffffff1a;color:#fff}.pptx-ng-print-dialog__btn--primary{border-color:#3b82f6;background:#3b82f6;color:#fff}.pptx-ng-print-dialog__btn--primary:hover{background:#2f6fd6}\n"], dependencies: [{ kind: "component", type: PrintSettingsPanelComponent, selector: "pptx-print-settings-panel", inputs: ["settings", "totalSlides", "activeSlideIndex"], outputs: ["settingsChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
54179
|
+
`, isInline: true, styles: [".pptx-ng-print-dialog__backdrop{position:fixed;inset:0;z-index:1200;display:flex;align-items:center;justify-content:center;background:#0009;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.pptx-ng-print-dialog{display:flex;flex-direction:column;width:780px;max-width:calc(100vw - 2rem);max-height:90vh;border:1px solid rgba(255,255,255,.12);border-radius:.75rem;background:#1e1e1e;color:#e5e5e5;box-shadow:0 20px 60px #0009}.pptx-ng-print-dialog__header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid rgba(255,255,255,.1)}.pptx-ng-print-dialog__title{margin:0;font-size:.875rem;font-weight:600;color:#fff}.pptx-ng-print-dialog__icon-btn{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;padding:0;border:0;border-radius:.25rem;background:transparent;color:#fff9;font-size:.75rem;cursor:pointer;transition:background .12s,color .12s}.pptx-ng-print-dialog__icon-btn:hover{background:#ffffff1a;color:#fff}.pptx-ng-print-dialog__body{flex:1;overflow-y:auto;padding:1rem 1.25rem}.pptx-ng-print-dialog__footer{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1.25rem;border-top:1px solid rgba(255,255,255,.1)}.pptx-ng-print-dialog__estimate{font-size:.75rem;color:#ffffff80}.pptx-ng-print-dialog__actions{display:flex;gap:.5rem}.pptx-ng-print-dialog__btn{padding:.5rem 1rem;border:1px solid rgba(255,255,255,.15);border-radius:.5rem;background:#ffffff0a;color:#ffffffb3;font-size:.8125rem;cursor:pointer;transition:background .12s,color .12s,border-color .12s}.pptx-ng-print-dialog__btn:hover{background:#ffffff1a;color:#fff}.pptx-ng-print-dialog__btn--primary{border-color:#3b82f6;background:#3b82f6;color:#fff}.pptx-ng-print-dialog__btn--primary:hover{background:#2f6fd6}.pptx-ng-print-dialog__backdrop.is-mobile{align-items:flex-end}.pptx-ng-print-dialog.is-mobile{width:100%;max-width:none;max-height:88dvh;border-left:none;border-right:none;border-bottom:none;border-top-left-radius:1rem;border-top-right-radius:1rem;border-bottom-left-radius:0;border-bottom-right-radius:0;padding-bottom:max(env(safe-area-inset-bottom),0px)}@media(max-width:767px),(pointer:coarse){.pptx-ng-print-dialog__backdrop{align-items:flex-end}.pptx-ng-print-dialog{width:100%;max-width:none;max-height:88dvh;border-left:none;border-right:none;border-bottom:none;border-top-left-radius:1rem;border-top-right-radius:1rem;border-bottom-left-radius:0;border-bottom-right-radius:0;padding-bottom:max(env(safe-area-inset-bottom),0px)}}\n"], dependencies: [{ kind: "component", type: PrintSettingsPanelComponent, selector: "pptx-print-settings-panel", inputs: ["settings", "totalSlides", "activeSlideIndex"], outputs: ["settingsChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
53317
54180
|
}
|
|
53318
54181
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: PrintDialogComponent, decorators: [{
|
|
53319
54182
|
type: Component,
|
|
53320
|
-
args: [{ selector: 'pptx-print-dialog', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [PrintSettingsPanelComponent], template: `
|
|
54183
|
+
args: [{ selector: 'pptx-print-dialog', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [PrintSettingsPanelComponent], providers: [IsMobileService], template: `
|
|
53321
54184
|
<div
|
|
53322
54185
|
class="pptx-ng-print-dialog__backdrop"
|
|
54186
|
+
[class.is-mobile]="mobile.isMobile()"
|
|
53323
54187
|
role="dialog"
|
|
53324
54188
|
aria-modal="true"
|
|
53325
54189
|
aria-label="Print"
|
|
53326
54190
|
(click)="onBackdropClick($event)"
|
|
53327
54191
|
>
|
|
53328
|
-
<div class="
|
|
54192
|
+
<div [class]="dialogClass()">
|
|
53329
54193
|
<!-- Header -->
|
|
53330
54194
|
<div class="pptx-ng-print-dialog__header">
|
|
53331
54195
|
<h2 class="pptx-ng-print-dialog__title">Print</h2>
|
|
@@ -53371,7 +54235,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
53371
54235
|
</div>
|
|
53372
54236
|
</div>
|
|
53373
54237
|
</div>
|
|
53374
|
-
`, styles: [".pptx-ng-print-dialog__backdrop{position:fixed;inset:0;z-index:1200;display:flex;align-items:center;justify-content:center;background:#0009;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.pptx-ng-print-dialog{display:flex;flex-direction:column;width:780px;max-width:calc(100vw - 2rem);max-height:90vh;border:1px solid rgba(255,255,255,.12);border-radius:.75rem;background:#1e1e1e;color:#e5e5e5;box-shadow:0 20px 60px #0009}.pptx-ng-print-dialog__header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid rgba(255,255,255,.1)}.pptx-ng-print-dialog__title{margin:0;font-size:.875rem;font-weight:600;color:#fff}.pptx-ng-print-dialog__icon-btn{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;padding:0;border:0;border-radius:.25rem;background:transparent;color:#fff9;font-size:.75rem;cursor:pointer;transition:background .12s,color .12s}.pptx-ng-print-dialog__icon-btn:hover{background:#ffffff1a;color:#fff}.pptx-ng-print-dialog__body{flex:1;overflow-y:auto;padding:1rem 1.25rem}.pptx-ng-print-dialog__footer{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1.25rem;border-top:1px solid rgba(255,255,255,.1)}.pptx-ng-print-dialog__estimate{font-size:.75rem;color:#ffffff80}.pptx-ng-print-dialog__actions{display:flex;gap:.5rem}.pptx-ng-print-dialog__btn{padding:.5rem 1rem;border:1px solid rgba(255,255,255,.15);border-radius:.5rem;background:#ffffff0a;color:#ffffffb3;font-size:.8125rem;cursor:pointer;transition:background .12s,color .12s,border-color .12s}.pptx-ng-print-dialog__btn:hover{background:#ffffff1a;color:#fff}.pptx-ng-print-dialog__btn--primary{border-color:#3b82f6;background:#3b82f6;color:#fff}.pptx-ng-print-dialog__btn--primary:hover{background:#2f6fd6}\n"] }]
|
|
54238
|
+
`, styles: [".pptx-ng-print-dialog__backdrop{position:fixed;inset:0;z-index:1200;display:flex;align-items:center;justify-content:center;background:#0009;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.pptx-ng-print-dialog{display:flex;flex-direction:column;width:780px;max-width:calc(100vw - 2rem);max-height:90vh;border:1px solid rgba(255,255,255,.12);border-radius:.75rem;background:#1e1e1e;color:#e5e5e5;box-shadow:0 20px 60px #0009}.pptx-ng-print-dialog__header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid rgba(255,255,255,.1)}.pptx-ng-print-dialog__title{margin:0;font-size:.875rem;font-weight:600;color:#fff}.pptx-ng-print-dialog__icon-btn{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;padding:0;border:0;border-radius:.25rem;background:transparent;color:#fff9;font-size:.75rem;cursor:pointer;transition:background .12s,color .12s}.pptx-ng-print-dialog__icon-btn:hover{background:#ffffff1a;color:#fff}.pptx-ng-print-dialog__body{flex:1;overflow-y:auto;padding:1rem 1.25rem}.pptx-ng-print-dialog__footer{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1.25rem;border-top:1px solid rgba(255,255,255,.1)}.pptx-ng-print-dialog__estimate{font-size:.75rem;color:#ffffff80}.pptx-ng-print-dialog__actions{display:flex;gap:.5rem}.pptx-ng-print-dialog__btn{padding:.5rem 1rem;border:1px solid rgba(255,255,255,.15);border-radius:.5rem;background:#ffffff0a;color:#ffffffb3;font-size:.8125rem;cursor:pointer;transition:background .12s,color .12s,border-color .12s}.pptx-ng-print-dialog__btn:hover{background:#ffffff1a;color:#fff}.pptx-ng-print-dialog__btn--primary{border-color:#3b82f6;background:#3b82f6;color:#fff}.pptx-ng-print-dialog__btn--primary:hover{background:#2f6fd6}.pptx-ng-print-dialog__backdrop.is-mobile{align-items:flex-end}.pptx-ng-print-dialog.is-mobile{width:100%;max-width:none;max-height:88dvh;border-left:none;border-right:none;border-bottom:none;border-top-left-radius:1rem;border-top-right-radius:1rem;border-bottom-left-radius:0;border-bottom-right-radius:0;padding-bottom:max(env(safe-area-inset-bottom),0px)}@media(max-width:767px),(pointer:coarse){.pptx-ng-print-dialog__backdrop{align-items:flex-end}.pptx-ng-print-dialog{width:100%;max-width:none;max-height:88dvh;border-left:none;border-right:none;border-bottom:none;border-top-left-radius:1rem;border-top-right-radius:1rem;border-bottom-left-radius:0;border-bottom-right-radius:0;padding-bottom:max(env(safe-area-inset-bottom),0px)}}\n"] }]
|
|
53375
54239
|
}], ctorParameters: () => [], propDecorators: { slides: [{ type: i0.Input, args: [{ isSignal: true, alias: "slides", required: true }] }], activeSlideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeSlideIndex", required: true }] }], defaultSlidesPerPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultSlidesPerPage", required: false }] }], defaultFrameSlides: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultFrameSlides", required: false }] }], print: [{ type: i0.Output, args: ["print"] }], cancel: [{ type: i0.Output, args: ["cancel"] }], onKeydown: [{
|
|
53376
54240
|
type: HostListener,
|
|
53377
54241
|
args: ['document:keydown', ['$event']]
|