pptx-angular-viewer 1.1.41 → 1.1.42
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';
|
|
@@ -26142,6 +26142,298 @@ function issueTrackKey(issue, index) {
|
|
|
26142
26142
|
return `${issue.slideIndex}-${issue.type}-${issue.elementId ?? 'slide'}-${index}`;
|
|
26143
26143
|
}
|
|
26144
26144
|
|
|
26145
|
+
/**
|
|
26146
|
+
* Accessibility utilities for the PowerPoint viewer.
|
|
26147
|
+
*
|
|
26148
|
+
* Provides functions for computing reading order, generating ARIA labels,
|
|
26149
|
+
* determining ARIA roles, and detecting reduced-motion preferences.
|
|
26150
|
+
*
|
|
26151
|
+
* Pure (framework-agnostic): consumed by every binding's element renderer.
|
|
26152
|
+
*
|
|
26153
|
+
* @module render/accessibility
|
|
26154
|
+
*/
|
|
26155
|
+
// ---------------------------------------------------------------------------
|
|
26156
|
+
// Reading order
|
|
26157
|
+
// ---------------------------------------------------------------------------
|
|
26158
|
+
/**
|
|
26159
|
+
* Computes a reading-order index for each element on a slide.
|
|
26160
|
+
*
|
|
26161
|
+
* Elements are sorted top-to-bottom first, then left-to-right for elements
|
|
26162
|
+
* at roughly the same vertical position (within a tolerance band).
|
|
26163
|
+
* Returns a `Map<elementId, tabIndex>` with 1-based indices.
|
|
26164
|
+
*
|
|
26165
|
+
* @param elements - The flat list of elements on the current slide.
|
|
26166
|
+
* @param tolerancePx - Vertical tolerance for grouping elements into the
|
|
26167
|
+
* same "row" (default 20px).
|
|
26168
|
+
* @returns Map from element ID to 1-based reading order index.
|
|
26169
|
+
*/
|
|
26170
|
+
function computeReadingOrder(elements, tolerancePx = 20) {
|
|
26171
|
+
if (elements.length === 0) {
|
|
26172
|
+
return new Map();
|
|
26173
|
+
}
|
|
26174
|
+
const sorted = [...elements]
|
|
26175
|
+
.filter((el) => !el.hidden)
|
|
26176
|
+
.sort((a, b) => {
|
|
26177
|
+
const dy = a.y - b.y;
|
|
26178
|
+
if (Math.abs(dy) > tolerancePx) {
|
|
26179
|
+
return dy;
|
|
26180
|
+
}
|
|
26181
|
+
return a.x - b.x;
|
|
26182
|
+
});
|
|
26183
|
+
const result = new Map();
|
|
26184
|
+
sorted.forEach((el, idx) => {
|
|
26185
|
+
result.set(el.id, idx + 1);
|
|
26186
|
+
});
|
|
26187
|
+
return result;
|
|
26188
|
+
}
|
|
26189
|
+
// ---------------------------------------------------------------------------
|
|
26190
|
+
// ARIA roles
|
|
26191
|
+
// ---------------------------------------------------------------------------
|
|
26192
|
+
/**
|
|
26193
|
+
* Maps a PptxElement to its appropriate ARIA role.
|
|
26194
|
+
*
|
|
26195
|
+
* - image / picture => "img"
|
|
26196
|
+
* - table => "table"
|
|
26197
|
+
* - group => "group"
|
|
26198
|
+
* - chart => "img" (complex visualisation treated as image)
|
|
26199
|
+
* - text / shape with text => "text" is not valid; use undefined and rely on
|
|
26200
|
+
* aria-label alone. However, shapes without text act as decorative images.
|
|
26201
|
+
* - connector => "img"
|
|
26202
|
+
* - media => "application"
|
|
26203
|
+
*
|
|
26204
|
+
* @param element - The element to determine a role for.
|
|
26205
|
+
* @returns The ARIA role string, or undefined when none is needed.
|
|
26206
|
+
*/
|
|
26207
|
+
function getAriaRole(element) {
|
|
26208
|
+
switch (element.type) {
|
|
26209
|
+
case 'image':
|
|
26210
|
+
case 'picture':
|
|
26211
|
+
return 'img';
|
|
26212
|
+
case 'table':
|
|
26213
|
+
return 'table';
|
|
26214
|
+
case 'group':
|
|
26215
|
+
return 'group';
|
|
26216
|
+
case 'chart':
|
|
26217
|
+
return 'img';
|
|
26218
|
+
case 'smartArt':
|
|
26219
|
+
return 'img';
|
|
26220
|
+
case 'connector':
|
|
26221
|
+
return 'img';
|
|
26222
|
+
case 'media':
|
|
26223
|
+
return 'application';
|
|
26224
|
+
case 'ink':
|
|
26225
|
+
return 'img';
|
|
26226
|
+
case 'model3d':
|
|
26227
|
+
return 'img';
|
|
26228
|
+
case 'text':
|
|
26229
|
+
return undefined;
|
|
26230
|
+
case 'shape': {
|
|
26231
|
+
// Shapes with text act as text containers; shapes without text are decorative
|
|
26232
|
+
if (hasTextProperties(element) && element.text) {
|
|
26233
|
+
return undefined;
|
|
26234
|
+
}
|
|
26235
|
+
return 'img';
|
|
26236
|
+
}
|
|
26237
|
+
default:
|
|
26238
|
+
return undefined;
|
|
26239
|
+
}
|
|
26240
|
+
}
|
|
26241
|
+
// ---------------------------------------------------------------------------
|
|
26242
|
+
// ARIA labels
|
|
26243
|
+
// ---------------------------------------------------------------------------
|
|
26244
|
+
/**
|
|
26245
|
+
* Generates a human-readable ARIA label for an element.
|
|
26246
|
+
*
|
|
26247
|
+
* Priority:
|
|
26248
|
+
* 1. Image altText (for image/picture elements)
|
|
26249
|
+
* 2. Text content (for text-bearing elements)
|
|
26250
|
+
* 3. Chart title (from chartData)
|
|
26251
|
+
* 4. Element type fallback label
|
|
26252
|
+
*
|
|
26253
|
+
* @param element - The element to generate a label for.
|
|
26254
|
+
* @returns A descriptive string for the `aria-label` attribute.
|
|
26255
|
+
*/
|
|
26256
|
+
function getAriaLabel(element) {
|
|
26257
|
+
// Image alt text
|
|
26258
|
+
if ((element.type === 'image' || element.type === 'picture') &&
|
|
26259
|
+
'altText' in element &&
|
|
26260
|
+
typeof element.altText === 'string' &&
|
|
26261
|
+
element.altText.trim()) {
|
|
26262
|
+
return element.altText.trim();
|
|
26263
|
+
}
|
|
26264
|
+
// Text content
|
|
26265
|
+
if (hasTextProperties(element) && element.text) {
|
|
26266
|
+
const text = element.text.trim();
|
|
26267
|
+
if (text) {
|
|
26268
|
+
// Truncate long text for the label
|
|
26269
|
+
return text.length > 120 ? `${text.slice(0, 117)}...` : text;
|
|
26270
|
+
}
|
|
26271
|
+
}
|
|
26272
|
+
// Chart title
|
|
26273
|
+
if (element.type === 'chart' && 'chartData' in element && element.chartData) {
|
|
26274
|
+
const cd = element.chartData;
|
|
26275
|
+
if (cd.title) {
|
|
26276
|
+
return `Chart: ${cd.title}`;
|
|
26277
|
+
}
|
|
26278
|
+
return 'Chart';
|
|
26279
|
+
}
|
|
26280
|
+
// SmartArt
|
|
26281
|
+
if (element.type === 'smartArt') {
|
|
26282
|
+
return 'SmartArt diagram';
|
|
26283
|
+
}
|
|
26284
|
+
// Table
|
|
26285
|
+
if (element.type === 'table') {
|
|
26286
|
+
return 'Table';
|
|
26287
|
+
}
|
|
26288
|
+
// Media
|
|
26289
|
+
if (element.type === 'media') {
|
|
26290
|
+
return 'Media element';
|
|
26291
|
+
}
|
|
26292
|
+
// Group
|
|
26293
|
+
if (element.type === 'group') {
|
|
26294
|
+
return 'Group of elements';
|
|
26295
|
+
}
|
|
26296
|
+
// Connector
|
|
26297
|
+
if (element.type === 'connector') {
|
|
26298
|
+
return 'Connector line';
|
|
26299
|
+
}
|
|
26300
|
+
// Ink
|
|
26301
|
+
if (element.type === 'ink') {
|
|
26302
|
+
return 'Ink drawing';
|
|
26303
|
+
}
|
|
26304
|
+
// 3D Model
|
|
26305
|
+
if (element.type === 'model3d') {
|
|
26306
|
+
return '3D Model';
|
|
26307
|
+
}
|
|
26308
|
+
// Shape fallback
|
|
26309
|
+
if (element.type === 'shape') {
|
|
26310
|
+
if ('shapeType' in element && element.shapeType) {
|
|
26311
|
+
return `Shape: ${element.shapeType}`;
|
|
26312
|
+
}
|
|
26313
|
+
return 'Shape';
|
|
26314
|
+
}
|
|
26315
|
+
// Generic fallback
|
|
26316
|
+
return getTypeFallbackLabel(element.type);
|
|
26317
|
+
}
|
|
26318
|
+
/**
|
|
26319
|
+
* Returns a user-friendly fallback label based on element type.
|
|
26320
|
+
*/
|
|
26321
|
+
function getTypeFallbackLabel(type) {
|
|
26322
|
+
switch (type) {
|
|
26323
|
+
case 'text':
|
|
26324
|
+
return 'Text box';
|
|
26325
|
+
case 'shape':
|
|
26326
|
+
return 'Shape';
|
|
26327
|
+
case 'image':
|
|
26328
|
+
case 'picture':
|
|
26329
|
+
return 'Image';
|
|
26330
|
+
case 'table':
|
|
26331
|
+
return 'Table';
|
|
26332
|
+
case 'chart':
|
|
26333
|
+
return 'Chart';
|
|
26334
|
+
case 'connector':
|
|
26335
|
+
return 'Connector';
|
|
26336
|
+
case 'group':
|
|
26337
|
+
return 'Group';
|
|
26338
|
+
case 'smartArt':
|
|
26339
|
+
return 'SmartArt';
|
|
26340
|
+
case 'media':
|
|
26341
|
+
return 'Media';
|
|
26342
|
+
case 'ink':
|
|
26343
|
+
return 'Drawing';
|
|
26344
|
+
case 'ole':
|
|
26345
|
+
return 'Embedded object';
|
|
26346
|
+
case 'contentPart':
|
|
26347
|
+
return 'Content part';
|
|
26348
|
+
case 'model3d':
|
|
26349
|
+
return '3D Model';
|
|
26350
|
+
default:
|
|
26351
|
+
return 'Element';
|
|
26352
|
+
}
|
|
26353
|
+
}
|
|
26354
|
+
// ---------------------------------------------------------------------------
|
|
26355
|
+
// ARIA role descriptions
|
|
26356
|
+
// ---------------------------------------------------------------------------
|
|
26357
|
+
/**
|
|
26358
|
+
* Returns an `aria-roledescription` string for an element, providing
|
|
26359
|
+
* assistive-technology users with a more specific description of the
|
|
26360
|
+
* element's purpose than the generic ARIA role alone.
|
|
26361
|
+
*
|
|
26362
|
+
* @param element - The element to describe.
|
|
26363
|
+
* @returns A human-readable role description, or undefined when none is needed.
|
|
26364
|
+
*/
|
|
26365
|
+
function getAriaRoleDescription(element) {
|
|
26366
|
+
switch (element.type) {
|
|
26367
|
+
case 'shape': {
|
|
26368
|
+
const shapeType = 'shapeType' in element && typeof element.shapeType === 'string'
|
|
26369
|
+
? element.shapeType
|
|
26370
|
+
: undefined;
|
|
26371
|
+
if (shapeType) {
|
|
26372
|
+
return `shape: ${shapeType}`;
|
|
26373
|
+
}
|
|
26374
|
+
return 'shape';
|
|
26375
|
+
}
|
|
26376
|
+
case 'chart':
|
|
26377
|
+
return 'chart';
|
|
26378
|
+
case 'connector':
|
|
26379
|
+
return 'connector line';
|
|
26380
|
+
case 'smartArt':
|
|
26381
|
+
return 'diagram';
|
|
26382
|
+
case 'image':
|
|
26383
|
+
case 'picture':
|
|
26384
|
+
return 'image';
|
|
26385
|
+
case 'table':
|
|
26386
|
+
return 'data table';
|
|
26387
|
+
case 'group':
|
|
26388
|
+
return 'grouped elements';
|
|
26389
|
+
case 'media':
|
|
26390
|
+
return 'media player';
|
|
26391
|
+
case 'ink':
|
|
26392
|
+
return 'ink drawing';
|
|
26393
|
+
case 'model3d':
|
|
26394
|
+
return '3D model';
|
|
26395
|
+
default:
|
|
26396
|
+
return undefined;
|
|
26397
|
+
}
|
|
26398
|
+
}
|
|
26399
|
+
// ---------------------------------------------------------------------------
|
|
26400
|
+
// Reduced motion detection
|
|
26401
|
+
// ---------------------------------------------------------------------------
|
|
26402
|
+
/**
|
|
26403
|
+
* Queries the `prefers-reduced-motion: reduce` media query.
|
|
26404
|
+
*
|
|
26405
|
+
* @returns `true` if the user prefers reduced motion, `false` otherwise.
|
|
26406
|
+
*/
|
|
26407
|
+
function prefersReducedMotion() {
|
|
26408
|
+
if (typeof window === 'undefined') {
|
|
26409
|
+
return false;
|
|
26410
|
+
}
|
|
26411
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
26412
|
+
}
|
|
26413
|
+
/**
|
|
26414
|
+
* Returns a CSS-property object (framework-agnostic; spreadable into any
|
|
26415
|
+
* binding's style prop) that disables or tones
|
|
26416
|
+
* down animations and transitions when the user's OS-level
|
|
26417
|
+
* `prefers-reduced-motion: reduce` setting is active.
|
|
26418
|
+
*
|
|
26419
|
+
* When reduced motion is **not** preferred the function returns an empty
|
|
26420
|
+
* object so it can always be spread into a style prop safely.
|
|
26421
|
+
*
|
|
26422
|
+
* @returns CSS properties that suppress animations when appropriate.
|
|
26423
|
+
*/
|
|
26424
|
+
function getReducedMotionStyles() {
|
|
26425
|
+
if (!prefersReducedMotion()) {
|
|
26426
|
+
return {};
|
|
26427
|
+
}
|
|
26428
|
+
return {
|
|
26429
|
+
animationDuration: '0.001ms',
|
|
26430
|
+
animationIterationCount: 1,
|
|
26431
|
+
transitionDuration: '0.001ms',
|
|
26432
|
+
animationDelay: '0ms',
|
|
26433
|
+
transitionDelay: '0ms',
|
|
26434
|
+
};
|
|
26435
|
+
}
|
|
26436
|
+
|
|
26145
26437
|
/**
|
|
26146
26438
|
* Convert an array of points into an SVG path `d` attribute string.
|
|
26147
26439
|
* - 0 points -> `''`
|
|
@@ -29380,6 +29672,229 @@ function canUseClipboard(nav) {
|
|
|
29380
29672
|
typeof nav.clipboard.writeText === 'function');
|
|
29381
29673
|
}
|
|
29382
29674
|
|
|
29675
|
+
/**
|
|
29676
|
+
* is-mobile.ts: Injectable signal-based mobile-detection service plus a
|
|
29677
|
+
* pure `computeIsMobile` helper for unit-testing.
|
|
29678
|
+
*
|
|
29679
|
+
* Ported from: packages/react/src/viewer/hooks/useIsMobile.ts
|
|
29680
|
+
*
|
|
29681
|
+
* The service tracks two independent media conditions:
|
|
29682
|
+
* - `(pointer: coarse)`: primary pointer is a touch screen / stylus
|
|
29683
|
+
* - viewport width below {@link MOBILE_BREAKPOINT}
|
|
29684
|
+
*
|
|
29685
|
+
* Both conditions are evaluated once at construction time and then kept live
|
|
29686
|
+
* via `MediaQueryList.addEventListener`. The service is SSR-safe: when
|
|
29687
|
+
* `matchMedia` is not available (server / test environment without a DOM) all
|
|
29688
|
+
* signals default to `false` and no listeners are registered.
|
|
29689
|
+
*/
|
|
29690
|
+
// ---------------------------------------------------------------------------
|
|
29691
|
+
// Constants
|
|
29692
|
+
// ---------------------------------------------------------------------------
|
|
29693
|
+
/** Viewport width (px) below which the UI switches to mobile layout. */
|
|
29694
|
+
const MOBILE_BREAKPOINT = 768;
|
|
29695
|
+
/** Tablet breakpoint: below this width (but >= MOBILE) is tablet. */
|
|
29696
|
+
const TABLET_BREAKPOINT = 1024;
|
|
29697
|
+
/**
|
|
29698
|
+
* Max viewport height (px) at which a *touch* device is treated as mobile
|
|
29699
|
+
* regardless of width. Catches landscape phones (e.g. 915×412), which are wide
|
|
29700
|
+
* enough to fall in the "tablet" width band but far too short for the desktop
|
|
29701
|
+
* ribbon + side panels, so they need the mobile chrome. Tablets in landscape are
|
|
29702
|
+
* taller (~760px+) so they stay on the desktop layout. Mirrors React's
|
|
29703
|
+
* `MOBILE_LANDSCAPE_MAX_HEIGHT` in useIsMobile.ts.
|
|
29704
|
+
*/
|
|
29705
|
+
const MOBILE_LANDSCAPE_MAX_HEIGHT = 500;
|
|
29706
|
+
// ---------------------------------------------------------------------------
|
|
29707
|
+
// Pure helpers (no Angular deps, safe in vitest without a DOM)
|
|
29708
|
+
// ---------------------------------------------------------------------------
|
|
29709
|
+
/**
|
|
29710
|
+
* Decide whether the current environment should use the mobile layout:
|
|
29711
|
+
* - a narrow viewport (`width < MOBILE_BREAKPOINT`), OR
|
|
29712
|
+
* - a short *touch* viewport below the tablet width: a landscape phone, which
|
|
29713
|
+
* is wide enough to look like a tablet but far too short for the desktop
|
|
29714
|
+
* ribbon + side panels.
|
|
29715
|
+
*
|
|
29716
|
+
* This mirrors React's `isMobileViewport(width, height, isTouch)` so the three
|
|
29717
|
+
* frameworks switch chrome at the same breakpoints (and the shared mobile e2e
|
|
29718
|
+
* specs pass identically). A tall touch tablet (e.g. 820×1180) is NOT mobile.
|
|
29719
|
+
*
|
|
29720
|
+
* @pure: no side effects, fully testable without a DOM.
|
|
29721
|
+
*/
|
|
29722
|
+
function computeIsMobile(width, height, isTouch) {
|
|
29723
|
+
if (width < MOBILE_BREAKPOINT) {
|
|
29724
|
+
return true;
|
|
29725
|
+
}
|
|
29726
|
+
return isTouch && height > 0 && height < MOBILE_LANDSCAPE_MAX_HEIGHT && width < TABLET_BREAKPOINT;
|
|
29727
|
+
}
|
|
29728
|
+
/**
|
|
29729
|
+
* Decide whether the current environment is "tablet" (desktop chrome, but in
|
|
29730
|
+
* the 768–1023px width band). A short landscape-phone touch viewport is mobile,
|
|
29731
|
+
* not tablet (handled by {@link computeIsMobile}).
|
|
29732
|
+
*
|
|
29733
|
+
* @pure
|
|
29734
|
+
*/
|
|
29735
|
+
function computeIsTablet(width, height, isTouch) {
|
|
29736
|
+
if (computeIsMobile(width, height, isTouch)) {
|
|
29737
|
+
return false;
|
|
29738
|
+
}
|
|
29739
|
+
return width >= MOBILE_BREAKPOINT && width < TABLET_BREAKPOINT;
|
|
29740
|
+
}
|
|
29741
|
+
// ---------------------------------------------------------------------------
|
|
29742
|
+
// IsMobileService
|
|
29743
|
+
// ---------------------------------------------------------------------------
|
|
29744
|
+
/**
|
|
29745
|
+
* `IsMobileService`: provides reactive signals for the current viewport /
|
|
29746
|
+
* pointer kind so components can switch between mobile and desktop chrome
|
|
29747
|
+
* without subscribing to resize events themselves.
|
|
29748
|
+
*
|
|
29749
|
+
* Inject at the component level (or provide at root); the service cleans up
|
|
29750
|
+
* its `MediaQueryList` listeners automatically via `DestroyRef`.
|
|
29751
|
+
*
|
|
29752
|
+
* ```ts
|
|
29753
|
+
* providers: [IsMobileService]
|
|
29754
|
+
* // or globally:
|
|
29755
|
+
* // provideIsMobile() (see factory below)
|
|
29756
|
+
* ```
|
|
29757
|
+
*
|
|
29758
|
+
* ```ts
|
|
29759
|
+
* protected readonly mobile = inject(IsMobileService);
|
|
29760
|
+
* // in template: @if (mobile.isMobile()) { … }
|
|
29761
|
+
* ```
|
|
29762
|
+
*/
|
|
29763
|
+
class IsMobileService {
|
|
29764
|
+
/** True when the primary pointer is coarse (touch / stylus). */
|
|
29765
|
+
isCoarsePointer = signal(false, /* @ts-ignore */
|
|
29766
|
+
...(ngDevMode ? [{ debugName: "isCoarsePointer" }] : /* istanbul ignore next */ []));
|
|
29767
|
+
/** True when the viewport width is below {@link MOBILE_BREAKPOINT}. */
|
|
29768
|
+
isNarrowViewport = signal(false, /* @ts-ignore */
|
|
29769
|
+
...(ngDevMode ? [{ debugName: "isNarrowViewport" }] : /* istanbul ignore next */ []));
|
|
29770
|
+
/**
|
|
29771
|
+
* True when either the pointer is coarse OR the viewport is narrow.
|
|
29772
|
+
* Use this as the single gate for showing mobile chrome.
|
|
29773
|
+
*/
|
|
29774
|
+
isMobile = signal(false, /* @ts-ignore */
|
|
29775
|
+
...(ngDevMode ? [{ debugName: "isMobile" }] : /* istanbul ignore next */ []));
|
|
29776
|
+
/** True when the viewport is in the tablet range (desktop pointer only). */
|
|
29777
|
+
isTablet = signal(false, /* @ts-ignore */
|
|
29778
|
+
...(ngDevMode ? [{ debugName: "isTablet" }] : /* istanbul ignore next */ []));
|
|
29779
|
+
/**
|
|
29780
|
+
* CSS pixels the on-screen keyboard currently covers at the bottom of the
|
|
29781
|
+
* layout viewport (0 when no keyboard is open). The orchestrator lifts the
|
|
29782
|
+
* fixed mobile bottom bar by this amount so it stays above the keyboard.
|
|
29783
|
+
*/
|
|
29784
|
+
keyboardInset = signal(0, /* @ts-ignore */
|
|
29785
|
+
...(ngDevMode ? [{ debugName: "keyboardInset" }] : /* istanbul ignore next */ []));
|
|
29786
|
+
/** True while {@link keyboardInset} is large enough to count as "open". */
|
|
29787
|
+
isKeyboardOpen = signal(false, /* @ts-ignore */
|
|
29788
|
+
...(ngDevMode ? [{ debugName: "isKeyboardOpen" }] : /* istanbul ignore next */ []));
|
|
29789
|
+
constructor() {
|
|
29790
|
+
const destroyRef = inject(DestroyRef);
|
|
29791
|
+
// Guard: matchMedia / window may not be available in SSR / test envs.
|
|
29792
|
+
if (typeof matchMedia !== 'function' || typeof window === 'undefined') {
|
|
29793
|
+
return;
|
|
29794
|
+
}
|
|
29795
|
+
this._wireKeyboardInset(destroyRef);
|
|
29796
|
+
// ── Coarse-pointer media query (drives the touch flag) ───────────────────
|
|
29797
|
+
const coarseMql = matchMedia('(pointer: coarse)');
|
|
29798
|
+
this.isCoarsePointer.set(coarseMql.matches);
|
|
29799
|
+
const onCoarseChange = (evt) => {
|
|
29800
|
+
this.isCoarsePointer.set(evt.matches);
|
|
29801
|
+
this._recompute();
|
|
29802
|
+
};
|
|
29803
|
+
coarseMql.addEventListener('change', onCoarseChange);
|
|
29804
|
+
// ── Viewport size tracking ───────────────────────────────────────────────
|
|
29805
|
+
// Width-only media queries cannot express the "short landscape phone"
|
|
29806
|
+
// rule (which depends on height + touch), so track the live viewport size
|
|
29807
|
+
// on resize and recompute the derived flags from width/height/touch.
|
|
29808
|
+
const onResize = () => this._recompute();
|
|
29809
|
+
window.addEventListener('resize', onResize);
|
|
29810
|
+
if (typeof screen !== 'undefined' && screen.orientation) {
|
|
29811
|
+
screen.orientation.addEventListener('change', onResize);
|
|
29812
|
+
}
|
|
29813
|
+
this._recompute();
|
|
29814
|
+
// ── Cleanup on destroy ───────────────────────────────────────────────────
|
|
29815
|
+
destroyRef.onDestroy(() => {
|
|
29816
|
+
coarseMql.removeEventListener('change', onCoarseChange);
|
|
29817
|
+
window.removeEventListener('resize', onResize);
|
|
29818
|
+
if (typeof screen !== 'undefined' && screen.orientation) {
|
|
29819
|
+
screen.orientation.removeEventListener('change', onResize);
|
|
29820
|
+
}
|
|
29821
|
+
});
|
|
29822
|
+
}
|
|
29823
|
+
/** Recompute all derived flags from the live viewport size + pointer kind. */
|
|
29824
|
+
_recompute() {
|
|
29825
|
+
const width = window.innerWidth;
|
|
29826
|
+
const height = window.innerHeight;
|
|
29827
|
+
const touch = this.isCoarsePointer();
|
|
29828
|
+
this.isNarrowViewport.set(width < MOBILE_BREAKPOINT);
|
|
29829
|
+
this.isMobile.set(computeIsMobile(width, height, touch));
|
|
29830
|
+
this.isTablet.set(computeIsTablet(width, height, touch));
|
|
29831
|
+
}
|
|
29832
|
+
/**
|
|
29833
|
+
* Track the on-screen-keyboard inset via the `VisualViewport` API and keep the
|
|
29834
|
+
* focused editable visible: when the keyboard shrinks the visual viewport,
|
|
29835
|
+
* update {@link keyboardInset} / {@link isKeyboardOpen} and scroll the active
|
|
29836
|
+
* input / textarea / contenteditable into the area above the keyboard. No-op
|
|
29837
|
+
* when `visualViewport` is unavailable (desktop / SSR), so desktop is unchanged.
|
|
29838
|
+
*/
|
|
29839
|
+
_wireKeyboardInset(destroyRef) {
|
|
29840
|
+
const vv = window.visualViewport;
|
|
29841
|
+
if (!vv) {
|
|
29842
|
+
return;
|
|
29843
|
+
}
|
|
29844
|
+
const update = () => {
|
|
29845
|
+
const metrics = readViewportMetrics(window);
|
|
29846
|
+
const inset = metrics ? computeKeyboardInset(metrics) : 0;
|
|
29847
|
+
this.keyboardInset.set(inset);
|
|
29848
|
+
this.isKeyboardOpen.set(isKeyboardOpen(inset));
|
|
29849
|
+
if (inset > 0) {
|
|
29850
|
+
window.requestAnimationFrame(() => this._scrollFocusedIntoView(inset));
|
|
29851
|
+
}
|
|
29852
|
+
};
|
|
29853
|
+
const onFocusIn = () => {
|
|
29854
|
+
window.requestAnimationFrame(() => {
|
|
29855
|
+
const metrics = readViewportMetrics(window);
|
|
29856
|
+
const inset = metrics ? computeKeyboardInset(metrics) : 0;
|
|
29857
|
+
if (inset > 0) {
|
|
29858
|
+
this._scrollFocusedIntoView(inset);
|
|
29859
|
+
}
|
|
29860
|
+
});
|
|
29861
|
+
};
|
|
29862
|
+
update();
|
|
29863
|
+
vv.addEventListener('resize', update);
|
|
29864
|
+
vv.addEventListener('scroll', update);
|
|
29865
|
+
document.addEventListener('focusin', onFocusIn);
|
|
29866
|
+
destroyRef.onDestroy(() => {
|
|
29867
|
+
vv.removeEventListener('resize', update);
|
|
29868
|
+
vv.removeEventListener('scroll', update);
|
|
29869
|
+
document.removeEventListener('focusin', onFocusIn);
|
|
29870
|
+
});
|
|
29871
|
+
}
|
|
29872
|
+
/** Scroll the focused editable into the area above the keyboard, if needed. */
|
|
29873
|
+
_scrollFocusedIntoView(keyboardInset) {
|
|
29874
|
+
if (keyboardInset <= 0 || typeof document === 'undefined') {
|
|
29875
|
+
return;
|
|
29876
|
+
}
|
|
29877
|
+
const active = document.activeElement;
|
|
29878
|
+
if (!(active instanceof HTMLElement)) {
|
|
29879
|
+
return;
|
|
29880
|
+
}
|
|
29881
|
+
const tag = active.tagName;
|
|
29882
|
+
if (tag !== 'INPUT' && tag !== 'TEXTAREA' && !active.isContentEditable) {
|
|
29883
|
+
return;
|
|
29884
|
+
}
|
|
29885
|
+
const rect = active.getBoundingClientRect();
|
|
29886
|
+
const delta = computeScrollDelta({ top: rect.top, bottom: rect.bottom }, window.innerHeight, keyboardInset);
|
|
29887
|
+
if (delta !== 0) {
|
|
29888
|
+
window.scrollBy({ top: delta, behavior: 'smooth' });
|
|
29889
|
+
}
|
|
29890
|
+
}
|
|
29891
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: IsMobileService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
29892
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: IsMobileService });
|
|
29893
|
+
}
|
|
29894
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: IsMobileService, decorators: [{
|
|
29895
|
+
type: Injectable
|
|
29896
|
+
}], ctorParameters: () => [] });
|
|
29897
|
+
|
|
29383
29898
|
/**
|
|
29384
29899
|
* modal-dialog.component.ts: Reusable, accessible modal dialog shell.
|
|
29385
29900
|
*
|
|
@@ -29406,6 +29921,16 @@ function canUseClipboard(nav) {
|
|
|
29406
29921
|
* </pptx-modal-dialog>
|
|
29407
29922
|
* ```
|
|
29408
29923
|
*/
|
|
29924
|
+
/**
|
|
29925
|
+
* Map the mobile flag to the modal-panel class list. Pure (no Angular / DOM) so
|
|
29926
|
+
* it can be unit-tested without TestBed: on mobile the panel gets the
|
|
29927
|
+
* `is-mobile` modifier that turns it into a bottom sheet (matching the CSS media
|
|
29928
|
+
* query, but driven by the {@link IsMobileService} 768px breakpoint so the three
|
|
29929
|
+
* frameworks switch chrome at the same width).
|
|
29930
|
+
*/
|
|
29931
|
+
function modalPanelClass(isMobile) {
|
|
29932
|
+
return isMobile ? 'pptx-ng-modal-panel is-mobile' : 'pptx-ng-modal-panel';
|
|
29933
|
+
}
|
|
29409
29934
|
class ModalDialogComponent {
|
|
29410
29935
|
/** Whether the dialog is visible. */
|
|
29411
29936
|
open = input(false, /* @ts-ignore */
|
|
@@ -29415,6 +29940,14 @@ class ModalDialogComponent {
|
|
|
29415
29940
|
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
29416
29941
|
/** Fired on backdrop click, the `×` button, and `Escape`. */
|
|
29417
29942
|
close = output();
|
|
29943
|
+
/** Reactive viewport / pointer flags (drives the bottom-sheet layout). */
|
|
29944
|
+
mobile = inject(IsMobileService);
|
|
29945
|
+
/**
|
|
29946
|
+
* Panel class list: gains the `is-mobile` bottom-sheet modifier under the
|
|
29947
|
+
* mobile breakpoint. See {@link modalPanelClass}.
|
|
29948
|
+
*/
|
|
29949
|
+
panelClass = computed(() => modalPanelClass(this.mobile.isMobile()), /* @ts-ignore */
|
|
29950
|
+
...(ngDevMode ? [{ debugName: "panelClass" }] : /* istanbul ignore next */ []));
|
|
29418
29951
|
/** Close on `Escape`, regardless of where focus currently sits. */
|
|
29419
29952
|
onDocumentKeydown(event) {
|
|
29420
29953
|
if (!this.open()) {
|
|
@@ -29477,11 +30010,15 @@ class ModalDialogComponent {
|
|
|
29477
30010
|
this.dragY.set(0);
|
|
29478
30011
|
}
|
|
29479
30012
|
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: `
|
|
30013
|
+
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
30014
|
@if (open()) {
|
|
29482
|
-
<div
|
|
30015
|
+
<div
|
|
30016
|
+
class="pptx-ng-modal-backdrop"
|
|
30017
|
+
[class.is-mobile]="mobile.isMobile()"
|
|
30018
|
+
(click)="onBackdropClick()"
|
|
30019
|
+
>
|
|
29483
30020
|
<div
|
|
29484
|
-
class="
|
|
30021
|
+
[class]="panelClass()"
|
|
29485
30022
|
role="dialog"
|
|
29486
30023
|
aria-modal="true"
|
|
29487
30024
|
[attr.aria-label]="title() || null"
|
|
@@ -29521,15 +30058,19 @@ class ModalDialogComponent {
|
|
|
29521
30058
|
</div>
|
|
29522
30059
|
</div>
|
|
29523
30060
|
}
|
|
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:
|
|
30061
|
+
`, 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
30062
|
}
|
|
29526
30063
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ModalDialogComponent, decorators: [{
|
|
29527
30064
|
type: Component,
|
|
29528
|
-
args: [{ selector: 'pptx-modal-dialog', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
30065
|
+
args: [{ selector: 'pptx-modal-dialog', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, providers: [IsMobileService], template: `
|
|
29529
30066
|
@if (open()) {
|
|
29530
|
-
<div
|
|
30067
|
+
<div
|
|
30068
|
+
class="pptx-ng-modal-backdrop"
|
|
30069
|
+
[class.is-mobile]="mobile.isMobile()"
|
|
30070
|
+
(click)="onBackdropClick()"
|
|
30071
|
+
>
|
|
29531
30072
|
<div
|
|
29532
|
-
class="
|
|
30073
|
+
[class]="panelClass()"
|
|
29533
30074
|
role="dialog"
|
|
29534
30075
|
aria-modal="true"
|
|
29535
30076
|
[attr.aria-label]="title() || null"
|
|
@@ -29569,7 +30110,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
29569
30110
|
</div>
|
|
29570
30111
|
</div>
|
|
29571
30112
|
}
|
|
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:
|
|
30113
|
+
`, 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
30114
|
}], 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
30115
|
type: HostListener,
|
|
29575
30116
|
args: ['document:keydown', ['$event']]
|
|
@@ -40197,6 +40738,15 @@ class InspectorPanelComponent {
|
|
|
40197
40738
|
slideIndex = input.required(/* @ts-ignore */
|
|
40198
40739
|
...(ngDevMode ? [{ debugName: "slideIndex" }] : /* istanbul ignore next */ []));
|
|
40199
40740
|
editor = inject(EditorStateService);
|
|
40741
|
+
/** Reactive viewport / pointer flags (drives the bottom-sheet layout). */
|
|
40742
|
+
mobile = inject(IsMobileService);
|
|
40743
|
+
/**
|
|
40744
|
+
* Root class list: gains the `is-mobile` modifier under the mobile
|
|
40745
|
+
* breakpoint so the panel becomes a full-width, touch-sized bottom sheet.
|
|
40746
|
+
* See {@link inspectorRootClass}.
|
|
40747
|
+
*/
|
|
40748
|
+
inspectorClass = computed(() => inspectorRootClass(this.mobile.isMobile()), /* @ts-ignore */
|
|
40749
|
+
...(ngDevMode ? [{ debugName: "inspectorClass" }] : /* istanbul ignore next */ []));
|
|
40200
40750
|
/** Alias so the template can call el() without conflicting with Angular internals. */
|
|
40201
40751
|
el = computed(() => this.element(), /* @ts-ignore */
|
|
40202
40752
|
...(ngDevMode ? [{ debugName: "el" }] : /* istanbul ignore next */ []));
|
|
@@ -40372,7 +40922,7 @@ class InspectorPanelComponent {
|
|
|
40372
40922
|
this.editor.updateElement(this.slideIndex(), cur.id, textStylePatch(cur, { underline: !this.currentUnderline() }));
|
|
40373
40923
|
}
|
|
40374
40924
|
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: `
|
|
40925
|
+
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
40926
|
<!--
|
|
40377
40927
|
NOTE (mobile-safe inputs): every numeric / colour input is keyed on the
|
|
40378
40928
|
selected element's id via @if blocks. Angular destroys and recreates the
|
|
@@ -40382,7 +40932,7 @@ class InspectorPanelComponent {
|
|
|
40382
40932
|
mid-edit: the caret stays put and the on-screen keyboard does not dismiss.
|
|
40383
40933
|
All commits happen on (change) (blur), reading event.target.value.
|
|
40384
40934
|
-->
|
|
40385
|
-
<aside class="
|
|
40935
|
+
<aside [class]="inspectorClass()" aria-label="Element properties">
|
|
40386
40936
|
<!-- ── Transform: Position & Size ─────────────────────────────────── -->
|
|
40387
40937
|
<section class="pptx-ng-inspector__section">
|
|
40388
40938
|
<h3 class="pptx-ng-inspector__heading">Transform</h3>
|
|
@@ -40665,7 +41215,7 @@ class InspectorPanelComponent {
|
|
|
40665
41215
|
</div>
|
|
40666
41216
|
</section>
|
|
40667
41217
|
</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:
|
|
41218
|
+
`, 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
41219
|
}
|
|
40670
41220
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: InspectorPanelComponent, decorators: [{
|
|
40671
41221
|
type: Component,
|
|
@@ -40677,7 +41227,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
40677
41227
|
ChartDataEditorComponent,
|
|
40678
41228
|
SmartArtPropertiesComponent,
|
|
40679
41229
|
AnimationAuthorPanelComponent,
|
|
40680
|
-
], template: `
|
|
41230
|
+
], providers: [IsMobileService], template: `
|
|
40681
41231
|
<!--
|
|
40682
41232
|
NOTE (mobile-safe inputs): every numeric / colour input is keyed on the
|
|
40683
41233
|
selected element's id via @if blocks. Angular destroys and recreates the
|
|
@@ -40687,7 +41237,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
40687
41237
|
mid-edit: the caret stays put and the on-screen keyboard does not dismiss.
|
|
40688
41238
|
All commits happen on (change) (blur), reading event.target.value.
|
|
40689
41239
|
-->
|
|
40690
|
-
<aside class="
|
|
41240
|
+
<aside [class]="inspectorClass()" aria-label="Element properties">
|
|
40691
41241
|
<!-- ── Transform: Position & Size ─────────────────────────────────── -->
|
|
40692
41242
|
<section class="pptx-ng-inspector__section">
|
|
40693
41243
|
<h3 class="pptx-ng-inspector__heading">Transform</h3>
|
|
@@ -40970,8 +41520,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
40970
41520
|
</div>
|
|
40971
41521
|
</section>
|
|
40972
41522
|
</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:
|
|
41523
|
+
`, 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
41524
|
}], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], slideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideIndex", required: true }] }] } });
|
|
41525
|
+
// ── Pure helpers (no Angular / DOM; unit-testable without TestBed) ────────────
|
|
41526
|
+
/**
|
|
41527
|
+
* Map the mobile flag to the inspector root class list. On mobile the panel
|
|
41528
|
+
* gains the `is-mobile` modifier that makes it a full-width, touch-sized
|
|
41529
|
+
* bottom-sheet body (the orchestrator host wraps it with the drawer chrome).
|
|
41530
|
+
*/
|
|
41531
|
+
function inspectorRootClass(isMobile) {
|
|
41532
|
+
return isMobile ? 'pptx-ng-inspector is-mobile' : 'pptx-ng-inspector';
|
|
41533
|
+
}
|
|
40975
41534
|
// ── Module-private helpers ───────────────────────────────────────────────────
|
|
40976
41535
|
/** Extract a finite number from an input change event, or null if invalid. */
|
|
40977
41536
|
function numberFromEvent(event) {
|
|
@@ -40992,229 +41551,6 @@ function stringFromEvent(event) {
|
|
|
40992
41551
|
return val.length > 0 ? val : null;
|
|
40993
41552
|
}
|
|
40994
41553
|
|
|
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
41554
|
/**
|
|
41219
41555
|
* `LoadContentService`: Angular port of the React `useLoadContent` hook and
|
|
41220
41556
|
* the Vue `useLoadContent` composable.
|
|
@@ -53172,6 +53508,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
53172
53508
|
* }
|
|
53173
53509
|
* ```
|
|
53174
53510
|
*/
|
|
53511
|
+
/**
|
|
53512
|
+
* Map the mobile flag to the print-dialog panel class list. Pure (no Angular /
|
|
53513
|
+
* DOM) so it can be unit-tested without TestBed. On mobile the dialog gains the
|
|
53514
|
+
* `is-mobile` modifier that turns it into a full-width bottom sheet.
|
|
53515
|
+
*/
|
|
53516
|
+
function printDialogClass(isMobile) {
|
|
53517
|
+
return isMobile ? 'pptx-ng-print-dialog is-mobile' : 'pptx-ng-print-dialog';
|
|
53518
|
+
}
|
|
53175
53519
|
class PrintDialogComponent {
|
|
53176
53520
|
// -------------------------------------------------------------------------
|
|
53177
53521
|
// Inputs / outputs
|
|
@@ -53192,6 +53536,14 @@ class PrintDialogComponent {
|
|
|
53192
53536
|
print = output();
|
|
53193
53537
|
/** Emits when the dialog is dismissed (Cancel / Escape / backdrop). */
|
|
53194
53538
|
cancel = output();
|
|
53539
|
+
/** Reactive viewport / pointer flags (drives the bottom-sheet layout). */
|
|
53540
|
+
mobile = inject(IsMobileService);
|
|
53541
|
+
/**
|
|
53542
|
+
* Dialog class list: gains the `is-mobile` bottom-sheet modifier under the
|
|
53543
|
+
* mobile breakpoint. See {@link printDialogClass}.
|
|
53544
|
+
*/
|
|
53545
|
+
dialogClass = computed(() => printDialogClass(this.mobile.isMobile()), /* @ts-ignore */
|
|
53546
|
+
...(ngDevMode ? [{ debugName: "dialogClass" }] : /* istanbul ignore next */ []));
|
|
53195
53547
|
// -------------------------------------------------------------------------
|
|
53196
53548
|
// State
|
|
53197
53549
|
// -------------------------------------------------------------------------
|
|
@@ -53259,15 +53611,16 @@ class PrintDialogComponent {
|
|
|
53259
53611
|
}
|
|
53260
53612
|
}
|
|
53261
53613
|
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: `
|
|
53614
|
+
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
53615
|
<div
|
|
53264
53616
|
class="pptx-ng-print-dialog__backdrop"
|
|
53617
|
+
[class.is-mobile]="mobile.isMobile()"
|
|
53265
53618
|
role="dialog"
|
|
53266
53619
|
aria-modal="true"
|
|
53267
53620
|
aria-label="Print"
|
|
53268
53621
|
(click)="onBackdropClick($event)"
|
|
53269
53622
|
>
|
|
53270
|
-
<div class="
|
|
53623
|
+
<div [class]="dialogClass()">
|
|
53271
53624
|
<!-- Header -->
|
|
53272
53625
|
<div class="pptx-ng-print-dialog__header">
|
|
53273
53626
|
<h2 class="pptx-ng-print-dialog__title">Print</h2>
|
|
@@ -53313,19 +53666,20 @@ class PrintDialogComponent {
|
|
|
53313
53666
|
</div>
|
|
53314
53667
|
</div>
|
|
53315
53668
|
</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 });
|
|
53669
|
+
`, 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
53670
|
}
|
|
53318
53671
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: PrintDialogComponent, decorators: [{
|
|
53319
53672
|
type: Component,
|
|
53320
|
-
args: [{ selector: 'pptx-print-dialog', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [PrintSettingsPanelComponent], template: `
|
|
53673
|
+
args: [{ selector: 'pptx-print-dialog', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [PrintSettingsPanelComponent], providers: [IsMobileService], template: `
|
|
53321
53674
|
<div
|
|
53322
53675
|
class="pptx-ng-print-dialog__backdrop"
|
|
53676
|
+
[class.is-mobile]="mobile.isMobile()"
|
|
53323
53677
|
role="dialog"
|
|
53324
53678
|
aria-modal="true"
|
|
53325
53679
|
aria-label="Print"
|
|
53326
53680
|
(click)="onBackdropClick($event)"
|
|
53327
53681
|
>
|
|
53328
|
-
<div class="
|
|
53682
|
+
<div [class]="dialogClass()">
|
|
53329
53683
|
<!-- Header -->
|
|
53330
53684
|
<div class="pptx-ng-print-dialog__header">
|
|
53331
53685
|
<h2 class="pptx-ng-print-dialog__title">Print</h2>
|
|
@@ -53371,7 +53725,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
53371
53725
|
</div>
|
|
53372
53726
|
</div>
|
|
53373
53727
|
</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"] }]
|
|
53728
|
+
`, 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
53729
|
}], 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
53730
|
type: HostListener,
|
|
53377
53731
|
args: ['document:keydown', ['$event']]
|