pptx-angular-viewer 3.20.0 → 3.21.0

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.
@@ -20265,13 +20265,13 @@ function buildCartesianViewModel(element, chartData, categoryLabels, kind) {
20265
20265
  // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
20266
20266
  const primaryRange = isStacked
20267
20267
  ? {
20268
- ...stackedRange(chartData, catCount, isPercent, layout.plotHeight),
20268
+ ...stackedRange(chartData, catCount, isPercent, layout.autoPlotHeight),
20269
20269
  ...(primaryAxis?.orientation === 'maxMin' ? { reverseOrder: true } : {}),
20270
20270
  }
20271
- : computeValueRangeForChart(primaryPlotSeries.length > 0 ? primaryPlotSeries : chartData.series, chartData.axes, layout.plotHeight);
20271
+ : computeValueRangeForChart(primaryPlotSeries.length > 0 ? primaryPlotSeries : chartData.series, chartData.axes, layout.autoPlotHeight);
20272
20272
  // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
20273
20273
  const secondaryRange = useSecondary && secondaryPlotSeries.length > 0
20274
- ? computeValueRangeForAxis(secondaryPlotSeries, chartData.axes?.find((axis) => axis.axisType === 'valAx' && axis.axPos === 'r'), layout.plotHeight)
20274
+ ? computeValueRangeForAxis(secondaryPlotSeries, chartData.axes?.find((axis) => axis.axisType === 'valAx' && axis.axPos === 'r'), layout.autoPlotHeight)
20275
20275
  : undefined;
20276
20276
  // eslint-disable-next-line one-var -- pre-existing, unrelated to this change
20277
20277
  const axisRes = buildCartesianAxes(chartData, layout, primaryRange, secondaryRange, catCount);
@@ -20496,9 +20496,9 @@ function buildComboViewModel(element, chartData, categoryLabels) {
20496
20496
  const primaryAxisId = getPrimaryValueAxisId(chartData.axes);
20497
20497
  const primaryAxis = chartData.axes?.find((axis) => axis.axisId === primaryAxisId);
20498
20498
  const secondaryAxisFormatting = getSecondaryValueAxis(chartData.axes);
20499
- const primaryRange = computeValueRangeForAxis(primarySeries, primaryAxis, layout.plotHeight);
20499
+ const primaryRange = computeValueRangeForAxis(primarySeries, primaryAxis, layout.autoPlotHeight);
20500
20500
  const secondaryRange = secondary.length > 0
20501
- ? computeValueRangeForAxis(secondary.map((entry) => entry.series), secondaryAxisFormatting, layout.plotHeight)
20501
+ ? computeValueRangeForAxis(secondary.map((entry) => entry.series), secondaryAxisFormatting, layout.autoPlotHeight)
20502
20502
  : undefined;
20503
20503
  const primaryCategoryAxis = chartData.axes?.find((axis) => (axis.axisType === 'catAx' || axis.axisType === 'dateAx') &&
20504
20504
  axis.axisId === primaryAxis?.crossAxisId);
@@ -21896,7 +21896,7 @@ function buildSurfaceWallPanels(cols, rows, cellSize, zScale, offsetX, offsetY,
21896
21896
  */
21897
21897
  function buildIsometricSurfaceViewModel(element, chartData, categoryLabels) {
21898
21898
  const layout = computePlotLayout(element.width, element.height, chartData, false);
21899
- const range = computeValueRange(chartData.series, layout.plotHeight);
21899
+ const range = computeValueRange(chartData.series, layout.autoPlotHeight);
21900
21900
  const catCount = Math.max(categoryLabels.length, 1);
21901
21901
  const seriesCount = chartData.series.length;
21902
21902
  // Grid cell count (vertices = cells + 1 in each dimension).
@@ -22042,7 +22042,7 @@ function buildIsometricSurfaceViewModel(element, chartData, categoryLabels) {
22042
22042
  */
22043
22043
  function buildFlatSurfaceViewModel(element, chartData, categoryLabels) {
22044
22044
  const layout = computePlotLayout(element.width, element.height, chartData, false);
22045
- const range = computeValueRange(chartData.series, layout.plotHeight);
22045
+ const range = computeValueRange(chartData.series, layout.autoPlotHeight);
22046
22046
  const catCount = Math.max(categoryLabels.length, 1);
22047
22047
  const seriesCount = chartData.series.length;
22048
22048
  const cellW = layout.plotWidth / Math.max(catCount - 1, 1);
@@ -23222,6 +23222,9 @@ function computePlotLayout(elementWidth, elementHeight, chartData, hasAxes, opti
23222
23222
  const rowCount = options.dataTableRowCount ?? 1;
23223
23223
  plotBottom -= 14 + rowCount * 14;
23224
23224
  }
23225
+ // The automatic plot height, before any manual layout override below can
23226
+ // change it: see `PlotLayout.autoPlotHeight`'s doc comment.
23227
+ const autoPlotHeight = Math.max(plotBottom - plotTop, 1);
23225
23228
  // c:plotArea/c:layout/c:manualLayout: the author placed the plot area by
23226
23229
  // hand. Each field the layout omits keeps the automatic value above, which
23227
23230
  // is also what a `factor`-mode offset is relative to. An `outer` target
@@ -23248,6 +23251,7 @@ function computePlotLayout(elementWidth, elementHeight, chartData, hasAxes, opti
23248
23251
  plotBottom: plotTop + plotHeight,
23249
23252
  plotWidth,
23250
23253
  plotHeight,
23254
+ autoPlotHeight,
23251
23255
  };
23252
23256
  }
23253
23257
 
@@ -24307,7 +24311,7 @@ function buildWaterfallViewModel(element, chartData, categoryLabels) {
24307
24311
  const series = chartData.series[0];
24308
24312
  const values = series?.values ?? [];
24309
24313
  const steps = buildWaterfallSteps(values, series?.waterfallOptions);
24310
- const range = computeWaterfallRange(steps, layout.plotHeight);
24314
+ const range = computeWaterfallRange(steps, layout.autoPlotHeight);
24311
24315
  const catCount = Math.max(categoryLabels.length, values.length, 1);
24312
24316
  const barWidth = (layout.plotWidth / catCount) * 0.6;
24313
24317
  const gap = (layout.plotWidth / catCount) * 0.2;
@@ -26399,11 +26403,11 @@ function buildStackedDragGeometry(element, chartData, seriesIndex, pointIndex) {
26399
26403
  return null;
26400
26404
  }
26401
26405
  const percent = grouping === 'percentStacked', catCount = Math.max(chartData.categories.length, ...seriesValues.map((values) => values.length), pointIndex + 1), plots = computeStackedSeriesPlots(seriesValues, catCount, percent), baseValue = plots[seriesIndex]?.base[pointIndex] ?? 0, otherAbsSum = seriesValues.reduce((sum, values, si) => (si === seriesIndex ? sum : sum + Math.abs(values[pointIndex] ?? 0)), 0), layoutOpts = computeLayoutOptions(chartData.axes, chartData.dataTable, chartData.series.length), layout = computePlotLayout(element.width, element.height, chartData, true, layoutOpts),
26402
- // Computed from the SAME `layout.plotHeight` `chart-cartesian.ts`'s
26406
+ // Computed from the SAME `layout.autoPlotHeight` `chart-cartesian.ts`'s
26403
26407
  // `stackedRange` uses, so a drag never disagrees with what is on screen.
26404
26408
  range = percent
26405
26409
  ? { min: 0, max: 100, span: 100 }
26406
- : computeStackedValueRange(chartData.series, catCount, layout.plotHeight);
26410
+ : computeStackedValueRange(chartData.series, catCount, layout.autoPlotHeight);
26407
26411
  return {
26408
26412
  range,
26409
26413
  plotTop: layout.plotTop,
@@ -49178,6 +49182,7 @@ const translationsEn = {
49178
49182
  'pptx.sections.addAfter': 'Add Section After',
49179
49183
  // Notes
49180
49184
  'pptx.notes.title': 'Notes',
49185
+ 'pptx.notes.hasNotes': '(has notes)',
49181
49186
  'pptx.notes.slideN': 'Slide {{n}}',
49182
49187
  'pptx.notes.noSlide': 'No slide selected',
49183
49188
  'pptx.notes.clickToAddNotes': 'Click to add notes',
@@ -49533,6 +49538,8 @@ const translationsEn = {
49533
49538
  'pptx.slideShow.subtitles': 'Subtitles',
49534
49539
  // Record tab (camera overlay placeholders)
49535
49540
  'pptx.record.cameo': 'Cameo',
49541
+ 'pptx.record.manage': 'Manage',
49542
+ 'pptx.record.camera': 'Camera',
49536
49543
  'pptx.record.clear': 'Clear',
49537
49544
  'pptx.record.learnMore': 'Learn More',
49538
49545
  'pptx.record.resetToCameo': 'Reset to Cameo',
@@ -51642,6 +51649,7 @@ const translationsEn = {
51642
51649
  'pptx.textFormatting.paragraphBefore': 'Paragraph Before',
51643
51650
  'pptx.textFormatting.textFlow': 'Text Flow',
51644
51651
  'pptx.textProperties.advancedTextHeading': 'Advanced Text',
51652
+ 'pptx.textProperties.autoFitToShape': 'Auto-fit text to shape',
51645
51653
  'pptx.textProperties.defaultFontOption': 'Default ({{font}})',
51646
51654
  'pptx.textProperties.firstIndent': 'First Indent',
51647
51655
  'pptx.textProperties.leftMargin': 'Left Margin',
@@ -69009,279 +69017,103 @@ function asSelectionIds(value) {
69009
69017
  }
69010
69018
 
69011
69019
  /**
69012
- * Create a throttled presence publisher bound to an awareness instance. The
69013
- * initial presence is announced synchronously so peers learn of us at once.
69020
+ * Subscribe before reading so a sync completed during attachment is not lost.
69021
+ * The initial snapshot is delivered synchronously, then only changed values.
69014
69022
  */
69015
- function createPresencePublisher(awareness, identity) {
69016
- const local = { activeSlideIndex: 0, cursorX: 0, cursorY: 0 };
69017
- // Starts at 0 so the first real update publishes immediately (the initial
69018
- // announce below does not consume the throttle budget).
69019
- let lastBroadcast = 0;
69020
- let pending = null;
69021
- function publish() {
69022
- awareness.setLocalStateField('presence', {
69023
- userName: identity.userName,
69024
- userColor: identity.userColor,
69025
- userAvatar: identity.userAvatar,
69026
- role: identity.role,
69027
- activeSlideIndex: local.activeSlideIndex,
69028
- cursorX: local.cursorX,
69029
- cursorY: local.cursorY,
69030
- selectedElementId: local.selectedElementId,
69031
- lastUpdated: new Date().toISOString(),
69032
- });
69033
- }
69034
- /** Whether `patch` would actually change any field of the local state. */
69035
- function changesLocalState(patch) {
69036
- return Object.keys(patch).some((key) => patch[key] !== local[key]);
69037
- }
69038
- function update(patch) {
69039
- // A patch that says nothing new is dropped before it reaches the wire.
69040
- // Bindings call this from pointer-move and selection handlers that
69041
- // re-fire with identical values (a cursor resting inside one slide pixel,
69042
- // a re-click on the already-selected element). Publishing anyway re-stamps
69043
- // `lastUpdated`, which every peer sees as a presence change and turns into
69044
- // a re-render, so an idle room kept re-rendering all five bindings.
69045
- // `flush` is exempt: the heartbeat MUST keep publishing or peers time us out.
69046
- if (!changesLocalState(patch)) {
69023
+ function observeExternalCollaborationSession(session, onSnapshot) {
69024
+ let active = true;
69025
+ let subscribing = true;
69026
+ let previous;
69027
+ let unsubscribe;
69028
+ const notify = () => {
69029
+ if (!active || subscribing) {
69047
69030
  return;
69048
69031
  }
69049
- Object.assign(local, patch);
69050
- const elapsed = Date.now() - lastBroadcast;
69051
- if (elapsed >= BROADCAST_THROTTLE_MS) {
69052
- if (pending !== null) {
69053
- clearTimeout(pending);
69054
- pending = null;
69055
- }
69056
- lastBroadcast = Date.now();
69057
- publish();
69058
- }
69059
- else if (pending === null) {
69060
- pending = setTimeout(() => {
69061
- pending = null;
69062
- lastBroadcast = Date.now();
69063
- publish();
69064
- }, BROADCAST_THROTTLE_MS - elapsed);
69065
- }
69066
- }
69067
- function flush() {
69068
- if (pending !== null) {
69069
- clearTimeout(pending);
69070
- pending = null;
69032
+ const snapshot = session.getSnapshot();
69033
+ if (previous?.status === snapshot.status && previous.synced === snapshot.synced) {
69034
+ return;
69071
69035
  }
69072
- lastBroadcast = Date.now();
69073
- publish();
69074
- }
69075
- function dispose() {
69076
- if (pending !== null) {
69077
- clearTimeout(pending);
69078
- pending = null;
69036
+ previous = { status: snapshot.status, synced: snapshot.synced };
69037
+ onSnapshot(previous);
69038
+ };
69039
+ const dispose = () => {
69040
+ if (!active) {
69041
+ return;
69079
69042
  }
69043
+ active = false;
69044
+ unsubscribe?.();
69045
+ };
69046
+ try {
69047
+ unsubscribe = session.subscribe(notify);
69048
+ subscribing = false;
69049
+ notify();
69080
69050
  }
69081
- // Announce initial presence immediately (without consuming the throttle).
69082
- publish();
69083
- return { update, flush, dispose };
69084
- }
69085
-
69086
- /**
69087
- * Shallow, order-sensitive array equality, for selector subscriptions whose
69088
- * slice is an array.
69089
- *
69090
- * Needed more often than it looks. Several shared stores rebuild their state by
69091
- * deep-cloning it on every write (`cloneViewerOptions` is one), so a nested
69092
- * array is a NEW array after any change anywhere in the object, even when its
69093
- * contents did not move. Under the default `Object.is` a selector reading that
69094
- * array would therefore fire on every unrelated write, which is exactly the
69095
- * over-notification a selector is supposed to prevent.
69096
- */
69097
- function sameArray(a, b, isEqual = Object.is) {
69098
- if (a === b) {
69099
- return true;
69100
- }
69101
- if (a.length !== b.length) {
69102
- return false;
69103
- }
69104
- return a.every((entry, index) => {
69105
- const other = b[index];
69106
- return other !== undefined && isEqual(entry, other);
69107
- });
69108
- }
69109
- function zoomEqual(a, b) {
69110
- if (a === b) {
69111
- return true;
69112
- }
69113
- if (!a || !b) {
69114
- return false;
69115
- }
69116
- return a.scale === b.scale && a.originX === b.originX && a.originY === b.originY;
69117
- }
69118
- function pointerEqual(a, b) {
69119
- if (a === b) {
69120
- return true;
69121
- }
69122
- if (!a || !b) {
69123
- return false;
69124
- }
69125
- return a.tool === b.tool && a.x === b.x && a.y === b.y && a.color === b.color;
69126
- }
69127
- /**
69128
- * Whether two presentation snapshots convey the same state.
69129
- *
69130
- * `sequence` is deliberately EXCLUDED. It is a monotonic change counter that is
69131
- * bumped by construction on every merge, so including it would make every
69132
- * snapshot unequal to every other and defeat the entire guard. Nothing compares
69133
- * sequences for ordering; the audience-side validator only type-checks it.
69134
- *
69135
- * `inkStrokes` is compared by reference on purpose. The shared ink helpers are
69136
- * copy-on-write (`appendPresentationInkPoint` returns a new array), so identity
69137
- * is an exact change signal, and a deep walk would run per pointer move while a
69138
- * presenter is drawing, which is the one place we can least afford it.
69139
- */
69140
- function presentationSnapshotsEqual(a, b) {
69141
- if (a === b) {
69142
- return true;
69143
- }
69144
- return (a.slideIndex === b.slideIndex &&
69145
- a.buildStep === b.buildStep &&
69146
- a.blackout === b.blackout &&
69147
- a.paused === b.paused &&
69148
- a.elapsedMs === b.elapsedMs &&
69149
- a.caption === b.caption &&
69150
- a.subtitlesVisible === b.subtitlesVisible &&
69151
- a.inkMarkupVisible === b.inkMarkupVisible &&
69152
- a.inkStrokes === b.inkStrokes &&
69153
- zoomEqual(a.zoom, b.zoom) &&
69154
- pointerEqual(a.pointer, b.pointer));
69155
- }
69156
- /**
69157
- * Whether two sanitised presence entries describe the same collaborator in the
69158
- * same place.
69159
- *
69160
- * `lastUpdated` is deliberately EXCLUDED, and that exclusion is the whole point:
69161
- * every peer re-stamps it on each heartbeat, so comparing it would mean an
69162
- * idle room still re-rendered every client on a fixed interval forever. Dropping
69163
- * it does not weaken staleness handling, because a peer that genuinely goes
69164
- * stale is removed from the list by `derivePresenceList` and the resulting
69165
- * length change is caught here.
69166
- */
69167
- function presenceEntriesEqual(a, b) {
69168
- if (a === b) {
69169
- return true;
69170
- }
69171
- return (a.clientId === b.clientId &&
69172
- a.userName === b.userName &&
69173
- a.userColor === b.userColor &&
69174
- a.userAvatar === b.userAvatar &&
69175
- a.role === b.role &&
69176
- a.activeSlideIndex === b.activeSlideIndex &&
69177
- a.cursorX === b.cursorX &&
69178
- a.cursorY === b.cursorY &&
69179
- a.selectedElementId === b.selectedElementId);
69180
- }
69181
- /**
69182
- * Whether two derived presence lists are interchangeable, so a binding can keep
69183
- * its existing array (and skip the re-render) instead of adopting a fresh one.
69184
- * Order is significant, which is safe because `derivePresenceList` walks the
69185
- * awareness map in insertion order.
69186
- */
69187
- function presenceListsEqual(a, b) {
69188
- if (a === b) {
69189
- return true;
69190
- }
69191
- if (a.length !== b.length) {
69192
- return false;
69051
+ catch (error) {
69052
+ dispose();
69053
+ throw error;
69193
69054
  }
69194
- return a.every((entry, index) => {
69195
- const other = b[index];
69196
- return other !== undefined && presenceEntriesEqual(entry, other);
69197
- });
69055
+ return dispose;
69198
69056
  }
69199
-
69200
69057
  /**
69201
- * collaboration-presence-projector.ts: a memoising projection from a raw Yjs
69202
- * awareness map to the presence + cursor view-models every binding renders.
69203
- *
69204
- * WHY this is shared rather than five local guards: awareness fires on every
69205
- * peer heartbeat, and each peer re-stamps `lastUpdated` on a fixed interval, so
69206
- * an idle room emits a steady stream of events that carry no visible change.
69207
- * `derivePresenceList` allocates a fresh array per call, so each of those events
69208
- * looked like new state to React (`setState`), Vue (`ref.value =`), Angular
69209
- * (`signal.set`), Svelte (`$state =`) and Vanilla (an imperative repaint) alike.
69210
- * Five bindings were each re-rendering their collaboration layer on a timer.
69211
- *
69212
- * The projector holds the previous result and returns it BY IDENTITY when
69213
- * nothing a user could see has changed, along with an explicit `changed` flag
69214
- * for the imperative bindings that need to skip work rather than skip a render.
69215
- *
69216
- * The memo key is the derived presence list plus the local active slide, since
69217
- * cursors are filtered to the slide the local user is on: a peer that did not
69218
- * move still needs its cursor re-projected when WE change slide.
69058
+ * Give existing presence publishers a borrowed awareness view. Stop publishers
69059
+ * and remove their listeners before disposing this lease. Only the `presence`
69060
+ * field last written through this lease is restored; unrelated host fields and
69061
+ * subsequent host presence writes are preserved. A departed host is never
69062
+ * brought back by viewer cleanup.
69219
69063
  */
69220
- function createPresenceProjector() {
69221
- let lastList = [];
69222
- let lastCursors = [];
69223
- let lastActiveSlideIndex;
69224
- let primed = false;
69225
- return {
69226
- project(states, localClientId, canvasWidth, canvasHeight, localActiveSlideIndex, now) {
69227
- const list = derivePresenceList(states, localClientId, canvasWidth, canvasHeight, now ?? Date.now());
69228
- if (primed &&
69229
- localActiveSlideIndex === lastActiveSlideIndex &&
69230
- presenceListsEqual(lastList, list)) {
69231
- return { list: lastList, cursors: lastCursors, changed: false };
69064
+ function borrowExternalCollaborationAwareness(host) {
69065
+ let disposed = false;
69066
+ let ownsPresence = false;
69067
+ let hadPresence = false;
69068
+ let previousPresence;
69069
+ let lastPresence;
69070
+ const awareness = {
69071
+ clientID: host.clientID,
69072
+ getStates: () => host.getStates(),
69073
+ on: (event, callback) => host.on(event, callback),
69074
+ off: (event, callback) => host.off(event, callback),
69075
+ setLocalStateField: (field, value) => {
69076
+ if (disposed) {
69077
+ return;
69232
69078
  }
69233
- lastList = list;
69234
- lastCursors = presenceToCursors(list, localActiveSlideIndex);
69235
- lastActiveSlideIndex = localActiveSlideIndex;
69236
- primed = true;
69237
- return { list, cursors: lastCursors, changed: true };
69079
+ const state = host.getLocalState();
69080
+ if (state === null) {
69081
+ return;
69082
+ }
69083
+ if (field === 'presence') {
69084
+ if (!ownsPresence || state.presence !== lastPresence) {
69085
+ hadPresence = Object.hasOwn(state, 'presence');
69086
+ previousPresence = state.presence;
69087
+ }
69088
+ lastPresence = value;
69089
+ ownsPresence = true;
69090
+ }
69091
+ host.setLocalStateField(field, value);
69238
69092
  },
69239
- reset() {
69240
- lastList = [];
69241
- lastCursors = [];
69242
- lastActiveSlideIndex = undefined;
69243
- primed = false;
69093
+ };
69094
+ return {
69095
+ awareness,
69096
+ dispose: () => {
69097
+ if (disposed) {
69098
+ return;
69099
+ }
69100
+ disposed = true;
69101
+ const current = host.getLocalState();
69102
+ if (!ownsPresence || current === null || current.presence !== lastPresence) {
69103
+ return;
69104
+ }
69105
+ const restored = { ...current };
69106
+ if (hadPresence) {
69107
+ restored.presence = previousPresence;
69108
+ }
69109
+ else {
69110
+ delete restored.presence;
69111
+ }
69112
+ host.setLocalState(restored);
69244
69113
  },
69245
69114
  };
69246
69115
  }
69247
69116
 
69248
- const DEFAULT_LOCAL_COLOR = '#6366f1';
69249
- /**
69250
- * Two-letter initials for an avatar-circle fallback: first+last initial for a
69251
- * multi-word name, otherwise the first two characters.
69252
- */
69253
- function getUserInitials(name) {
69254
- const trimmed = name.trim(), parts = trimmed.split(/\s+/u).filter(Boolean), first = parts[0], last = parts[parts.length - 1];
69255
- if (parts.length >= 2 && first && last) {
69256
- return (first[0] + last[0]).toUpperCase();
69257
- }
69258
- return trimmed.slice(0, 2).toUpperCase();
69259
- }
69260
- /**
69261
- * Local user first, then remote users in their existing order - the order
69262
- * every binding's connected-users list has rendered since React's original.
69263
- */
69264
- function buildActiveSessionUsers(params) {
69265
- const local = {
69266
- id: 'local',
69267
- name: params.localUserName,
69268
- initials: params.localUserInitials?.trim()
69269
- ? params.localUserInitials.trim().slice(0, 2).toUpperCase()
69270
- : getUserInitials(params.localUserName),
69271
- color: params.localUserColor ?? DEFAULT_LOCAL_COLOR,
69272
- isLocal: true,
69273
- }, remote = params.remoteUsers.map((user) => ({
69274
- id: String(user.clientId),
69275
- name: user.userName,
69276
- initials: getUserInitials(user.userName),
69277
- color: user.userColor,
69278
- avatarUrl: user.userAvatar,
69279
- isLocal: false,
69280
- slideNumber: user.activeSlideIndex + 1,
69281
- }));
69282
- return [local, ...remote];
69283
- }
69284
-
69285
69117
  /**
69286
69118
  * collaboration-assets.ts: binary payload sync for the pptx-viewer
69287
69119
  * collaboration stack.
@@ -69649,8 +69481,16 @@ function decodeDelta(delta) {
69649
69481
  // attribute; a literal newline TEXT run (no pb/lb) must keep its text or
69650
69482
  // runs like "Project\nAtlas" collapse to "ProjectAtlas" on decode. The
69651
69483
  // zero-width space is always the empty-run attribute holder.
69652
- const isBreakMarker = op.insert === '\n' && (a.pb === '1' || a.lb === '1');
69653
- if (!isBreakMarker && op.insert !== '​') {
69484
+ const isBreakMarker = /^\n+$/u.test(op.insert) && (a.pb === '1' || a.lb === '1');
69485
+ if (isBreakMarker) {
69486
+ // Y.Text coalesces adjacent equal-attribute breaks into one delta op.
69487
+ // Each character still represents a separate paragraph or soft break.
69488
+ for (let index = 0; index < op.insert.length; index++) {
69489
+ segments.push(structuredClone(seg));
69490
+ }
69491
+ continue;
69492
+ }
69493
+ if (op.insert !== '​') {
69654
69494
  seg.text = op.insert;
69655
69495
  }
69656
69496
  segments.push(seg);
@@ -70051,28 +69891,6 @@ function observeYDocSlides(ydoc, onChange) {
70051
69891
  return () => arr.unobserveDeep(onChange);
70052
69892
  }
70053
69893
 
70054
- /**
70055
- * collaboration-broadcast-follow.ts: the single, framework-agnostic rule for
70056
- * whether the local user should auto-follow a broadcaster's active slide.
70057
- *
70058
- * A "broadcast" session has one presenter that joins with `role: 'owner'`.
70059
- * Auto-follow is one-way: only a local `viewer` is pulled along to the
70060
- * owner's slide. A `collaborator` (two-way editing peer) and the `owner`
70061
- * itself navigate freely and are never yanked.
70062
- *
70063
- * Every binding (React/Vue/Angular/Svelte/Vanilla) must consult this helper
70064
- * instead of hand-rolling the guard, so the policy cannot drift per binding
70065
- * (previously React/Angular gated on `localRole === 'viewer'` while Vue
70066
- * followed the owner regardless of the local role).
70067
- */
70068
- /**
70069
- * True when the local user should auto-follow the given broadcaster's active
70070
- * slide: the local user is a `viewer` and the broadcaster is the `owner`.
70071
- */
70072
- function shouldAutoFollowBroadcaster(input) {
70073
- return input.localRole === 'viewer' && input.broadcasterRole === 'owner';
70074
- }
70075
-
70076
69894
  /**
70077
69895
  * collaboration-text-merge.ts: character-level in-place merging of a desired
70078
69896
  * text state into a live Y.Text.
@@ -70440,6 +70258,432 @@ function reconcileSlidesInYDoc(slides, ydoc, factories, origin = LOCAL_SYNC_ORIG
70440
70258
  }, origin);
70441
70259
  }
70442
70260
 
70261
+ /** One readiness/adoption policy for both full-deck and interim writes. */
70262
+ function observeExternalCollaborationReadiness(session, options) {
70263
+ let active = true;
70264
+ let established = false;
70265
+ let awaitingJoin = options.sessionIntent === 'join';
70266
+ let lastAdopted;
70267
+ let lastReadOnly;
70268
+ let unsubscribe;
70269
+ const canWrite = () => active && options.gate.isOpen() && session.getSnapshot().synced && options.role !== 'viewer';
70270
+ const notifyReadOnly = () => {
70271
+ const readOnly = !canWrite();
70272
+ if (lastReadOnly !== readOnly) {
70273
+ lastReadOnly = readOnly;
70274
+ options.onReadOnlyChange?.(readOnly);
70275
+ }
70276
+ };
70277
+ const suspend = () => {
70278
+ options.gate.reset();
70279
+ options.livePatcher?.configure(null, null);
70280
+ options.onSuspend?.();
70281
+ notifyReadOnly();
70282
+ };
70283
+ const adopt = (slides) => {
70284
+ const serialized = JSON.stringify(slides);
70285
+ if (serialized !== lastAdopted) {
70286
+ lastAdopted = serialized;
70287
+ options.adoptSlides(slides);
70288
+ }
70289
+ };
70290
+ const refresh = () => {
70291
+ if (!active) {
70292
+ return;
70293
+ }
70294
+ const snapshot = session.getSnapshot();
70295
+ options.onStatus(snapshot.status);
70296
+ if (!snapshot.synced) {
70297
+ suspend();
70298
+ return;
70299
+ }
70300
+ if (options.gate.isOpen()) {
70301
+ return;
70302
+ }
70303
+ // Local UI state may have changed while suspended even when room bytes
70304
+ // did not. Re-adopt before reopening instead of trusting the old echo.
70305
+ lastAdopted = undefined;
70306
+ const slides = readSlidesFromYDoc(session.doc);
70307
+ if (slides.length > 0) {
70308
+ established = true;
70309
+ awaitingJoin = false;
70310
+ adopt(slides);
70311
+ }
70312
+ else if (established) {
70313
+ adopt(slides);
70314
+ }
70315
+ else if (awaitingJoin) {
70316
+ suspend();
70317
+ return;
70318
+ }
70319
+ options.livePatcher?.configure(options.role === 'viewer' ? null : session.doc, options.role === 'viewer' ? null : options.factories,
70320
+ // The host owns readiness, so accepted edits cannot wait in our timer.
70321
+ true);
70322
+ options.gate.open();
70323
+ notifyReadOnly();
70324
+ options.onReady?.({ seedEmptyRoom: slides.length === 0 && !established });
70325
+ };
70326
+ const unobserve = observeYDocSlides(session.doc, (_events, transaction) => {
70327
+ if (!active || !session.getSnapshot().synced) {
70328
+ return;
70329
+ }
70330
+ const slides = readSlidesFromYDoc(session.doc);
70331
+ if (slides.length > 0) {
70332
+ established = true;
70333
+ awaitingJoin = false;
70334
+ }
70335
+ if (transaction?.origin === LOCAL_SYNC_ORIGIN) {
70336
+ // A no-op reconciliation of [] must not establish room ownership.
70337
+ lastAdopted = JSON.stringify(slides);
70338
+ return;
70339
+ }
70340
+ if (!options.gate.isOpen()) {
70341
+ refresh();
70342
+ }
70343
+ else if (slides.length > 0 || established) {
70344
+ adopt(slides);
70345
+ }
70346
+ });
70347
+ const dispose = () => {
70348
+ if (!active) {
70349
+ return;
70350
+ }
70351
+ active = false;
70352
+ unsubscribe?.();
70353
+ unobserve();
70354
+ suspend();
70355
+ };
70356
+ try {
70357
+ unsubscribe = observeExternalCollaborationSession(session, refresh);
70358
+ }
70359
+ catch (error) {
70360
+ dispose();
70361
+ throw error;
70362
+ }
70363
+ return Object.assign(dispose, {
70364
+ refresh,
70365
+ canWrite,
70366
+ handleLoad: (origin) => {
70367
+ if (!active) {
70368
+ return false;
70369
+ }
70370
+ if (origin === 'user') {
70371
+ awaitingJoin = false;
70372
+ refresh();
70373
+ }
70374
+ else if (session.getSnapshot().synced) {
70375
+ const slides = readSlidesFromYDoc(session.doc);
70376
+ if (slides.length > 0) {
70377
+ lastAdopted = undefined;
70378
+ adopt(slides);
70379
+ return true;
70380
+ }
70381
+ }
70382
+ return false;
70383
+ },
70384
+ allowLocalLoad: () => {
70385
+ awaitingJoin = false;
70386
+ refresh();
70387
+ },
70388
+ });
70389
+ }
70390
+
70391
+ /**
70392
+ * Create a throttled presence publisher bound to an awareness instance. The
70393
+ * initial presence is announced synchronously so peers learn of us at once.
70394
+ */
70395
+ function createPresencePublisher(awareness, identity) {
70396
+ const local = { activeSlideIndex: 0, cursorX: 0, cursorY: 0 };
70397
+ // Starts at 0 so the first real update publishes immediately (the initial
70398
+ // announce below does not consume the throttle budget).
70399
+ let lastBroadcast = 0;
70400
+ let pending = null;
70401
+ function publish() {
70402
+ awareness.setLocalStateField('presence', {
70403
+ userName: identity.userName,
70404
+ userColor: identity.userColor,
70405
+ userAvatar: identity.userAvatar,
70406
+ role: identity.role,
70407
+ activeSlideIndex: local.activeSlideIndex,
70408
+ cursorX: local.cursorX,
70409
+ cursorY: local.cursorY,
70410
+ selectedElementId: local.selectedElementId,
70411
+ lastUpdated: new Date().toISOString(),
70412
+ });
70413
+ }
70414
+ /** Whether `patch` would actually change any field of the local state. */
70415
+ function changesLocalState(patch) {
70416
+ return Object.keys(patch).some((key) => patch[key] !== local[key]);
70417
+ }
70418
+ function update(patch) {
70419
+ // A patch that says nothing new is dropped before it reaches the wire.
70420
+ // Bindings call this from pointer-move and selection handlers that
70421
+ // re-fire with identical values (a cursor resting inside one slide pixel,
70422
+ // a re-click on the already-selected element). Publishing anyway re-stamps
70423
+ // `lastUpdated`, which every peer sees as a presence change and turns into
70424
+ // a re-render, so an idle room kept re-rendering all five bindings.
70425
+ // `flush` is exempt: the heartbeat MUST keep publishing or peers time us out.
70426
+ if (!changesLocalState(patch)) {
70427
+ return;
70428
+ }
70429
+ Object.assign(local, patch);
70430
+ const elapsed = Date.now() - lastBroadcast;
70431
+ if (elapsed >= BROADCAST_THROTTLE_MS) {
70432
+ if (pending !== null) {
70433
+ clearTimeout(pending);
70434
+ pending = null;
70435
+ }
70436
+ lastBroadcast = Date.now();
70437
+ publish();
70438
+ }
70439
+ else if (pending === null) {
70440
+ pending = setTimeout(() => {
70441
+ pending = null;
70442
+ lastBroadcast = Date.now();
70443
+ publish();
70444
+ }, BROADCAST_THROTTLE_MS - elapsed);
70445
+ }
70446
+ }
70447
+ function flush() {
70448
+ if (pending !== null) {
70449
+ clearTimeout(pending);
70450
+ pending = null;
70451
+ }
70452
+ lastBroadcast = Date.now();
70453
+ publish();
70454
+ }
70455
+ function dispose() {
70456
+ if (pending !== null) {
70457
+ clearTimeout(pending);
70458
+ pending = null;
70459
+ }
70460
+ }
70461
+ // Announce initial presence immediately (without consuming the throttle).
70462
+ publish();
70463
+ return { update, flush, dispose };
70464
+ }
70465
+
70466
+ /**
70467
+ * Shallow, order-sensitive array equality, for selector subscriptions whose
70468
+ * slice is an array.
70469
+ *
70470
+ * Needed more often than it looks. Several shared stores rebuild their state by
70471
+ * deep-cloning it on every write (`cloneViewerOptions` is one), so a nested
70472
+ * array is a NEW array after any change anywhere in the object, even when its
70473
+ * contents did not move. Under the default `Object.is` a selector reading that
70474
+ * array would therefore fire on every unrelated write, which is exactly the
70475
+ * over-notification a selector is supposed to prevent.
70476
+ */
70477
+ function sameArray(a, b, isEqual = Object.is) {
70478
+ if (a === b) {
70479
+ return true;
70480
+ }
70481
+ if (a.length !== b.length) {
70482
+ return false;
70483
+ }
70484
+ return a.every((entry, index) => {
70485
+ const other = b[index];
70486
+ return other !== undefined && isEqual(entry, other);
70487
+ });
70488
+ }
70489
+ function zoomEqual(a, b) {
70490
+ if (a === b) {
70491
+ return true;
70492
+ }
70493
+ if (!a || !b) {
70494
+ return false;
70495
+ }
70496
+ return a.scale === b.scale && a.originX === b.originX && a.originY === b.originY;
70497
+ }
70498
+ function pointerEqual(a, b) {
70499
+ if (a === b) {
70500
+ return true;
70501
+ }
70502
+ if (!a || !b) {
70503
+ return false;
70504
+ }
70505
+ return a.tool === b.tool && a.x === b.x && a.y === b.y && a.color === b.color;
70506
+ }
70507
+ /**
70508
+ * Whether two presentation snapshots convey the same state.
70509
+ *
70510
+ * `sequence` is deliberately EXCLUDED. It is a monotonic change counter that is
70511
+ * bumped by construction on every merge, so including it would make every
70512
+ * snapshot unequal to every other and defeat the entire guard. Nothing compares
70513
+ * sequences for ordering; the audience-side validator only type-checks it.
70514
+ *
70515
+ * `inkStrokes` is compared by reference on purpose. The shared ink helpers are
70516
+ * copy-on-write (`appendPresentationInkPoint` returns a new array), so identity
70517
+ * is an exact change signal, and a deep walk would run per pointer move while a
70518
+ * presenter is drawing, which is the one place we can least afford it.
70519
+ */
70520
+ function presentationSnapshotsEqual(a, b) {
70521
+ if (a === b) {
70522
+ return true;
70523
+ }
70524
+ return (a.slideIndex === b.slideIndex &&
70525
+ a.buildStep === b.buildStep &&
70526
+ a.blackout === b.blackout &&
70527
+ a.paused === b.paused &&
70528
+ a.elapsedMs === b.elapsedMs &&
70529
+ a.caption === b.caption &&
70530
+ a.subtitlesVisible === b.subtitlesVisible &&
70531
+ a.inkMarkupVisible === b.inkMarkupVisible &&
70532
+ a.inkStrokes === b.inkStrokes &&
70533
+ zoomEqual(a.zoom, b.zoom) &&
70534
+ pointerEqual(a.pointer, b.pointer));
70535
+ }
70536
+ /**
70537
+ * Whether two sanitised presence entries describe the same collaborator in the
70538
+ * same place.
70539
+ *
70540
+ * `lastUpdated` is deliberately EXCLUDED, and that exclusion is the whole point:
70541
+ * every peer re-stamps it on each heartbeat, so comparing it would mean an
70542
+ * idle room still re-rendered every client on a fixed interval forever. Dropping
70543
+ * it does not weaken staleness handling, because a peer that genuinely goes
70544
+ * stale is removed from the list by `derivePresenceList` and the resulting
70545
+ * length change is caught here.
70546
+ */
70547
+ function presenceEntriesEqual(a, b) {
70548
+ if (a === b) {
70549
+ return true;
70550
+ }
70551
+ return (a.clientId === b.clientId &&
70552
+ a.userName === b.userName &&
70553
+ a.userColor === b.userColor &&
70554
+ a.userAvatar === b.userAvatar &&
70555
+ a.role === b.role &&
70556
+ a.activeSlideIndex === b.activeSlideIndex &&
70557
+ a.cursorX === b.cursorX &&
70558
+ a.cursorY === b.cursorY &&
70559
+ a.selectedElementId === b.selectedElementId);
70560
+ }
70561
+ /**
70562
+ * Whether two derived presence lists are interchangeable, so a binding can keep
70563
+ * its existing array (and skip the re-render) instead of adopting a fresh one.
70564
+ * Order is significant, which is safe because `derivePresenceList` walks the
70565
+ * awareness map in insertion order.
70566
+ */
70567
+ function presenceListsEqual(a, b) {
70568
+ if (a === b) {
70569
+ return true;
70570
+ }
70571
+ if (a.length !== b.length) {
70572
+ return false;
70573
+ }
70574
+ return a.every((entry, index) => {
70575
+ const other = b[index];
70576
+ return other !== undefined && presenceEntriesEqual(entry, other);
70577
+ });
70578
+ }
70579
+
70580
+ /**
70581
+ * collaboration-presence-projector.ts: a memoising projection from a raw Yjs
70582
+ * awareness map to the presence + cursor view-models every binding renders.
70583
+ *
70584
+ * WHY this is shared rather than five local guards: awareness fires on every
70585
+ * peer heartbeat, and each peer re-stamps `lastUpdated` on a fixed interval, so
70586
+ * an idle room emits a steady stream of events that carry no visible change.
70587
+ * `derivePresenceList` allocates a fresh array per call, so each of those events
70588
+ * looked like new state to React (`setState`), Vue (`ref.value =`), Angular
70589
+ * (`signal.set`), Svelte (`$state =`) and Vanilla (an imperative repaint) alike.
70590
+ * Five bindings were each re-rendering their collaboration layer on a timer.
70591
+ *
70592
+ * The projector holds the previous result and returns it BY IDENTITY when
70593
+ * nothing a user could see has changed, along with an explicit `changed` flag
70594
+ * for the imperative bindings that need to skip work rather than skip a render.
70595
+ *
70596
+ * The memo key is the derived presence list plus the local active slide, since
70597
+ * cursors are filtered to the slide the local user is on: a peer that did not
70598
+ * move still needs its cursor re-projected when WE change slide.
70599
+ */
70600
+ function createPresenceProjector() {
70601
+ let lastList = [];
70602
+ let lastCursors = [];
70603
+ let lastActiveSlideIndex;
70604
+ let primed = false;
70605
+ return {
70606
+ project(states, localClientId, canvasWidth, canvasHeight, localActiveSlideIndex, now) {
70607
+ const list = derivePresenceList(states, localClientId, canvasWidth, canvasHeight, now ?? Date.now());
70608
+ if (primed &&
70609
+ localActiveSlideIndex === lastActiveSlideIndex &&
70610
+ presenceListsEqual(lastList, list)) {
70611
+ return { list: lastList, cursors: lastCursors, changed: false };
70612
+ }
70613
+ lastList = list;
70614
+ lastCursors = presenceToCursors(list, localActiveSlideIndex);
70615
+ lastActiveSlideIndex = localActiveSlideIndex;
70616
+ primed = true;
70617
+ return { list, cursors: lastCursors, changed: true };
70618
+ },
70619
+ reset() {
70620
+ lastList = [];
70621
+ lastCursors = [];
70622
+ lastActiveSlideIndex = undefined;
70623
+ primed = false;
70624
+ },
70625
+ };
70626
+ }
70627
+
70628
+ const DEFAULT_LOCAL_COLOR = '#6366f1';
70629
+ /**
70630
+ * Two-letter initials for an avatar-circle fallback: first+last initial for a
70631
+ * multi-word name, otherwise the first two characters.
70632
+ */
70633
+ function getUserInitials(name) {
70634
+ const trimmed = name.trim(), parts = trimmed.split(/\s+/u).filter(Boolean), first = parts[0], last = parts[parts.length - 1];
70635
+ if (parts.length >= 2 && first && last) {
70636
+ return (first[0] + last[0]).toUpperCase();
70637
+ }
70638
+ return trimmed.slice(0, 2).toUpperCase();
70639
+ }
70640
+ /**
70641
+ * Local user first, then remote users in their existing order - the order
70642
+ * every binding's connected-users list has rendered since React's original.
70643
+ */
70644
+ function buildActiveSessionUsers(params) {
70645
+ const local = {
70646
+ id: 'local',
70647
+ name: params.localUserName,
70648
+ initials: params.localUserInitials?.trim()
70649
+ ? params.localUserInitials.trim().slice(0, 2).toUpperCase()
70650
+ : getUserInitials(params.localUserName),
70651
+ color: params.localUserColor ?? DEFAULT_LOCAL_COLOR,
70652
+ isLocal: true,
70653
+ }, remote = params.remoteUsers.map((user) => ({
70654
+ id: String(user.clientId),
70655
+ name: user.userName,
70656
+ initials: getUserInitials(user.userName),
70657
+ color: user.userColor,
70658
+ avatarUrl: user.userAvatar,
70659
+ isLocal: false,
70660
+ slideNumber: user.activeSlideIndex + 1,
70661
+ }));
70662
+ return [local, ...remote];
70663
+ }
70664
+
70665
+ /**
70666
+ * collaboration-broadcast-follow.ts: the single, framework-agnostic rule for
70667
+ * whether the local user should auto-follow a broadcaster's active slide.
70668
+ *
70669
+ * A "broadcast" session has one presenter that joins with `role: 'owner'`.
70670
+ * Auto-follow is one-way: only a local `viewer` is pulled along to the
70671
+ * owner's slide. A `collaborator` (two-way editing peer) and the `owner`
70672
+ * itself navigate freely and are never yanked.
70673
+ *
70674
+ * Every binding (React/Vue/Angular/Svelte/Vanilla) must consult this helper
70675
+ * instead of hand-rolling the guard, so the policy cannot drift per binding
70676
+ * (previously React/Angular gated on `localRole === 'viewer'` while Vue
70677
+ * followed the owner regardless of the local role).
70678
+ */
70679
+ /**
70680
+ * True when the local user should auto-follow the given broadcaster's active
70681
+ * slide: the local user is a `viewer` and the broadcaster is the `owner`.
70682
+ */
70683
+ function shouldAutoFollowBroadcaster(input) {
70684
+ return input.localRole === 'viewer' && input.broadcasterRole === 'owner';
70685
+ }
70686
+
70443
70687
  /**
70444
70688
  * collaboration-live-patch-target.ts: locating an element inside the shared
70445
70689
  * Y.Doc and writing one interim patch into it.
@@ -70533,7 +70777,8 @@ function applyLivePatch(doc, factories, patch, textPatchLimit) {
70533
70777
  * pass uses (`reconcileElementTextBody`), so concurrent typing on one
70534
70778
  * element still merges instead of last-write-wins
70535
70779
  * - writes are throttled (~1 per 50ms) with a trailing write, plus an
70536
- * explicit `flush()` for gesture end
70780
+ * explicit `flush()` for gesture end; borrowed sessions opt into immediate
70781
+ * writes so the host can revoke readiness without losing accepted edits
70537
70782
  * - every transaction is tagged with LOCAL_SYNC_ORIGIN, exactly like
70538
70783
  * `reconcileSlidesInYDoc`, so the local observer skips the echo
70539
70784
  *
@@ -70552,6 +70797,7 @@ function createCollaborationLivePatcher(options = {}) {
70552
70797
  let factories = null;
70553
70798
  let timer = null;
70554
70799
  let lastWriteAt = Number.NEGATIVE_INFINITY;
70800
+ let immediate = false;
70555
70801
  const cancelTimer = () => {
70556
70802
  if (timer !== null) {
70557
70803
  clearTimeout(timer);
@@ -70580,6 +70826,10 @@ function createCollaborationLivePatcher(options = {}) {
70580
70826
  }
70581
70827
  };
70582
70828
  const schedule = () => {
70829
+ if (immediate) {
70830
+ writePending();
70831
+ return;
70832
+ }
70583
70833
  if (timer !== null) {
70584
70834
  return;
70585
70835
  }
@@ -70606,14 +70856,15 @@ function createCollaborationLivePatcher(options = {}) {
70606
70856
  return entry;
70607
70857
  };
70608
70858
  return {
70609
- configure(nextDoc, nextFactories) {
70610
- if (nextDoc === doc && nextFactories === factories) {
70859
+ configure(nextDoc, nextFactories, nextImmediate = false) {
70860
+ if (nextDoc === doc && nextFactories === factories && nextImmediate === immediate) {
70611
70861
  return;
70612
70862
  }
70613
70863
  cancelTimer();
70614
70864
  pending.clear();
70615
70865
  doc = nextDoc;
70616
70866
  factories = nextFactories;
70867
+ immediate = nextImmediate;
70617
70868
  lastWriteAt = Number.NEGATIVE_INFINITY;
70618
70869
  },
70619
70870
  isActive() {
@@ -70846,7 +71097,12 @@ function registerCollaborationTeardown(options) {
70846
71097
  doLeave();
70847
71098
  };
70848
71099
  const onBeforeUnload = () => {
70849
- doLeave();
71100
+ const allowed = typeof options.leaveOnBeforeUnload === 'function'
71101
+ ? options.leaveOnBeforeUnload()
71102
+ : options.leaveOnBeforeUnload !== false;
71103
+ if (allowed) {
71104
+ doLeave();
71105
+ }
70850
71106
  };
70851
71107
  const onPageShow = (event) => {
70852
71108
  if (event.persisted !== true || !left || !rejoin) {
@@ -71000,51 +71256,90 @@ function createDepartureChannel(roomId, awareness, factory) {
71000
71256
  * edits survive), re-serializes to PPTX bytes, and hands them to
71001
71257
  * `config.onWriteBack`.
71002
71258
  *
71003
- * Framework-agnostic: every binding (Vue, Svelte, Vanilla) shares this single
71004
- * implementation instead of maintaining its own near-identical copy. A
71005
- * binding without template-mode editing simply omits `getTemplateElements`/
71006
- * `mergeTemplateElements`. Angular keeps its own class-based
71007
- * `WriteBackScheduler` (a different calling convention tied to its DI style),
71008
- * not a duplicate of this one.
71259
+ * Framework-agnostic: every binding shares this scheduling policy. Bindings
71260
+ * with a retained serializer provide it through `serialize`; other bindings
71261
+ * use the source-byte reload path below.
71009
71262
  */
71010
71263
  const DEFAULT_DEBOUNCE_MS = 5_000;
71011
71264
  function createWriteBackScheduler(deps) {
71012
71265
  let timer = null;
71266
+ let generation = 0;
71267
+ let unsubscribe = null;
71013
71268
  function cancel() {
71269
+ generation++;
71270
+ unsubscribe?.();
71271
+ unsubscribe = null;
71014
71272
  if (timer !== null) {
71015
71273
  clearTimeout(timer);
71016
71274
  timer = null;
71017
71275
  }
71018
71276
  }
71019
71277
  function schedule(config) {
71278
+ const isReady = () => config.externalSession?.getSnapshot().synced ?? true;
71020
71279
  if (!config.onWriteBack || config.role !== 'owner' || !deps.getYDoc()) {
71021
71280
  return;
71022
71281
  }
71023
71282
  cancel();
71283
+ if (!isReady()) {
71284
+ return;
71285
+ }
71286
+ const token = generation;
71287
+ if (config.externalSession) {
71288
+ const detach = config.externalSession.subscribe(() => {
71289
+ if (!isReady()) {
71290
+ cancel();
71291
+ }
71292
+ });
71293
+ // A host may synchronously notify during subscription.
71294
+ if (token !== generation || !isReady()) {
71295
+ detach();
71296
+ return;
71297
+ }
71298
+ unsubscribe = detach;
71299
+ }
71024
71300
  const debounceMs = config.writeBackDebounceMs ?? DEFAULT_DEBOUNCE_MS;
71025
71301
  timer = setTimeout(async () => {
71026
71302
  timer = null;
71027
71303
  const ydoc = deps.getYDoc();
71028
- if (!ydoc || !config.onWriteBack) {
71029
- return;
71030
- }
71031
- const sourceBytes = deps.getSourceBytes?.();
71032
- if (!sourceBytes) {
71033
- return;
71034
- }
71304
+ const isCurrent = () => token === generation && isReady() && deps.getYDoc() === ydoc;
71035
71305
  try {
71306
+ if (!ydoc || !config.onWriteBack || !isReady()) {
71307
+ return;
71308
+ }
71309
+ if (deps.serialize) {
71310
+ const bytes = await deps.serialize(isCurrent);
71311
+ if (bytes && isCurrent()) {
71312
+ await config.onWriteBack(bytes);
71313
+ }
71314
+ return;
71315
+ }
71316
+ const sourceBytes = deps.getSourceBytes?.();
71317
+ if (!sourceBytes) {
71318
+ return;
71319
+ }
71036
71320
  const handler = new PptxHandler();
71037
71321
  await handler.load(sourceBytes.buffer);
71322
+ if (!isCurrent()) {
71323
+ return;
71324
+ }
71038
71325
  const slides = readSlidesFromYDoc(ydoc);
71039
71326
  const merged = deps.mergeTemplateElements
71040
71327
  ? deps.mergeTemplateElements(slides, deps.getTemplateElements?.() ?? {})
71041
71328
  : slides;
71042
71329
  const bytes = await handler.save(merged, deps.getSaveOptions?.());
71043
- config.onWriteBack(bytes);
71330
+ if (isCurrent()) {
71331
+ await config.onWriteBack(bytes);
71332
+ }
71044
71333
  }
71045
71334
  catch {
71046
71335
  /* non-fatal */
71047
71336
  }
71337
+ finally {
71338
+ if (token === generation) {
71339
+ unsubscribe?.();
71340
+ unsubscribe = null;
71341
+ }
71342
+ }
71048
71343
  }, debounceMs);
71049
71344
  }
71050
71345
  return { schedule, cancel };
@@ -107630,7 +107925,7 @@ function createLocalStorageBackend(namespace) {
107630
107925
  /** Try IndexedDB first; fall back to localStorage on any failure. */
107631
107926
  async function resolveBackend(dbName, namespace) {
107632
107927
  try {
107633
- const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-amzrYVHP.mjs');
107928
+ const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-DewzErjQ.mjs');
107634
107929
  const db = await openChatDb(dbName);
107635
107930
  return createIdbBackend(db);
107636
107931
  }
@@ -110475,6 +110770,153 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
110475
110770
  `, styles: [":host{position:absolute;inset:0;pointer-events:none;overflow:visible;z-index:9999}.pptx-ng-collab-cursor{position:absolute;top:0;left:0;pointer-events:none;will-change:transform;transition:transform 90ms linear}.pptx-ng-collab-pointer{display:block;filter:drop-shadow(0 1px 1px rgba(0,0,0,.35))}.pptx-ng-collab-label{position:absolute;top:16px;left:12px;max-width:150px;padding:2px 6px;border-radius:4px;color:#fff;font-family:system-ui,sans-serif;font-size:10px;font-weight:500;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;box-shadow:0 1px 2px #0000004d}\n"] }]
110476
110771
  }], propDecorators: { cursors: [{ type: i0.Input, args: [{ isSignal: true, alias: "cursors", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }] } });
110477
110772
 
110773
+ /** Angular signals over the shared memoized awareness projection. */
110774
+ class CollaborationPresenceState {
110775
+ constructor() {
110776
+ this.presence = signal([], /* @ts-ignore */
110777
+ ...(ngDevMode ? [{ debugName: "presence" }] : /* istanbul ignore next */ []));
110778
+ this.cursors = computed(() => presenceToCursors(this.presence()), /* @ts-ignore */
110779
+ ...(ngDevMode ? [{ debugName: "cursors" }] : /* istanbul ignore next */ []));
110780
+ /** The client id the local user is currently following (null when free). */
110781
+ this.followedClientId = signal(null, /* @ts-ignore */
110782
+ ...(ngDevMode ? [{ debugName: "followedClientId" }] : /* istanbul ignore next */ []));
110783
+ /** Active-slide index of the followed peer, or null when not following. */
110784
+ this.followedSlideIndex = computed(() => {
110785
+ const id = this.followedClientId();
110786
+ return id === null
110787
+ ? null
110788
+ : (this.presence().find((peer) => peer.clientId === id)?.activeSlideIndex ?? null);
110789
+ }, /* @ts-ignore */
110790
+ ...(ngDevMode ? [{ debugName: "followedSlideIndex" }] : /* istanbul ignore next */ []));
110791
+ /** Active-slide index of the first owner peer (the broadcaster), or null. */
110792
+ this.broadcasterSlideIndex = computed(() => this.presence().find((peer) => peer.role === 'owner')?.activeSlideIndex ?? null, /* @ts-ignore */
110793
+ ...(ngDevMode ? [{ debugName: "broadcasterSlideIndex" }] : /* istanbul ignore next */ []));
110794
+ this.projector = createPresenceProjector();
110795
+ }
110796
+ refresh(session, width, height) {
110797
+ if (!session) {
110798
+ this.reset();
110799
+ return;
110800
+ }
110801
+ // Awareness fires on idle heartbeats too. Preserve array identity unless
110802
+ // visible presence changes so overlays do not rerender on a timer (#145).
110803
+ const { list, changed } = this.projector.project(session.awareness.getStates(), session.selfId, width, height);
110804
+ if (changed) {
110805
+ this.presence.set(list);
110806
+ }
110807
+ }
110808
+ reset() {
110809
+ // Rejoining the same peers must not reuse the departed session's memo.
110810
+ this.projector.reset();
110811
+ this.presence.set([]);
110812
+ this.followedClientId.set(null);
110813
+ }
110814
+ }
110815
+
110816
+ /**
110817
+ * collaboration-local-presence.ts: publishes the local user's presence into a
110818
+ * single awareness `presence` field.
110819
+ *
110820
+ * Cursor, selection, and active-slide updates all merge into one record so a
110821
+ * later cursor move never clobbers the selection (or vice versa). The remote
110822
+ * side reads this shape via the shared `derivePresenceList`.
110823
+ */
110824
+ class LocalPresencePublisher {
110825
+ constructor(awareness, identity) {
110826
+ this.awareness = awareness;
110827
+ this.identity = identity;
110828
+ this.activeSlide = 0;
110829
+ this.cursor = { x: 0, y: 0 };
110830
+ }
110831
+ /** Re-emit the merged presence record (also used as a heartbeat). */
110832
+ publish() {
110833
+ this.awareness.setLocalStateField('presence', {
110834
+ userName: this.identity.userName,
110835
+ userColor: this.identity.userColor,
110836
+ userAvatar: this.identity.userAvatar,
110837
+ role: this.identity.role,
110838
+ activeSlideIndex: this.activeSlide,
110839
+ cursorX: this.cursor.x,
110840
+ cursorY: this.cursor.y,
110841
+ selectedElementId: this.selection,
110842
+ lastUpdated: new Date().toISOString(),
110843
+ });
110844
+ }
110845
+ setCursor(x, y, activeSlideIndex = this.activeSlide) {
110846
+ this.cursor = { x, y };
110847
+ this.activeSlide = activeSlideIndex;
110848
+ this.publish();
110849
+ }
110850
+ setSelection(selectedElementId, activeSlideIndex = this.activeSlide) {
110851
+ this.selection = selectedElementId;
110852
+ this.activeSlide = activeSlideIndex;
110853
+ this.publish();
110854
+ }
110855
+ setActiveSlide(index) {
110856
+ this.activeSlide = Math.max(0, Math.floor(index));
110857
+ this.publish();
110858
+ }
110859
+ }
110860
+
110861
+ /** Attach viewer-owned observers without acquiring ownership of host resources. */
110862
+ function activateExternalSession(external, config, factories, deps) {
110863
+ const borrowed = borrowExternalCollaborationAwareness(external.awareness);
110864
+ const awareness = borrowed.awareness;
110865
+ deps.slideSync.bind({
110866
+ ydoc: external.doc,
110867
+ factories,
110868
+ onRemoteSlides: deps.onRemoteSlides,
110869
+ scheduleWriteBack: deps.scheduleWriteBack,
110870
+ readOnly: config.role === 'viewer',
110871
+ external: true,
110872
+ });
110873
+ const localPresence = new LocalPresencePublisher(awareness, {
110874
+ userName: config.userName,
110875
+ userColor: config.userColor ?? DEFAULT_CURSOR_COLOR,
110876
+ userAvatar: config.userAvatar,
110877
+ role: config.role,
110878
+ });
110879
+ localPresence.publish();
110880
+ awareness.on('change', deps.refreshPresence);
110881
+ awareness.on('update', deps.refreshPresence);
110882
+ let readiness;
110883
+ const dispose = () => {
110884
+ readiness?.();
110885
+ awareness.off('change', deps.refreshPresence);
110886
+ awareness.off('update', deps.refreshPresence);
110887
+ borrowed.dispose();
110888
+ };
110889
+ try {
110890
+ readiness = observeExternalCollaborationReadiness(external, {
110891
+ gate: deps.slideSync.gate,
110892
+ livePatcher: deps.livePatcher,
110893
+ factories,
110894
+ role: config.role,
110895
+ sessionIntent: config.sessionIntent,
110896
+ onStatus: deps.setStatus,
110897
+ onReadOnlyChange: deps.setReadOnly,
110898
+ adoptSlides: (slides) => deps.slideSync.adoptSlides(slides),
110899
+ onReady: ({ seedEmptyRoom }) => deps.slideSync.publishCurrent(seedEmptyRoom),
110900
+ onSuspend: () => {
110901
+ deps.slideSync.cancelPending();
110902
+ deps.cancelWriteBack();
110903
+ },
110904
+ });
110905
+ }
110906
+ catch (error) {
110907
+ dispose();
110908
+ throw error;
110909
+ }
110910
+ return {
110911
+ ydoc: external.doc,
110912
+ awareness,
110913
+ selfId: awareness.clientID,
110914
+ localPresence,
110915
+ readiness,
110916
+ dispose,
110917
+ };
110918
+ }
110919
+
110478
110920
  /**
110479
110921
  * collaboration-providers.ts: transport factories for the Angular
110480
110922
  * collaboration service.
@@ -110598,51 +111040,6 @@ function wireConnectionStatus(deps) {
110598
111040
  return { cancelConnectTimer };
110599
111041
  }
110600
111042
 
110601
- /**
110602
- * collaboration-local-presence.ts: publishes the local user's presence into a
110603
- * single awareness `presence` field.
110604
- *
110605
- * Cursor, selection, and active-slide updates all merge into one record so a
110606
- * later cursor move never clobbers the selection (or vice versa). The remote
110607
- * side reads this shape via the shared `derivePresenceList`.
110608
- */
110609
- class LocalPresencePublisher {
110610
- constructor(awareness, identity) {
110611
- this.awareness = awareness;
110612
- this.identity = identity;
110613
- this.activeSlide = 0;
110614
- this.cursor = { x: 0, y: 0 };
110615
- }
110616
- /** Re-emit the merged presence record (also used as a heartbeat). */
110617
- publish() {
110618
- this.awareness.setLocalStateField('presence', {
110619
- userName: this.identity.userName,
110620
- userColor: this.identity.userColor,
110621
- userAvatar: this.identity.userAvatar,
110622
- role: this.identity.role,
110623
- activeSlideIndex: this.activeSlide,
110624
- cursorX: this.cursor.x,
110625
- cursorY: this.cursor.y,
110626
- selectedElementId: this.selection,
110627
- lastUpdated: new Date().toISOString(),
110628
- });
110629
- }
110630
- setCursor(x, y, activeSlideIndex = this.activeSlide) {
110631
- this.cursor = { x, y };
110632
- this.activeSlide = activeSlideIndex;
110633
- this.publish();
110634
- }
110635
- setSelection(selectedElementId, activeSlideIndex = this.activeSlide) {
110636
- this.selection = selectedElementId;
110637
- this.activeSlide = activeSlideIndex;
110638
- this.publish();
110639
- }
110640
- setActiveSlide(index) {
110641
- this.activeSlide = Math.max(0, Math.floor(index));
110642
- this.publish();
110643
- }
110644
- }
110645
-
110646
111043
  /**
110647
111044
  * collaboration-session-setup.ts: build up and tear down a live collaboration
110648
111045
  * session for the Angular `CollaborationService`.
@@ -110658,10 +111055,12 @@ class LocalPresencePublisher {
110658
111055
  * connection-status + remote-slide changes, then open the first-write gate.
110659
111056
  */
110660
111057
  function activateSession(bundle, config, transport, deps) {
110661
- deps.livePatcher.configure(bundle.doc, bundle.factories);
111058
+ deps.livePatcher.configure(config.role === 'viewer' ? null : bundle.doc, config.role === 'viewer' ? null : bundle.factories);
111059
+ deps.setReadOnly(config.role === 'viewer');
110662
111060
  deps.slideSync.bind({
110663
111061
  ydoc: bundle.doc,
110664
111062
  factories: bundle.factories,
111063
+ readOnly: config.role === 'viewer',
110665
111064
  onRemoteSlides: deps.onRemoteSlides,
110666
111065
  scheduleWriteBack: deps.scheduleWriteBack,
110667
111066
  });
@@ -110691,33 +111090,46 @@ function activateSession(bundle, config, transport, deps) {
110691
111090
  const unobserve = observeYDocSlides(bundle.doc, (_events, transaction) => deps.slideSync.onRemoteChange(transaction));
110692
111091
  return {
110693
111092
  ydoc: bundle.doc,
110694
- provider: bundle.provider,
110695
111093
  awareness: bundle.awareness,
110696
- departure: bundle.departure,
110697
- factories: bundle.factories,
110698
111094
  selfId: bundle.awareness.clientID ?? -1,
110699
111095
  localPresence,
110700
- connection,
110701
- unobserve,
111096
+ dispose() {
111097
+ connection.cancelConnectTimer();
111098
+ unobserve();
111099
+ bundle.awareness.off?.('change', deps.refreshPresence);
111100
+ bundle.awareness.off?.('update', deps.refreshPresence);
111101
+ bundle.departure.announce();
111102
+ bundle.departure.dispose();
111103
+ clearLocalAwareness(bundle.awareness);
111104
+ bundle.provider.disconnect();
111105
+ bundle.provider.destroy();
111106
+ bundle.doc.destroy();
111107
+ },
110702
111108
  };
110703
111109
  }
110704
- /**
110705
- * Dispose a live session. Announces the departure synchronously first: the
110706
- * provider's own awareness removal is broadcast a microtask later and would be
110707
- * dropped when this runs from a document being destroyed, leaving a ghost
110708
- * collaborator until the 30s awareness timeout.
110709
- */
110710
- function teardownSession(session, refreshPresence) {
110711
- session.connection.cancelConnectTimer();
110712
- session.unobserve();
110713
- session.awareness.off?.('change', refreshPresence);
110714
- session.awareness.off?.('update', refreshPresence);
110715
- session.departure.announce();
110716
- session.departure.dispose();
110717
- clearLocalAwareness(session.awareness);
110718
- session.provider.disconnect();
110719
- session.provider.destroy();
110720
- session.ydoc.destroy();
111110
+
111111
+ /** Finish lazy imports only while the requesting connection still owns the service. */
111112
+ async function connectSession(config, transport, deps, isCurrent) {
111113
+ if (config.externalSession) {
111114
+ const Y = await import('yjs');
111115
+ if (!isCurrent()) {
111116
+ return null;
111117
+ }
111118
+ return activateExternalSession(config.externalSession, config, {
111119
+ createMap: () => new Y.Map(),
111120
+ createArray: () => new Y.Array(),
111121
+ createText: () => new Y.Text(),
111122
+ }, deps);
111123
+ }
111124
+ const bundle = transport === 'webrtc' ? await createWebrtcBundle(config) : await createWebsocketBundle(config);
111125
+ if (!isCurrent()) {
111126
+ bundle.departure.dispose();
111127
+ bundle.provider.disconnect();
111128
+ bundle.provider.destroy();
111129
+ bundle.doc.destroy();
111130
+ return null;
111131
+ }
111132
+ return activateSession(bundle, config, transport, deps);
110721
111133
  }
110722
111134
 
110723
111135
  /**
@@ -110743,11 +111155,13 @@ class SlideSyncEngine {
110743
111155
  this.#lastSynced = '';
110744
111156
  this.#applyingRemote = false;
110745
111157
  this.#pending = null;
111158
+ this.#current = [];
110746
111159
  }
110747
111160
  #binding;
110748
111161
  #lastSynced;
110749
111162
  #applyingRemote;
110750
111163
  #pending;
111164
+ #current;
110751
111165
  /** Attach the engine to a freshly connected session's doc. */
110752
111166
  bind(binding) {
110753
111167
  this.#binding = binding;
@@ -110759,6 +111173,7 @@ class SlideSyncEngine {
110759
111173
  this.#pending = null;
110760
111174
  this.#lastSynced = '';
110761
111175
  this.#applyingRemote = false;
111176
+ this.#current = [];
110762
111177
  }
110763
111178
  /**
110764
111179
  * Record the current local deck as the sync baseline so the first (unchanged)
@@ -110767,8 +111182,33 @@ class SlideSyncEngine {
110767
111182
  * overwrites the shared document before receiving it.
110768
111183
  */
110769
111184
  seedBaseline(slides) {
111185
+ this.#current = slides;
110770
111186
  this.#lastSynced = JSON.stringify(slides);
110771
111187
  }
111188
+ /** A ready empty create room must publish even when startup seeded an echo baseline. */
111189
+ publishCurrent(seedEmptyRoom) {
111190
+ if (seedEmptyRoom) {
111191
+ this.#lastSynced = '';
111192
+ }
111193
+ this.broadcast(this.#current);
111194
+ }
111195
+ /** Drop a queued write when host readiness is revoked; keep the displayed deck. */
111196
+ cancelPending() {
111197
+ this.#pending = null;
111198
+ }
111199
+ /** Apply slides selected by the shared readiness policy, including an empty room. */
111200
+ adoptSlides(slides) {
111201
+ this.#pending = null;
111202
+ this.seedBaseline(slides);
111203
+ this.#applyingRemote = true;
111204
+ try {
111205
+ this.#binding?.onRemoteSlides?.(slides);
111206
+ }
111207
+ finally {
111208
+ this.#applyingRemote = false;
111209
+ }
111210
+ this.#binding?.scheduleWriteBack();
111211
+ }
110772
111212
  /**
110773
111213
  * Open the first-write gate on the provider's initial-sync confirmation.
110774
111214
  * y-websocket emits 'sync' with a boolean; y-webrtc emits 'synced' with an
@@ -110799,8 +111239,11 @@ class SlideSyncEngine {
110799
111239
  * skipped, and while the gate is shut the deck is held as pending.
110800
111240
  */
110801
111241
  broadcast(slides) {
111242
+ if (!this.#applyingRemote) {
111243
+ this.#current = slides;
111244
+ }
110802
111245
  const b = this.#binding;
110803
- if (!b || this.#applyingRemote || slides.length === 0) {
111246
+ if (!b || b.readOnly || this.#applyingRemote || (slides.length === 0 && !b.external)) {
110804
111247
  return;
110805
111248
  }
110806
111249
  if (!this.gate.isOpen()) {
@@ -110827,11 +111270,7 @@ class SlideSyncEngine {
110827
111270
  }
110828
111271
  // Suppress the echo: record what we just applied so the subsequent local
110829
111272
  // broadcast (driven by the editor signal) is a no-op.
110830
- this.#lastSynced = JSON.stringify(remote);
110831
- this.#applyingRemote = true;
110832
- b.onRemoteSlides?.(remote);
110833
- this.#applyingRemote = false;
110834
- b.scheduleWriteBack();
111273
+ this.adoptSlides(remote);
110835
111274
  }
110836
111275
  /**
110837
111276
  * Re-adopt the shared document's slides after a local content load committed
@@ -110857,10 +111296,7 @@ class SlideSyncEngine {
110857
111296
  if (!shouldRoomSlidesReplaceLoad(origin, docSlides.length)) {
110858
111297
  return false;
110859
111298
  }
110860
- this.#lastSynced = JSON.stringify(docSlides);
110861
- this.#applyingRemote = true;
110862
- b.onRemoteSlides?.(docSlides);
110863
- this.#applyingRemote = false;
111299
+ this.adoptSlides(docSlides);
110864
111300
  return true;
110865
111301
  }
110866
111302
  /**
@@ -110994,7 +111430,6 @@ function showsTemplateAffordance(element, editTemplateMode) {
110994
111430
  * into `.pptx` bytes for `config.onWriteBack`. Kept out of the service so the
110995
111431
  * service stays within the repo's per-file size budget.
110996
111432
  */
110997
- const WRITE_BACK_DEBOUNCE_MS = 5_000;
110998
111433
  /**
110999
111434
  * Serialize the current Y.Doc slide state (with the separated master/layout
111000
111435
  * template elements merged back) into `.pptx` bytes. `sourceBytes` seeds the
@@ -111005,9 +111440,12 @@ const WRITE_BACK_DEBOUNCE_MS = 5_000;
111005
111440
  * Save/Export path (`buildDeckSaveOptions`). Without it the write-back
111006
111441
  * dropped every session-level edit outside `slides`.
111007
111442
  */
111008
- async function serializeWriteBack(ydoc, sourceBytes, templateElements, saveOptions) {
111443
+ async function serializeWriteBack(ydoc, sourceBytes, templateElements, saveOptions, isCurrent = () => true) {
111009
111444
  const handler = new PptxHandler();
111010
111445
  await handler.load(sourceBytes.buffer);
111446
+ if (!isCurrent()) {
111447
+ throw new Error('Collaboration session changed during serialization');
111448
+ }
111011
111449
  const slides = buildSaveSlides(readSlidesFromYDoc(ydoc), templateElements);
111012
111450
  return handler.save(slides, saveOptions);
111013
111451
  }
@@ -111018,48 +111456,46 @@ async function serializeWriteBack(ydoc, sourceBytes, templateElements, saveOptio
111018
111456
  */
111019
111457
  class WriteBackScheduler {
111020
111458
  constructor() {
111021
- this.timer = null;
111459
+ this.ydoc = null;
111460
+ this.serialize = null;
111461
+ this.scheduler = createWriteBackScheduler({
111462
+ getYDoc: () => this.ydoc,
111463
+ serialize: (isCurrent) => this.serialize?.(isCurrent) ?? null,
111464
+ });
111022
111465
  }
111023
111466
  schedule(config, ydoc, getSourceBytes, getTemplateElements, getSaveOptions) {
111024
111467
  if (!config?.onWriteBack || config.role !== 'owner' || !ydoc) {
111025
111468
  return;
111026
111469
  }
111027
111470
  this.cancel();
111028
- const ms = config.writeBackDebounceMs ?? WRITE_BACK_DEBOUNCE_MS;
111029
- this.timer = setTimeout(() => {
111030
- this.timer = null;
111471
+ this.ydoc = ydoc;
111472
+ this.serialize = async (isCurrent) => {
111031
111473
  const bytes = getSourceBytes?.();
111032
- if (!bytes || !config.onWriteBack) {
111033
- return;
111474
+ if (!bytes) {
111475
+ return null;
111034
111476
  }
111035
- void serializeWriteBack(ydoc, bytes, getTemplateElements?.() ?? {}, getSaveOptions?.())
111036
- .then((out) => config.onWriteBack?.(out))
111037
- .catch(() => {
111038
- /* non-fatal */
111039
- });
111040
- }, ms);
111477
+ return serializeWriteBack(ydoc, bytes, getTemplateElements?.() ?? {}, getSaveOptions?.(), isCurrent);
111478
+ };
111479
+ this.scheduler.schedule(config);
111041
111480
  }
111042
111481
  cancel() {
111043
- if (this.timer !== null) {
111044
- clearTimeout(this.timer);
111045
- this.timer = null;
111046
- }
111482
+ this.scheduler.cancel();
111483
+ this.serialize = null;
111484
+ this.ydoc = null;
111047
111485
  }
111048
111486
  }
111049
111487
 
111050
111488
  /**
111051
111489
  * CollaborationService: Angular real-time collaboration (Yjs) service.
111052
111490
  *
111053
- * Owns the provider lifecycle and the reactive collaboration state consumed by
111054
- * the viewer component:
111055
- * - Transport is `y-websocket` (default) or serverless `y-webrtc`
111056
- * (`config.transport === 'webrtc'`), created via `./collaboration-providers`.
111057
- * - Local edits sync via the granular `reconcileSlidesInYDoc` (only changed
111058
- * slides/elements/fields mutate) in a transaction tagged `LOCAL_SYNC_ORIGIN`;
111059
- * the remote observer skips its own local-origin writes.
111060
- * - Websocket connections fail fast on mixed content and time out to `'error'`
111061
- * after `CONNECTION_TIMEOUT_MS`; `retry()` reconnects with the last config.
111062
- * - Elected-writer write-back (role === 'owner') via `WriteBackScheduler`.
111491
+ * Owns viewer state and built-in transports, or borrows a host-owned session:
111492
+ * - Transport is y-websocket (default) or serverless y-webrtc, created through
111493
+ * collaboration-session-connect. External resources are never destroyed.
111494
+ * - Local edits reconcile changed slides/elements/fields in a transaction
111495
+ * tagged LOCAL_SYNC_ORIGIN; observers skip their own local writes.
111496
+ * - Websocket connections fail fast on mixed content and time out to error;
111497
+ * retry() reconnects with the last config.
111498
+ * - Elected-writer write-back (role owner) uses the shared WriteBackScheduler.
111063
111499
  *
111064
111500
  * Provide at the component level: `@Component({ providers: [CollaborationService] })`.
111065
111501
  */
@@ -111077,36 +111513,24 @@ class CollaborationService {
111077
111513
  /** Role of the local user in the active session, or undefined when idle. */
111078
111514
  this.activeRole = signal(undefined, /* @ts-ignore */
111079
111515
  ...(ngDevMode ? [{ debugName: "activeRole" }] : /* istanbul ignore next */ []));
111080
- this.presence = signal([], /* @ts-ignore */
111081
- ...(ngDevMode ? [{ debugName: "presence" }] : /* istanbul ignore next */ []));
111082
- this.cursors = computed(() => presenceToCursors(this.presence()), /* @ts-ignore */
111083
- ...(ngDevMode ? [{ debugName: "cursors" }] : /* istanbul ignore next */ []));
111516
+ /** Active viewer role or host readiness can temporarily block editing. */
111517
+ this.readOnly = signal(false, /* @ts-ignore */
111518
+ ...(ngDevMode ? [{ debugName: "readOnly" }] : /* istanbul ignore next */ []));
111519
+ this.presenceState = new CollaborationPresenceState();
111520
+ this.presence = this.presenceState.presence;
111521
+ this.cursors = this.presenceState.cursors;
111084
111522
  this.connectedCount = computed(() => this.presence().length + (this.active() ? 1 : 0), /* @ts-ignore */
111085
111523
  ...(ngDevMode ? [{ debugName: "connectedCount" }] : /* istanbul ignore next */ []));
111086
- /** The client id the local user is currently following (null when free). */
111087
- this.followedClientId = signal(null, /* @ts-ignore */
111088
- ...(ngDevMode ? [{ debugName: "followedClientId" }] : /* istanbul ignore next */ []));
111089
- /** Active-slide index of the followed peer, or null when not following. */
111090
- this.followedSlideIndex = computed(() => {
111091
- const id = this.followedClientId();
111092
- if (id === null) {
111093
- return null;
111094
- }
111095
- return this.presence().find((p) => p.clientId === id)?.activeSlideIndex ?? null;
111096
- }, /* @ts-ignore */
111097
- ...(ngDevMode ? [{ debugName: "followedSlideIndex" }] : /* istanbul ignore next */ []));
111098
- /** Active-slide index of the first `owner` peer (the broadcaster), or null. */
111099
- this.broadcasterSlideIndex = computed(() => this.presence().find((p) => p.role === 'owner')?.activeSlideIndex ?? null, /* @ts-ignore */
111100
- ...(ngDevMode ? [{ debugName: "broadcasterSlideIndex" }] : /* istanbul ignore next */ []));
111524
+ this.followedClientId = this.presenceState.followedClientId;
111525
+ this.followedSlideIndex = this.presenceState.followedSlideIndex;
111526
+ this.broadcasterSlideIndex = this.presenceState.broadcasterSlideIndex;
111101
111527
  /**
111102
111528
  * Interim ("live preview") Y.Doc write channel: publishes in-flight inline
111103
111529
  * editor text that has not reached the slides state yet, so peers see typing
111104
111530
  * as it happens instead of on commit. Dormant outside a session.
111105
111531
  */
111106
111532
  this.livePatcher = createCollaborationLivePatcher();
111107
- // The live session's transport objects + wiring handles, owned as one atomic
111108
- // unit: null when disconnected, assigned by connect(), disposed by
111109
- // disconnect(). See collaboration-session-setup.ts.
111533
+ // One handle owns viewer wiring, but external transport resources stay borrowed.
111110
111534
  this.session = null;
111111
111535
  this.writeBack = new WriteBackScheduler();
111112
111536
  /** Granular local<->doc slide sync (gate + echo dedupe + broadcast/adopt). */
@@ -111121,38 +111545,20 @@ class CollaborationService {
111121
111545
  this.lastConfig = null;
111122
111546
  this.lastOptions = {};
111123
111547
  /**
111124
- * Reentrancy token for {@link connect}: bumped by every connect() and
111125
- * disconnect(). A connect() whose token no longer matches after an await was
111126
- * superseded, so it tears down whatever it just created and leaves the
111127
- * service state to the newer call (a second provider join on the same room
111128
- * would otherwise throw inside Yjs and kill the surviving session).
111548
+ * Reentrancy token for connect: every connect/disconnect invalidates pending
111549
+ * async setup. A superseded setup releases only what it created and never
111550
+ * tears down the newer session (a second provider join can otherwise throw).
111129
111551
  */
111130
111552
  this.connectToken = 0;
111131
- /** Memoises the awareness -> presence projection so idle heartbeats are dropped. */
111132
- this.projector = createPresenceProjector();
111133
111553
  this.refreshPresence = () => {
111134
- const s = this.session;
111135
- if (!s) {
111136
- // Leaving the room clears the memo, so a re-join is never mistaken for
111137
- // "nothing changed" against the previous session's peers.
111138
- this.projector.reset();
111139
- this.presence.set([]);
111140
- return;
111141
- }
111142
- // A signal notifies on every `set` with a fresh array, and awareness fires
111143
- // on each peer heartbeat as well as on our own writes, so this used to
111144
- // re-run the collaboration overlay's computeds on a timer. The shared
111145
- // projector reports whether anything visible actually moved (issue #145).
111146
- const { list, changed } = this.projector.project(s.awareness.getStates(), s.selfId, this.canvasWidth, this.canvasHeight);
111147
- if (changed) {
111148
- this.presence.set(list);
111149
- }
111554
+ this.presenceState.refresh(this.session, this.canvasWidth, this.canvasHeight);
111150
111555
  };
111151
111556
  // Service destruction is not the only way a session ends: a tab close, a
111152
111557
  // navigation, or an embedding page detaching the viewer's iframe destroys
111153
111558
  // the document without running Angular teardown, leaving a ghost peer in
111154
111559
  // everyone else's presence list. Leave the room from `pagehide` too.
111155
111560
  const disposeTeardown = registerCollaborationTeardown({
111561
+ leaveOnBeforeUnload: () => !this.currentConfig?.externalSession,
111156
111562
  leave: () => this.disconnect(),
111157
111563
  rejoin: () => void this.retry(),
111158
111564
  });
@@ -111173,14 +111579,14 @@ class CollaborationService {
111173
111579
  this.status.set('error');
111174
111580
  return;
111175
111581
  }
111176
- // Falls back from a blank serverUrl the same way Vue's session layer
111177
- // already does, so a bare CollaborationConfig behaves identically
111178
- // regardless of which binding's session layer receives it directly (not
111179
- // just via the Share/Broadcast dialogs, which already pre-resolve it).
111582
+ // Blank server URLs select webrtc consistently with Share/Broadcast
111583
+ // dialogs and the other bindings; external sessions never open it.
111180
111584
  const transport = config.transport ?? resolveTransportForServerUrl(config.serverUrl);
111181
111585
  // Fail fast on mixed content (websocket only): an https page cannot open a
111182
111586
  // ws:// socket, so surface the error rather than hanging until the timeout.
111183
- if (transport !== 'webrtc' && isMixedContentBlocked(config.serverUrl)) {
111587
+ if (!config.externalSession &&
111588
+ transport !== 'webrtc' &&
111589
+ isMixedContentBlocked(config.serverUrl)) {
111184
111590
  this.status.set('error');
111185
111591
  return;
111186
111592
  }
@@ -111192,27 +111598,17 @@ class CollaborationService {
111192
111598
  this.getSaveOptions = options.getSaveOptions ?? null;
111193
111599
  this.currentConfig = config;
111194
111600
  this.activeRole.set(config.role);
111601
+ this.readOnly.set(Boolean(config.externalSession) || config.role === 'viewer');
111195
111602
  this.status.set('connecting');
111196
111603
  try {
111197
- const bundle = transport === 'webrtc'
111198
- ? await createWebrtcBundle(config)
111199
- : await createWebsocketBundle(config);
111200
- if (token !== this.connectToken) {
111201
- // Superseded by a newer connect() or a disconnect() while awaiting
111202
- // the transport: destroy the just-created bundle and bail without
111203
- // touching the (newer call's) service state.
111204
- bundle.departure.dispose();
111205
- bundle.provider.disconnect();
111206
- bundle.provider.destroy();
111207
- bundle.doc.destroy();
111208
- return;
111209
- }
111210
- this.session = activateSession(bundle, config, transport, {
111604
+ const session = await connectSession(config, transport, {
111211
111605
  slideSync: this.slideSync,
111212
111606
  livePatcher: this.livePatcher,
111213
111607
  onRemoteSlides: this.onRemoteSlides,
111214
111608
  refreshPresence: this.refreshPresence,
111215
111609
  scheduleWriteBack: () => this.scheduleWriteBack(),
111610
+ cancelWriteBack: () => this.writeBack.cancel(),
111611
+ setReadOnly: (readOnly) => this.readOnly.set(readOnly),
111216
111612
  setStatus: (status) => this.status.set(status),
111217
111613
  getStatus: () => this.status(),
111218
111614
  isActive: () => this.active(),
@@ -111220,9 +111616,19 @@ class CollaborationService {
111220
111616
  this.disconnect();
111221
111617
  this.status.set('error');
111222
111618
  },
111223
- });
111619
+ }, () => token === this.connectToken);
111620
+ if (!session || token !== this.connectToken) {
111621
+ session?.dispose();
111622
+ return;
111623
+ }
111624
+ this.session = session;
111224
111625
  this.active.set(true);
111225
111626
  this.refreshPresence();
111627
+ // Readiness can seed/adopt synchronously before this.session exists.
111628
+ // Now an owner's startup snapshot has its live document available.
111629
+ if (session.readiness) {
111630
+ this.scheduleWriteBack();
111631
+ }
111226
111632
  }
111227
111633
  catch {
111228
111634
  if (token !== this.connectToken) {
@@ -111245,7 +111651,7 @@ class CollaborationService {
111245
111651
  this.slideSync.reset();
111246
111652
  this.writeBack.cancel();
111247
111653
  if (this.session) {
111248
- teardownSession(this.session, this.refreshPresence);
111654
+ this.session.dispose();
111249
111655
  this.session = null;
111250
111656
  }
111251
111657
  this.livePatcher.configure(null, null);
@@ -111254,8 +111660,8 @@ class CollaborationService {
111254
111660
  this.status.set('disconnected');
111255
111661
  this.active.set(false);
111256
111662
  this.activeRole.set(undefined);
111257
- this.presence.set([]);
111258
- this.followedClientId.set(null);
111663
+ this.readOnly.set(false);
111664
+ this.presenceState.reset();
111259
111665
  }
111260
111666
  /**
111261
111667
  * Record the current local deck as the sync baseline so the first (unchanged)
@@ -111271,6 +111677,9 @@ class CollaborationService {
111271
111677
  * Returns true when the room's slides were adopted over the loaded deck.
111272
111678
  */
111273
111679
  adoptDocSlidesAfterLoad(origin = 'user') {
111680
+ if (this.session?.readiness) {
111681
+ return this.session.readiness.handleLoad(origin);
111682
+ }
111274
111683
  return this.connected() ? this.slideSync.adoptDocAfterLoad(origin) : false;
111275
111684
  }
111276
111685
  /**
@@ -111291,11 +111700,14 @@ class CollaborationService {
111291
111700
  setActiveSlide(index) {
111292
111701
  this.session?.localPresence.setActiveSlide(index);
111293
111702
  }
111294
- /** Follow the given peer's active slide, or `null` to stop following. */
111703
+ /** Follow the given peer's active slide, or null to stop following. */
111295
111704
  followUser(clientId) {
111296
111705
  this.followedClientId.set(clientId);
111297
111706
  }
111298
111707
  scheduleWriteBack() {
111708
+ if (this.currentConfig?.externalSession && !this.slideSync.gate.isOpen()) {
111709
+ return;
111710
+ }
111299
111711
  this.writeBack.schedule(this.currentConfig, this.session?.ydoc ?? null, this.getSourceBytes, this.getTemplateElements, this.getSaveOptions);
111300
111712
  }
111301
111713
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: CollaborationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -130029,7 +130441,7 @@ class SlideCanvasComponent {
130029
130441
  // SmartArt), so template (master/layout) element commits can resolve
130030
130442
  // their owning slide; template elements are absent from slides[].elements.
130031
130443
  { provide: SLIDE_CONTEXT, useExisting: forwardRef(() => SlideCanvasComponent) },
130032
- ], viewQueries: [{ propertyName: "textEditor", first: true, predicate: ["textEditor"], descendants: true, isSignal: true }, { propertyName: "listEditor", first: true, predicate: InlineListEditorComponent, descendants: true, isSignal: true }, { propertyName: "stageRef", first: true, predicate: ["stage"], descendants: true, isSignal: true }, { propertyName: "viewportRef", first: true, predicate: ["viewport"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n\t#viewport\n\tclass=\"pptx-ng-canvas-viewport\"\n\t[attr.data-pptx-viewport]=\"interactive() ? '' : null\"\n\t(pointerdown)=\"onViewportPointerDown($event)\"\n>\n\t<div class=\"pptx-ng-canvas-wrapper\" [ngStyle]=\"wrapperStyle()\">\n\t\t<div\n\t\t\t#stage\n\t\t\tclass=\"pptx-ng-canvas-stage\"\n\t\t\t[class.is-editable]=\"editable()\"\n\t\t\trole=\"region\"\n\t\t\t[attr.aria-roledescription]=\"interactive() || presenting() ? 'slide' : null\"\n\t\t\t[attr.data-pptx-ai-active]=\"aiActive() ? 'true' : null\"\n\t\t\t[ngStyle]=\"stageStyle()\"\n\t\t\t(pointerdown)=\"onStagePointerDown($event)\"\n\t\t\t(contextmenu)=\"onContextMenu($event)\"\n\t\t\t(dblclick)=\"onDblClick($event)\"\n\t\t>\n\t\t\t<!--\n\t\t\t\tTemplate layer: inherited master/layout elements, rendered BEHIND\n\t\t\t\tthe slide's own elements (lower z-index). Interactive + given the\n\t\t\t\tamber editable affordance only while editTemplateMode is on; when\n\t\t\t\toff they render inertly with no affordance, exactly as core\n\t\t\t\tdelivered them. `marked` keeps the data-pptx-element contract on\n\t\t\t\tthem even while locked: the marker means \"rendered slide element\",\n\t\t\t\tnot \"editable right now\".\n\t\t\t-->\n\t\t\t@for (element of visibleTemplateElements(); track element.id; let i = $index) {\n\t\t\t\t<pptx-element-renderer\n\t\t\t\t\t[element]=\"element\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zIndex]=\"i\"\n\t\t\t\t\t[obstacles]=\"connectorObstacles()\"\n\t\t\t\t\t[canvasWidth]=\"canvasSize().width\"\n\t\t\t\t\t[canvasHeight]=\"canvasSize().height\"\n\t\t\t\t\t[interactive]=\"interactive() && editTemplateMode()\"\n\t\t\t\t\t[marked]=\"interactive()\"\n\t\t\t\t\t[exposeElementId]=\"exposeElementIds()\"\n\t\t\t\t\t[editable]=\"editable() && editTemplateMode()\"\n\t\t\t\t\t[fieldContext]=\"fieldContext()\"\n\t\t\t\t\t[slideElements]=\"allElements()\"\n\t\t\t\t\t[editTemplateMode]=\"editTemplateMode()\"\n\t\t\t\t\t[editingElementId]=\"editingId()\"\n\t\t\t\t\t(cellCommit)=\"cellCommit.emit($event)\"\n\t\t\t\t\t(tableChange)=\"tableChange.emit($event)\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@for (element of elements(); track element.id; let i = $index) {\n\t\t\t\t<pptx-element-renderer\n\t\t\t\t\t[element]=\"element\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zIndex]=\"visibleTemplateElements().length + i\"\n\t\t\t\t\t[obstacles]=\"connectorObstacles()\"\n\t\t\t\t\t[canvasWidth]=\"canvasSize().width\"\n\t\t\t\t\t[canvasHeight]=\"canvasSize().height\"\n\t\t\t\t\t[interactive]=\"interactive()\"\n\t\t\t\t\t[exposeElementId]=\"exposeElementIds()\"\n\t\t\t\t\t[presenting]=\"presenting()\"\n\t\t\t\t\t[editable]=\"editable()\"\n\t\t\t\t\t[fieldContext]=\"fieldContext()\"\n\t\t\t\t\t[slideElements]=\"allElements()\"\n\t\t\t\t\t[editTemplateMode]=\"false\"\n\t\t\t\t\t[editingElementId]=\"editingId()\"\n\t\t\t\t\t(cellCommit)=\"cellCommit.emit($event)\"\n\t\t\t\t\t(tableChange)=\"tableChange.emit($event)\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t<pptx-activex-controls-overlay\n\t\t\t\t[controls]=\"slide()?.activeXControls ?? []\"\n\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t/>\n\t\t\t@if (aiHighlights().length > 0 || aiActive()) {\n\t\t\t\t<pptx-ai-focus-highlight-overlay\n\t\t\t\t\t[highlights]=\"aiHighlights()\"\n\t\t\t\t\t[elements]=\"elements()\"\n\t\t\t\t\t[activeSlideIndex]=\"aiActiveSlideIndex()\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@if (aiChangeBatch(); as batch) {\n\t\t\t\t<pptx-ai-change-overlay [batch]=\"batch\" [activeSlideIndex]=\"aiActiveSlideIndex()\" />\n\t\t\t}\n\t\t\t@for (box of selectionBoxes(); track box.id) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-selection\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"box.x\"\n\t\t\t\t\t[style.top.px]=\"box.y\"\n\t\t\t\t\t[style.width.px]=\"box.width\"\n\t\t\t\t\t[style.height.px]=\"box.height\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (h of handleBoxes(); track h.handle) {\n\t\t\t\t@let controlArtwork = selectionControlArtwork(h, h.handle);\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"resize\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.resize' | translate: { handle: h.handle }\"\n\t\t\t\t\t[ngStyle]=\"controlArtwork.frame\"\n\t\t\t\t\t[style.cursor]=\"h.cursor\"\n\t\t\t\t\t[style.--pptx-selection-width.px]=\"singleSelected()?.width\"\n\t\t\t\t\t[style.--pptx-selection-height.px]=\"singleSelected()?.height\"\n\t\t\t\t\t(pointerdown)=\"onHandlePointerDown($event, h.handle)\"\n\t\t\t\t\t(keydown)=\"onResizeHandleKeydown($event, h.handle)\"\n\t\t\t\t>\n\t\t\t\t\t<span data-pptx-handle-hit [ngStyle]=\"resizeHitAreaStyle(h.handle)\"></span>\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"pptx-ng-control-artwork\"\n\t\t\t\t\t\tdata-pptx-handle-artwork\n\t\t\t\t\t\t[ngStyle]=\"controlArtwork.artwork\"\n\t\t\t\t\t></span>\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t@if (marqueeRect(); as mr) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-marquee\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"mr.x\"\n\t\t\t\t\t[style.top.px]=\"mr.y\"\n\t\t\t\t\t[style.width.px]=\"mr.width\"\n\t\t\t\t\t[style.height.px]=\"mr.height\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (g of snapGuides(); track $index) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-snap-guide\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos : g.start\"\n\t\t\t\t\t[style.top.px]=\"g.axis === 'x' ? g.start : g.pos\"\n\t\t\t\t\t[style.width.px]=\"g.axis === 'x' ? 0 : g.end - g.start\"\n\t\t\t\t\t[style.height.px]=\"g.axis === 'x' ? g.end - g.start : 0\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@if (rotateHandle(); as rh) {\n\t\t\t\t@let rotateArtwork = selectionControlArtwork(rh);\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-rotate-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.z-index]=\"editingBox() ? 10003 : null\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"rotate\"\n\t\t\t\t\t[pptxRotateHandleFor]=\"singleSelected()?.id\"\n\t\t\t\t\t[pptxRotateHandleStage]=\"stage\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.rotate' | translate\"\n\t\t\t\t\t[ngStyle]=\"rotateArtwork.frame\"\n\t\t\t\t\t(pointerdown)=\"onRotatePointerDown($event)\"\n\t\t\t\t\t(keydown)=\"onRotateHandleKeydown($event)\"\n\t\t\t\t>\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"pptx-ng-control-artwork\"\n\t\t\t\t\t\tdata-pptx-handle-artwork\n\t\t\t\t\t\t[ngStyle]=\"rotateArtwork.artwork\"\n\t\t\t\t\t></span>\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tShape-adjustment affordances (amber diamonds), ONE per `a:avLst`\n\t\t\t\tguide. Existence, position and cursor all come from the SHARED\n\t\t\t\t`getShapeAdjustmentHandleDescriptors`, so they appear only for a\n\t\t\t\tgeometry with an adjustable parameter, sit where the other four\n\t\t\t\tbindings put them, and are absent for a shape locked with\n\t\t\t\t`noAdjustHandles`. Dragging one writes `shapeAdjustments`; it does\n\t\t\t\tNOT resize the element.\n\t\t\t-->\n\t\t\t@for (ah of adjustHandles(); track ah.key) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-adjust-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"adjust\"\n\t\t\t\t\t[attr.data-pptx-adjust-key]=\"ah.key\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.adjust' | translate\"\n\t\t\t\t\t[style.left.px]=\"ah.left\"\n\t\t\t\t\t[style.top.px]=\"ah.top\"\n\t\t\t\t\t[style.width.px]=\"ah.size\"\n\t\t\t\t\t[style.height.px]=\"ah.size\"\n\t\t\t\t\t[style.cursor]=\"ah.cursor\"\n\t\t\t\t\t(pointerdown)=\"onAdjustPointerDown($event, ah)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tPowerPoint's floating \"Chart Elements\"/\"Chart Styles\"/\"Chart\n\t\t\t\tFilters\" quick-action icons, shown just outside the chart's\n\t\t\t\ttop-right corner when it is the single selected element.\n\t\t\t-->\n\t\t\t@if (editable() && quickActionsChartElement(); as chartEl) {\n\t\t\t\t<pptx-chart-quick-actions-overlay\n\t\t\t\t\t[element]=\"chartEl\"\n\t\t\t\t\t[canEdit]=\"editable()\"\n\t\t\t\t\t[zoom]=\"effectiveScalePublic()\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tConnector endpoint authoring: drag an end onto a shape's connection\n\t\t\t\tpoint to bind it (`a:stCxn` / `a:endCxn`), or off one to detach.\n\t\t\t\tCandidate sites are revealed only while an end is in flight so they\n\t\t\t\tnever obscure the deck at rest.\n\t\t\t-->\n\t\t\t@for (site of connectorSiteCandidates(); track site.key) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-connection-site\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[class.is-snapped]=\"site.snapped\"\n\t\t\t\t\tdata-pptx-connection-site\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[style.left.px]=\"site.left\"\n\t\t\t\t\t[style.top.px]=\"site.top\"\n\t\t\t\t\t[style.width.px]=\"site.size\"\n\t\t\t\t\t[style.height.px]=\"site.size\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (ep of connectorEndpoints(); track ep.kind) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-connector-endpoint\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[class.is-attached]=\"ep.attached\"\n\t\t\t\t\t[attr.data-pptx-connector-endpoint]=\"ep.kind\"\n\t\t\t\t\t[attr.data-pptx-connector-attached]=\"ep.attached\"\n\t\t\t\t\t[attr.aria-label]=\"\n\t\t\t\t\t\t(ep.kind === 'start'\n\t\t\t\t\t\t\t? 'pptx.canvas.connectorEndpointStart'\n\t\t\t\t\t\t\t: 'pptx.canvas.connectorEndpointEnd'\n\t\t\t\t\t\t) | translate\n\t\t\t\t\t\"\n\t\t\t\t\t[style.left.px]=\"ep.left\"\n\t\t\t\t\t[style.top.px]=\"ep.top\"\n\t\t\t\t\t[style.width.px]=\"ep.size\"\n\t\t\t\t\t[style.height.px]=\"ep.size\"\n\t\t\t\t\t(pointerdown)=\"onConnectorEndpointPointerDown($event, ep.kind)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t\t@if (editingBox(); as eb) {\n\t\t\t\t@if (inlineListSeed(); as seed) {\n\t\t\t\t\t@for (session of [seed]; track session) {\n\t\t\t\t\t\t<pptx-inline-list-editor\n\t\t\t\t\t\t\t[element]=\"eb.element\"\n\t\t\t\t\t\t\t[seed]=\"session\"\n\t\t\t\t\t\t\t[activationSelection]=\"listActivationSelection()\"\n\t\t\t\t\t\t\t[spellCheck]=\"spellCheck()\"\n\t\t\t\t\t\t\t(textInput)=\"textInput.emit($event)\"\n\t\t\t\t\t\t\t(textCommit)=\"textCommit.emit($event)\"\n\t\t\t\t\t\t\t(textCancel)=\"textCancel.emit()\"\n\t\t\t\t\t\t\t(textFormat)=\"textFormat.emit($event)\"\n\t\t\t\t\t\t\t(listSession)=\"listSession.emit($event)\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t} @else {\n\t\t\t\t\t<textarea\n\t\t\t\t\t\t#textEditor\n\t\t\t\t\t\tdata-inline-editor\n\t\t\t\t\t\tclass=\"pptx-ng-text-editor\"\n\t\t\t\t\t\taria-label=\"Edit slide text\"\n\t\t\t\t\t\t[spellcheck]=\"spellCheck()\"\n\t\t\t\t\t\t[style.left.px]=\"eb.x\"\n\t\t\t\t\t\t[style.top.px]=\"eb.y\"\n\t\t\t\t\t\t[style.width.px]=\"eb.width\"\n\t\t\t\t\t\t[style.height.px]=\"eb.height\"\n\t\t\t\t\t\t(pointerdown)=\"$event.stopPropagation()\"\n\t\t\t\t\t\t(input)=\"onTextInput($event, eb.id)\"\n\t\t\t\t\t\t(blur)=\"commitText($event, eb.id)\"\n\t\t\t\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t\t\t\t></textarea>\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tView overlays: editor aids only, never on thumbnails/preview/presentation.\n\t\t\t\tAll are pointer-events:none so they never break selection/drag.\n\t\t\t\tNone carry data-pptx-element / aria-roledescription / data-pptx-viewport.\n\t\t\t-->\n\t\t\t@if (interactive() && showGrid()) {\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-overlay-grid\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t>\n\t\t\t\t\t<defs>\n\t\t\t\t\t\t<pattern\n\t\t\t\t\t\t\t[attr.id]=\"gridPatternId\"\n\t\t\t\t\t\t\t[attr.width]=\"gridSpacingPx()\"\n\t\t\t\t\t\t\t[attr.height]=\"gridSpacingPx()\"\n\t\t\t\t\t\t\tpatternUnits=\"userSpaceOnUse\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t[attr.cx]=\"gridSpacingPx() / 2\"\n\t\t\t\t\t\t\t\t[attr.cy]=\"gridSpacingPx() / 2\"\n\t\t\t\t\t\t\t\tr=\"0.6\"\n\t\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.55)\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</pattern>\n\t\t\t\t\t</defs>\n\t\t\t\t\t<rect\n\t\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t\t\t[attr.fill]=\"'url(#' + gridPatternId + ')'\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t@if (interactive() && showGuides()) {\n\t\t\t\t<!--\n\t\t\t\t\tCenter crosshair: one horizontal line and one vertical line\n\t\t\t\t\tthrough the midpoint of the slide. Static; draggable guides\n\t\t\t\t\tare a follow-up.\n\t\t\t\t-->\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-overlay-guides\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t>\n\t\t\t\t\t<!-- Horizontal center guide -->\n\t\t\t\t\t<line\n\t\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t\t[attr.y1]=\"canvasSize().height / 2\"\n\t\t\t\t\t\t[attr.x2]=\"canvasSize().width\"\n\t\t\t\t\t\t[attr.y2]=\"canvasSize().height / 2\"\n\t\t\t\t\t\tstroke=\"rgba(99,102,241,0.7)\"\n\t\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t\t\tstroke-dasharray=\"6 3\"\n\t\t\t\t\t/>\n\t\t\t\t\t<!-- Vertical center guide -->\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"canvasSize().width / 2\"\n\t\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t\t[attr.x2]=\"canvasSize().width / 2\"\n\t\t\t\t\t\t[attr.y2]=\"canvasSize().height\"\n\t\t\t\t\t\tstroke=\"rgba(99,102,241,0.7)\"\n\t\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t\t\tstroke-dasharray=\"6 3\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tUser-created ruler guides.\n\t\t\t\tEach guide has a non-interactive line body and an interactive\n\t\t\t\tdrag handle. Double-click the handle to delete the guide.\n\n\t\t\t\tDeliberately OUTSIDE the showGuides() gate above (which owns the\n\t\t\t\tstatic centre crosshair): a guide the user just dragged off a ruler\n\t\t\t\tstrip must be visible immediately, as it is in Vue and Vanilla.\n\t\t\t\tWhile it lived under that gate, the drag-out gesture silently\n\t\t\t\tproduced an invisible guide unless View > Guides happened to be on.\n\t\t\t-->\n\t\t\t@if (interactive()) {\n\t\t\t\t@for (g of rulerGuidesSvc.rulerGuides(); track g.id) {\n\t\t\t\t\t<!-- Guide line body: pointer-events:none -->\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"pptx-ng-ruler-guide-line\"\n\t\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos : 0\"\n\t\t\t\t\t\t[style.top.px]=\"g.axis === 'y' ? g.pos : 0\"\n\t\t\t\t\t\t[style.width]=\"g.axis === 'x' ? '1px' : '100%'\"\n\t\t\t\t\t\t[style.height]=\"g.axis === 'y' ? '1px' : '100%'\"\n\t\t\t\t\t></div>\n\t\t\t\t\t<!-- Drag handle: pointer-events:auto -->\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"pptx-ng-ruler-guide-handle\"\n\t\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos - 4 : 0\"\n\t\t\t\t\t\t[style.top.px]=\"g.axis === 'y' ? g.pos - 4 : 0\"\n\t\t\t\t\t\t[style.width]=\"g.axis === 'x' ? '9px' : '100%'\"\n\t\t\t\t\t\t[style.height]=\"g.axis === 'y' ? '9px' : '100%'\"\n\t\t\t\t\t\t[style.cursor]=\"g.axis === 'x' ? 'col-resize' : 'row-resize'\"\n\t\t\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onGuidePointerDown($event, g.id, g.axis)\"\n\t\t\t\t\t\t(dblclick)=\"rulerGuidesSvc.onGuideDoubleClick($event, g.id)\"\n\t\t\t\t\t\t[title]=\"'pptx.canvas.guideTooltip' | translate\"\n\t\t\t\t\t></div>\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tLive ink stroke preview: shown while the user is drawing.\n\t\t\t\tpointer-events:none so it never intercepts element gestures.\n\t\t\t\tNo data-pptx-element / aria-roledescription / data-pptx-viewport.\n\n\t\t\t\tRenders `inkDrawing.liveStrokeView()`: the same plain-path /\n\t\t\t\tpressure-circle / tilt-nib decision `InkRendererComponent` makes for\n\t\t\t\ta committed stroke, so a calligraphic lean or pressure-variable\n\t\t\t\twidth shows up while the pointer is still down, not only after\n\t\t\t\trelease.\n\t\t\t-->\n\t\t\t@if (inkDrawing.active() && inkDrawing.liveStrokeView() && drawTool() !== 'select') {\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-ink-preview\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t\tstyle=\"position: absolute; inset: 0; pointer-events: none; z-index: 70\"\n\t\t\t\t>\n\t\t\t\t\t@if (inkDrawing.liveStrokeView()?.nibMarks; as nibMarks) {\n\t\t\t\t\t\t<g [attr.opacity]=\"inkDrawing.liveStrokeView()?.opacity\">\n\t\t\t\t\t\t\t@for (m of nibMarks; track $index) {\n\t\t\t\t\t\t\t\t<ellipse\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"m.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"m.cy\"\n\t\t\t\t\t\t\t\t\t[attr.rx]=\"m.rPerp\"\n\t\t\t\t\t\t\t\t\t[attr.ry]=\"m.rTilt\"\n\t\t\t\t\t\t\t\t\t[attr.transform]=\"'rotate(' + m.rotationDeg + ' ' + m.cx + ' ' + m.cy + ')'\"\n\t\t\t\t\t\t\t\t\t[attr.fill]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t} @else if (inkDrawing.liveStrokeView()?.circles; as circles) {\n\t\t\t\t\t\t<g [attr.opacity]=\"inkDrawing.liveStrokeView()?.opacity\">\n\t\t\t\t\t\t\t@for (c of circles; track $index) {\n\t\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"c.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"c.cy\"\n\t\t\t\t\t\t\t\t\t[attr.r]=\"c.r\"\n\t\t\t\t\t\t\t\t\t[attr.fill]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t[attr.d]=\"inkDrawing.liveStrokeView()?.d\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t[attr.stroke]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"inkDrawing.liveStrokeView()?.width\"\n\t\t\t\t\t\t\t[attr.stroke-opacity]=\"inkDrawing.liveStrokeView()?.opacity\"\n\t\t\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tProjected overlays (collaboration cursors + remote selections).\n\t\t\t\tThey live INSIDE the scaled stage so the stage's CSS\n\t\t\t\ttransform:scale() applies the on-screen scale exactly once and\n\t\t\t\tthey can be authored in raw, unscaled slide coordinates, exactly\n\t\t\t\tlike the React/Vue overlays.\n\t\t\t-->\n\t\t\t<ng-content />\n\t\t</div>\n\n\t\t<!--\n\t\t\tRuler strips: siblings to the scaled stage inside the wrapper div,\n\t\t\tabsolutely positioned at top:0/left:0 within the wrapper's padding area.\n\t\t\tThe wrapper's padding (RULER_THICKNESS px top+left) reserves space for\n\t\t\tthese strips so the stage content starts below/right of them.\n\t\t\tpointer-events:none so they never intercept element gestures.\n\t\t-->\n\t\t@if (interactive() && showRulers()) {\n\t\t\t<!-- Corner square at the intersection of the two ruler strips -->\n\t\t\t<div class=\"pptx-ng-ruler-corner\" aria-hidden=\"true\"></div>\n\n\t\t\t<!--\n\t\t\t\tHorizontal ruler: spans the top padding row of the wrapper.\n\t\t\t\tA guide is dropped on pointer-UP (not down), resolved by the shared\n\t\t\t\trulerDragToGuidePosition, so a stray click on the strip cannot\n\t\t\t\tdrop a guide the user never dragged out - the rule React, Vue,\n\t\t\t\tSvelte and Vanilla all follow.\n\t\t\t-->\n\t\t\t<svg\n\t\t\t\tclass=\"pptx-ng-ruler-h\"\n\t\t\t\tdata-pptx-ruler=\"h\"\n\t\t\t\trole=\"presentation\"\n\t\t\t\t[attr.width]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t[style.cursor]=\"editable() ? 'row-resize' : null\"\n\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onRulerPointerDown('h', $event)\"\n\t\t\t\t(pointerup)=\"rulerGuidesSvc.onRulerPointerUp('h', $event)\"\n\t\t\t>\n\t\t\t\t<rect\n\t\t\t\t\t[attr.width]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t\tfill=\"#1e293b\"\n\t\t\t\t/>\n\t\t\t\t@if (hRulerHighlight(); as highlight) {\n\t\t\t\t\t<rect\n\t\t\t\t\t\t[attr.x]=\"highlight.start\"\n\t\t\t\t\t\ty=\"0\"\n\t\t\t\t\t\t[attr.width]=\"highlight.span\"\n\t\t\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t\t\tfill=\"rgba(96,165,250,0.25)\"\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\t<line\n\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t[attr.y1]=\"rulerThickness - 0.5\"\n\t\t\t\t\t[attr.x2]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t\t[attr.y2]=\"rulerThickness - 0.5\"\n\t\t\t\t\tstroke=\"rgba(255,255,255,0.15)\"\n\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t/>\n\t\t\t\t@for (tick of hRulerTicks(); track $index) {\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"tick.position\"\n\t\t\t\t\t\t[attr.y1]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.x2]=\"tick.position\"\n\t\t\t\t\t\t[attr.y2]=\"rulerThickness - rulerThickness * (tick.isMajor ? 0.6 : 0.3)\"\n\t\t\t\t\t\tstroke=\"rgba(156,163,175,0.7)\"\n\t\t\t\t\t\t[attr.stroke-width]=\"tick.isMajor ? 1 : 0.5\"\n\t\t\t\t\t/>\n\t\t\t\t\t@if (tick.label) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t[attr.x]=\"tick.position + 2\"\n\t\t\t\t\t\t\t[attr.y]=\"rulerFontSize + 1\"\n\t\t\t\t\t\t\t[attr.font-size]=\"rulerFontSize\"\n\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.9)\"\n\t\t\t\t\t\t\tstyle=\"font-family: system-ui, sans-serif\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ tick.label }}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</svg>\n\n\t\t\t<!-- Vertical ruler: spans the left padding column of the wrapper -->\n\t\t\t<svg\n\t\t\t\tclass=\"pptx-ng-ruler-v\"\n\t\t\t\tdata-pptx-ruler=\"v\"\n\t\t\t\trole=\"presentation\"\n\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t[attr.height]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t[style.cursor]=\"editable() ? 'col-resize' : null\"\n\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onRulerPointerDown('v', $event)\"\n\t\t\t\t(pointerup)=\"rulerGuidesSvc.onRulerPointerUp('v', $event)\"\n\t\t\t>\n\t\t\t\t<rect\n\t\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t\tfill=\"#1e293b\"\n\t\t\t\t/>\n\t\t\t\t@if (vRulerHighlight(); as highlight) {\n\t\t\t\t\t<rect\n\t\t\t\t\t\tx=\"0\"\n\t\t\t\t\t\t[attr.y]=\"highlight.start\"\n\t\t\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.height]=\"highlight.span\"\n\t\t\t\t\t\tfill=\"rgba(96,165,250,0.25)\"\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\t<line\n\t\t\t\t\t[attr.x1]=\"rulerThickness - 0.5\"\n\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t[attr.x2]=\"rulerThickness - 0.5\"\n\t\t\t\t\t[attr.y2]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t\tstroke=\"rgba(255,255,255,0.15)\"\n\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t/>\n\t\t\t\t@for (tick of vRulerTicks(); track $index) {\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.y1]=\"tick.position\"\n\t\t\t\t\t\t[attr.x2]=\"rulerThickness - rulerThickness * (tick.isMajor ? 0.6 : 0.3)\"\n\t\t\t\t\t\t[attr.y2]=\"tick.position\"\n\t\t\t\t\t\tstroke=\"rgba(156,163,175,0.7)\"\n\t\t\t\t\t\t[attr.stroke-width]=\"tick.isMajor ? 1 : 0.5\"\n\t\t\t\t\t/>\n\t\t\t\t\t@if (tick.label) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\tx=\"2\"\n\t\t\t\t\t\t\t[attr.y]=\"tick.position + rulerFontSize + 2\"\n\t\t\t\t\t\t\t[attr.font-size]=\"rulerFontSize\"\n\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.9)\"\n\t\t\t\t\t\t\tstyle=\"font-family: system-ui, sans-serif\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ tick.label }}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</svg>\n\t\t}\n\t</div>\n</div>\n", styles: [".pptx-ng-canvas-stage.is-editable{touch-action:none}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ElementRendererComponent, selector: "pptx-element-renderer", inputs: ["element", "mediaDataUrls", "zIndex", "obstacles", "canvasWidth", "canvasHeight", "interactive", "marked", "exposeElementId", "presenting", "editable", "fieldContext", "slideElements", "editTemplateMode", "parentGroupFill", "editingElementId"], outputs: ["cellCommit", "tableChange"] }, { kind: "component", type: AiFocusHighlightOverlayComponent, selector: "pptx-ai-focus-highlight-overlay", inputs: ["highlights", "elements", "activeSlideIndex"] }, { kind: "component", type: AiChangeOverlayComponent, selector: "pptx-ai-change-overlay", inputs: ["batch", "activeSlideIndex"] }, { kind: "component", type: ActiveXControlsOverlayComponent, selector: "pptx-activex-controls-overlay", inputs: ["controls", "canvasSize"] }, { kind: "component", type: InlineListEditorComponent, selector: "pptx-inline-list-editor", inputs: ["element", "seed", "spellCheck", "activationSelection"], outputs: ["textInput", "textCommit", "textCancel", "textFormat", "listSession"] }, { kind: "directive", type: RotateHandlePlacementDirective, selector: "[pptxRotateHandleFor]", inputs: ["pptxRotateHandleFor", "pptxRotateHandleStage"] }, { kind: "component", type: ChartQuickActionsOverlayComponent, selector: "pptx-chart-quick-actions-overlay", inputs: ["element", "canEdit", "zoom"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
130444
+ ], viewQueries: [{ propertyName: "textEditor", first: true, predicate: ["textEditor"], descendants: true, isSignal: true }, { propertyName: "listEditor", first: true, predicate: InlineListEditorComponent, descendants: true, isSignal: true }, { propertyName: "stageRef", first: true, predicate: ["stage"], descendants: true, isSignal: true }, { propertyName: "viewportRef", first: true, predicate: ["viewport"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n\t#viewport\n\tclass=\"pptx-ng-canvas-viewport\"\n\t[attr.data-pptx-viewport]=\"interactive() ? '' : null\"\n\t(pointerdown)=\"onViewportPointerDown($event)\"\n>\n\t<div class=\"pptx-ng-canvas-wrapper\" [ngStyle]=\"wrapperStyle()\">\n\t\t<div\n\t\t\t#stage\n\t\t\tclass=\"pptx-ng-canvas-stage\"\n\t\t\t[class.is-editable]=\"editable()\"\n\t\t\trole=\"region\"\n\t\t\t[attr.aria-roledescription]=\"interactive() || presenting() ? 'slide' : null\"\n\t\t\t[attr.data-pptx-ai-active]=\"aiActive() ? 'true' : null\"\n\t\t\t[ngStyle]=\"stageStyle()\"\n\t\t\t(pointerdown)=\"onStagePointerDown($event)\"\n\t\t\t(contextmenu)=\"onContextMenu($event)\"\n\t\t\t(dblclick)=\"onDblClick($event)\"\n\t\t>\n\t\t\t<!--\n\t\t\t\tTemplate layer: inherited master/layout elements, rendered BEHIND\n\t\t\t\tthe slide's own elements (lower z-index). Interactive + given the\n\t\t\t\tamber editable affordance only while editTemplateMode is on; when\n\t\t\t\toff they render inertly with no affordance, exactly as core\n\t\t\t\tdelivered them. `marked` keeps the data-pptx-element contract on\n\t\t\t\tthem even while locked: the marker means \"rendered slide element\",\n\t\t\t\tnot \"editable right now\".\n\t\t\t-->\n\t\t\t@for (element of visibleTemplateElements(); track element.id; let i = $index) {\n\t\t\t\t<pptx-element-renderer\n\t\t\t\t\t[element]=\"element\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zIndex]=\"i\"\n\t\t\t\t\t[obstacles]=\"connectorObstacles()\"\n\t\t\t\t\t[canvasWidth]=\"canvasSize().width\"\n\t\t\t\t\t[canvasHeight]=\"canvasSize().height\"\n\t\t\t\t\t[interactive]=\"interactive() && editTemplateMode()\"\n\t\t\t\t\t[marked]=\"interactive()\"\n\t\t\t\t\t[exposeElementId]=\"exposeElementIds()\"\n\t\t\t\t\t[editable]=\"editable() && editTemplateMode()\"\n\t\t\t\t\t[fieldContext]=\"fieldContext()\"\n\t\t\t\t\t[slideElements]=\"allElements()\"\n\t\t\t\t\t[editTemplateMode]=\"editTemplateMode()\"\n\t\t\t\t\t[editingElementId]=\"editable() ? editingId() : null\"\n\t\t\t\t\t(cellCommit)=\"cellCommit.emit($event)\"\n\t\t\t\t\t(tableChange)=\"tableChange.emit($event)\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@for (element of elements(); track element.id; let i = $index) {\n\t\t\t\t<pptx-element-renderer\n\t\t\t\t\t[element]=\"element\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zIndex]=\"visibleTemplateElements().length + i\"\n\t\t\t\t\t[obstacles]=\"connectorObstacles()\"\n\t\t\t\t\t[canvasWidth]=\"canvasSize().width\"\n\t\t\t\t\t[canvasHeight]=\"canvasSize().height\"\n\t\t\t\t\t[interactive]=\"interactive()\"\n\t\t\t\t\t[exposeElementId]=\"exposeElementIds()\"\n\t\t\t\t\t[presenting]=\"presenting()\"\n\t\t\t\t\t[editable]=\"editable()\"\n\t\t\t\t\t[fieldContext]=\"fieldContext()\"\n\t\t\t\t\t[slideElements]=\"allElements()\"\n\t\t\t\t\t[editTemplateMode]=\"false\"\n\t\t\t\t\t[editingElementId]=\"editable() ? editingId() : null\"\n\t\t\t\t\t(cellCommit)=\"cellCommit.emit($event)\"\n\t\t\t\t\t(tableChange)=\"tableChange.emit($event)\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t<pptx-activex-controls-overlay\n\t\t\t\t[controls]=\"slide()?.activeXControls ?? []\"\n\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t/>\n\t\t\t@if (aiHighlights().length > 0 || aiActive()) {\n\t\t\t\t<pptx-ai-focus-highlight-overlay\n\t\t\t\t\t[highlights]=\"aiHighlights()\"\n\t\t\t\t\t[elements]=\"elements()\"\n\t\t\t\t\t[activeSlideIndex]=\"aiActiveSlideIndex()\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@if (aiChangeBatch(); as batch) {\n\t\t\t\t<pptx-ai-change-overlay [batch]=\"batch\" [activeSlideIndex]=\"aiActiveSlideIndex()\" />\n\t\t\t}\n\t\t\t@for (box of selectionBoxes(); track box.id) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-selection\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"box.x\"\n\t\t\t\t\t[style.top.px]=\"box.y\"\n\t\t\t\t\t[style.width.px]=\"box.width\"\n\t\t\t\t\t[style.height.px]=\"box.height\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (h of handleBoxes(); track h.handle) {\n\t\t\t\t@let controlArtwork = selectionControlArtwork(h, h.handle);\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"resize\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.resize' | translate: { handle: h.handle }\"\n\t\t\t\t\t[ngStyle]=\"controlArtwork.frame\"\n\t\t\t\t\t[style.cursor]=\"h.cursor\"\n\t\t\t\t\t[style.--pptx-selection-width.px]=\"singleSelected()?.width\"\n\t\t\t\t\t[style.--pptx-selection-height.px]=\"singleSelected()?.height\"\n\t\t\t\t\t(pointerdown)=\"onHandlePointerDown($event, h.handle)\"\n\t\t\t\t\t(keydown)=\"onResizeHandleKeydown($event, h.handle)\"\n\t\t\t\t>\n\t\t\t\t\t<span data-pptx-handle-hit [ngStyle]=\"resizeHitAreaStyle(h.handle)\"></span>\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"pptx-ng-control-artwork\"\n\t\t\t\t\t\tdata-pptx-handle-artwork\n\t\t\t\t\t\t[ngStyle]=\"controlArtwork.artwork\"\n\t\t\t\t\t></span>\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t@if (marqueeRect(); as mr) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-marquee\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"mr.x\"\n\t\t\t\t\t[style.top.px]=\"mr.y\"\n\t\t\t\t\t[style.width.px]=\"mr.width\"\n\t\t\t\t\t[style.height.px]=\"mr.height\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (g of snapGuides(); track $index) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-snap-guide\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos : g.start\"\n\t\t\t\t\t[style.top.px]=\"g.axis === 'x' ? g.start : g.pos\"\n\t\t\t\t\t[style.width.px]=\"g.axis === 'x' ? 0 : g.end - g.start\"\n\t\t\t\t\t[style.height.px]=\"g.axis === 'x' ? g.end - g.start : 0\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@if (rotateHandle(); as rh) {\n\t\t\t\t@let rotateArtwork = selectionControlArtwork(rh);\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-rotate-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.z-index]=\"editingBox() ? 10003 : null\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"rotate\"\n\t\t\t\t\t[pptxRotateHandleFor]=\"singleSelected()?.id\"\n\t\t\t\t\t[pptxRotateHandleStage]=\"stage\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.rotate' | translate\"\n\t\t\t\t\t[ngStyle]=\"rotateArtwork.frame\"\n\t\t\t\t\t(pointerdown)=\"onRotatePointerDown($event)\"\n\t\t\t\t\t(keydown)=\"onRotateHandleKeydown($event)\"\n\t\t\t\t>\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"pptx-ng-control-artwork\"\n\t\t\t\t\t\tdata-pptx-handle-artwork\n\t\t\t\t\t\t[ngStyle]=\"rotateArtwork.artwork\"\n\t\t\t\t\t></span>\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tShape-adjustment affordances (amber diamonds), ONE per `a:avLst`\n\t\t\t\tguide. Existence, position and cursor all come from the SHARED\n\t\t\t\t`getShapeAdjustmentHandleDescriptors`, so they appear only for a\n\t\t\t\tgeometry with an adjustable parameter, sit where the other four\n\t\t\t\tbindings put them, and are absent for a shape locked with\n\t\t\t\t`noAdjustHandles`. Dragging one writes `shapeAdjustments`; it does\n\t\t\t\tNOT resize the element.\n\t\t\t-->\n\t\t\t@for (ah of adjustHandles(); track ah.key) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-adjust-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"adjust\"\n\t\t\t\t\t[attr.data-pptx-adjust-key]=\"ah.key\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.adjust' | translate\"\n\t\t\t\t\t[style.left.px]=\"ah.left\"\n\t\t\t\t\t[style.top.px]=\"ah.top\"\n\t\t\t\t\t[style.width.px]=\"ah.size\"\n\t\t\t\t\t[style.height.px]=\"ah.size\"\n\t\t\t\t\t[style.cursor]=\"ah.cursor\"\n\t\t\t\t\t(pointerdown)=\"onAdjustPointerDown($event, ah)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tPowerPoint's floating \"Chart Elements\"/\"Chart Styles\"/\"Chart\n\t\t\t\tFilters\" quick-action icons, shown just outside the chart's\n\t\t\t\ttop-right corner when it is the single selected element.\n\t\t\t-->\n\t\t\t@if (editable() && quickActionsChartElement(); as chartEl) {\n\t\t\t\t<pptx-chart-quick-actions-overlay\n\t\t\t\t\t[element]=\"chartEl\"\n\t\t\t\t\t[canEdit]=\"editable()\"\n\t\t\t\t\t[zoom]=\"effectiveScalePublic()\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tConnector endpoint authoring: drag an end onto a shape's connection\n\t\t\t\tpoint to bind it (`a:stCxn` / `a:endCxn`), or off one to detach.\n\t\t\t\tCandidate sites are revealed only while an end is in flight so they\n\t\t\t\tnever obscure the deck at rest.\n\t\t\t-->\n\t\t\t@for (site of connectorSiteCandidates(); track site.key) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-connection-site\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[class.is-snapped]=\"site.snapped\"\n\t\t\t\t\tdata-pptx-connection-site\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[style.left.px]=\"site.left\"\n\t\t\t\t\t[style.top.px]=\"site.top\"\n\t\t\t\t\t[style.width.px]=\"site.size\"\n\t\t\t\t\t[style.height.px]=\"site.size\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (ep of connectorEndpoints(); track ep.kind) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-connector-endpoint\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[class.is-attached]=\"ep.attached\"\n\t\t\t\t\t[attr.data-pptx-connector-endpoint]=\"ep.kind\"\n\t\t\t\t\t[attr.data-pptx-connector-attached]=\"ep.attached\"\n\t\t\t\t\t[attr.aria-label]=\"\n\t\t\t\t\t\t(ep.kind === 'start'\n\t\t\t\t\t\t\t? 'pptx.canvas.connectorEndpointStart'\n\t\t\t\t\t\t\t: 'pptx.canvas.connectorEndpointEnd'\n\t\t\t\t\t\t) | translate\n\t\t\t\t\t\"\n\t\t\t\t\t[style.left.px]=\"ep.left\"\n\t\t\t\t\t[style.top.px]=\"ep.top\"\n\t\t\t\t\t[style.width.px]=\"ep.size\"\n\t\t\t\t\t[style.height.px]=\"ep.size\"\n\t\t\t\t\t(pointerdown)=\"onConnectorEndpointPointerDown($event, ep.kind)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t\t@if (editingBox(); as eb) {\n\t\t\t\t@if (inlineListSeed(); as seed) {\n\t\t\t\t\t@for (session of [seed]; track session) {\n\t\t\t\t\t\t<pptx-inline-list-editor\n\t\t\t\t\t\t\t[element]=\"eb.element\"\n\t\t\t\t\t\t\t[seed]=\"session\"\n\t\t\t\t\t\t\t[activationSelection]=\"listActivationSelection()\"\n\t\t\t\t\t\t\t[spellCheck]=\"spellCheck()\"\n\t\t\t\t\t\t\t(textInput)=\"textInput.emit($event)\"\n\t\t\t\t\t\t\t(textCommit)=\"textCommit.emit($event)\"\n\t\t\t\t\t\t\t(textCancel)=\"textCancel.emit()\"\n\t\t\t\t\t\t\t(textFormat)=\"textFormat.emit($event)\"\n\t\t\t\t\t\t\t(listSession)=\"listSession.emit($event)\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t} @else {\n\t\t\t\t\t<textarea\n\t\t\t\t\t\t#textEditor\n\t\t\t\t\t\tdata-inline-editor\n\t\t\t\t\t\tclass=\"pptx-ng-text-editor\"\n\t\t\t\t\t\taria-label=\"Edit slide text\"\n\t\t\t\t\t\t[spellcheck]=\"spellCheck()\"\n\t\t\t\t\t\t[style.left.px]=\"eb.x\"\n\t\t\t\t\t\t[style.top.px]=\"eb.y\"\n\t\t\t\t\t\t[style.width.px]=\"eb.width\"\n\t\t\t\t\t\t[style.height.px]=\"eb.height\"\n\t\t\t\t\t\t(pointerdown)=\"$event.stopPropagation()\"\n\t\t\t\t\t\t(input)=\"onTextInput($event, eb.id)\"\n\t\t\t\t\t\t(blur)=\"commitText($event, eb.id)\"\n\t\t\t\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t\t\t\t></textarea>\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tView overlays: editor aids only, never on thumbnails/preview/presentation.\n\t\t\t\tAll are pointer-events:none so they never break selection/drag.\n\t\t\t\tNone carry data-pptx-element / aria-roledescription / data-pptx-viewport.\n\t\t\t-->\n\t\t\t@if (interactive() && showGrid()) {\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-overlay-grid\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t>\n\t\t\t\t\t<defs>\n\t\t\t\t\t\t<pattern\n\t\t\t\t\t\t\t[attr.id]=\"gridPatternId\"\n\t\t\t\t\t\t\t[attr.width]=\"gridSpacingPx()\"\n\t\t\t\t\t\t\t[attr.height]=\"gridSpacingPx()\"\n\t\t\t\t\t\t\tpatternUnits=\"userSpaceOnUse\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t[attr.cx]=\"gridSpacingPx() / 2\"\n\t\t\t\t\t\t\t\t[attr.cy]=\"gridSpacingPx() / 2\"\n\t\t\t\t\t\t\t\tr=\"0.6\"\n\t\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.55)\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</pattern>\n\t\t\t\t\t</defs>\n\t\t\t\t\t<rect\n\t\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t\t\t[attr.fill]=\"'url(#' + gridPatternId + ')'\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t@if (interactive() && showGuides()) {\n\t\t\t\t<!--\n\t\t\t\t\tCenter crosshair: one horizontal line and one vertical line\n\t\t\t\t\tthrough the midpoint of the slide. Static; draggable guides\n\t\t\t\t\tare a follow-up.\n\t\t\t\t-->\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-overlay-guides\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t>\n\t\t\t\t\t<!-- Horizontal center guide -->\n\t\t\t\t\t<line\n\t\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t\t[attr.y1]=\"canvasSize().height / 2\"\n\t\t\t\t\t\t[attr.x2]=\"canvasSize().width\"\n\t\t\t\t\t\t[attr.y2]=\"canvasSize().height / 2\"\n\t\t\t\t\t\tstroke=\"rgba(99,102,241,0.7)\"\n\t\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t\t\tstroke-dasharray=\"6 3\"\n\t\t\t\t\t/>\n\t\t\t\t\t<!-- Vertical center guide -->\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"canvasSize().width / 2\"\n\t\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t\t[attr.x2]=\"canvasSize().width / 2\"\n\t\t\t\t\t\t[attr.y2]=\"canvasSize().height\"\n\t\t\t\t\t\tstroke=\"rgba(99,102,241,0.7)\"\n\t\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t\t\tstroke-dasharray=\"6 3\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tUser-created ruler guides.\n\t\t\t\tEach guide has a non-interactive line body and an interactive\n\t\t\t\tdrag handle. Double-click the handle to delete the guide.\n\n\t\t\t\tDeliberately OUTSIDE the showGuides() gate above (which owns the\n\t\t\t\tstatic centre crosshair): a guide the user just dragged off a ruler\n\t\t\t\tstrip must be visible immediately, as it is in Vue and Vanilla.\n\t\t\t\tWhile it lived under that gate, the drag-out gesture silently\n\t\t\t\tproduced an invisible guide unless View > Guides happened to be on.\n\t\t\t-->\n\t\t\t@if (interactive()) {\n\t\t\t\t@for (g of rulerGuidesSvc.rulerGuides(); track g.id) {\n\t\t\t\t\t<!-- Guide line body: pointer-events:none -->\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"pptx-ng-ruler-guide-line\"\n\t\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos : 0\"\n\t\t\t\t\t\t[style.top.px]=\"g.axis === 'y' ? g.pos : 0\"\n\t\t\t\t\t\t[style.width]=\"g.axis === 'x' ? '1px' : '100%'\"\n\t\t\t\t\t\t[style.height]=\"g.axis === 'y' ? '1px' : '100%'\"\n\t\t\t\t\t></div>\n\t\t\t\t\t<!-- Drag handle: pointer-events:auto -->\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"pptx-ng-ruler-guide-handle\"\n\t\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos - 4 : 0\"\n\t\t\t\t\t\t[style.top.px]=\"g.axis === 'y' ? g.pos - 4 : 0\"\n\t\t\t\t\t\t[style.width]=\"g.axis === 'x' ? '9px' : '100%'\"\n\t\t\t\t\t\t[style.height]=\"g.axis === 'y' ? '9px' : '100%'\"\n\t\t\t\t\t\t[style.cursor]=\"g.axis === 'x' ? 'col-resize' : 'row-resize'\"\n\t\t\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onGuidePointerDown($event, g.id, g.axis)\"\n\t\t\t\t\t\t(dblclick)=\"rulerGuidesSvc.onGuideDoubleClick($event, g.id)\"\n\t\t\t\t\t\t[title]=\"'pptx.canvas.guideTooltip' | translate\"\n\t\t\t\t\t></div>\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tLive ink stroke preview: shown while the user is drawing.\n\t\t\t\tpointer-events:none so it never intercepts element gestures.\n\t\t\t\tNo data-pptx-element / aria-roledescription / data-pptx-viewport.\n\n\t\t\t\tRenders `inkDrawing.liveStrokeView()`: the same plain-path /\n\t\t\t\tpressure-circle / tilt-nib decision `InkRendererComponent` makes for\n\t\t\t\ta committed stroke, so a calligraphic lean or pressure-variable\n\t\t\t\twidth shows up while the pointer is still down, not only after\n\t\t\t\trelease.\n\t\t\t-->\n\t\t\t@if (inkDrawing.active() && inkDrawing.liveStrokeView() && drawTool() !== 'select') {\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-ink-preview\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t\tstyle=\"position: absolute; inset: 0; pointer-events: none; z-index: 70\"\n\t\t\t\t>\n\t\t\t\t\t@if (inkDrawing.liveStrokeView()?.nibMarks; as nibMarks) {\n\t\t\t\t\t\t<g [attr.opacity]=\"inkDrawing.liveStrokeView()?.opacity\">\n\t\t\t\t\t\t\t@for (m of nibMarks; track $index) {\n\t\t\t\t\t\t\t\t<ellipse\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"m.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"m.cy\"\n\t\t\t\t\t\t\t\t\t[attr.rx]=\"m.rPerp\"\n\t\t\t\t\t\t\t\t\t[attr.ry]=\"m.rTilt\"\n\t\t\t\t\t\t\t\t\t[attr.transform]=\"'rotate(' + m.rotationDeg + ' ' + m.cx + ' ' + m.cy + ')'\"\n\t\t\t\t\t\t\t\t\t[attr.fill]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t} @else if (inkDrawing.liveStrokeView()?.circles; as circles) {\n\t\t\t\t\t\t<g [attr.opacity]=\"inkDrawing.liveStrokeView()?.opacity\">\n\t\t\t\t\t\t\t@for (c of circles; track $index) {\n\t\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"c.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"c.cy\"\n\t\t\t\t\t\t\t\t\t[attr.r]=\"c.r\"\n\t\t\t\t\t\t\t\t\t[attr.fill]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t[attr.d]=\"inkDrawing.liveStrokeView()?.d\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t[attr.stroke]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"inkDrawing.liveStrokeView()?.width\"\n\t\t\t\t\t\t\t[attr.stroke-opacity]=\"inkDrawing.liveStrokeView()?.opacity\"\n\t\t\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tProjected overlays (collaboration cursors + remote selections).\n\t\t\t\tThey live INSIDE the scaled stage so the stage's CSS\n\t\t\t\ttransform:scale() applies the on-screen scale exactly once and\n\t\t\t\tthey can be authored in raw, unscaled slide coordinates, exactly\n\t\t\t\tlike the React/Vue overlays.\n\t\t\t-->\n\t\t\t<ng-content />\n\t\t</div>\n\n\t\t<!--\n\t\t\tRuler strips: siblings to the scaled stage inside the wrapper div,\n\t\t\tabsolutely positioned at top:0/left:0 within the wrapper's padding area.\n\t\t\tThe wrapper's padding (RULER_THICKNESS px top+left) reserves space for\n\t\t\tthese strips so the stage content starts below/right of them.\n\t\t\tpointer-events:none so they never intercept element gestures.\n\t\t-->\n\t\t@if (interactive() && showRulers()) {\n\t\t\t<!-- Corner square at the intersection of the two ruler strips -->\n\t\t\t<div class=\"pptx-ng-ruler-corner\" aria-hidden=\"true\"></div>\n\n\t\t\t<!--\n\t\t\t\tHorizontal ruler: spans the top padding row of the wrapper.\n\t\t\t\tA guide is dropped on pointer-UP (not down), resolved by the shared\n\t\t\t\trulerDragToGuidePosition, so a stray click on the strip cannot\n\t\t\t\tdrop a guide the user never dragged out - the rule React, Vue,\n\t\t\t\tSvelte and Vanilla all follow.\n\t\t\t-->\n\t\t\t<svg\n\t\t\t\tclass=\"pptx-ng-ruler-h\"\n\t\t\t\tdata-pptx-ruler=\"h\"\n\t\t\t\trole=\"presentation\"\n\t\t\t\t[attr.width]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t[style.cursor]=\"editable() ? 'row-resize' : null\"\n\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onRulerPointerDown('h', $event)\"\n\t\t\t\t(pointerup)=\"rulerGuidesSvc.onRulerPointerUp('h', $event)\"\n\t\t\t>\n\t\t\t\t<rect\n\t\t\t\t\t[attr.width]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t\tfill=\"#1e293b\"\n\t\t\t\t/>\n\t\t\t\t@if (hRulerHighlight(); as highlight) {\n\t\t\t\t\t<rect\n\t\t\t\t\t\t[attr.x]=\"highlight.start\"\n\t\t\t\t\t\ty=\"0\"\n\t\t\t\t\t\t[attr.width]=\"highlight.span\"\n\t\t\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t\t\tfill=\"rgba(96,165,250,0.25)\"\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\t<line\n\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t[attr.y1]=\"rulerThickness - 0.5\"\n\t\t\t\t\t[attr.x2]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t\t[attr.y2]=\"rulerThickness - 0.5\"\n\t\t\t\t\tstroke=\"rgba(255,255,255,0.15)\"\n\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t/>\n\t\t\t\t@for (tick of hRulerTicks(); track $index) {\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"tick.position\"\n\t\t\t\t\t\t[attr.y1]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.x2]=\"tick.position\"\n\t\t\t\t\t\t[attr.y2]=\"rulerThickness - rulerThickness * (tick.isMajor ? 0.6 : 0.3)\"\n\t\t\t\t\t\tstroke=\"rgba(156,163,175,0.7)\"\n\t\t\t\t\t\t[attr.stroke-width]=\"tick.isMajor ? 1 : 0.5\"\n\t\t\t\t\t/>\n\t\t\t\t\t@if (tick.label) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t[attr.x]=\"tick.position + 2\"\n\t\t\t\t\t\t\t[attr.y]=\"rulerFontSize + 1\"\n\t\t\t\t\t\t\t[attr.font-size]=\"rulerFontSize\"\n\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.9)\"\n\t\t\t\t\t\t\tstyle=\"font-family: system-ui, sans-serif\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ tick.label }}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</svg>\n\n\t\t\t<!-- Vertical ruler: spans the left padding column of the wrapper -->\n\t\t\t<svg\n\t\t\t\tclass=\"pptx-ng-ruler-v\"\n\t\t\t\tdata-pptx-ruler=\"v\"\n\t\t\t\trole=\"presentation\"\n\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t[attr.height]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t[style.cursor]=\"editable() ? 'col-resize' : null\"\n\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onRulerPointerDown('v', $event)\"\n\t\t\t\t(pointerup)=\"rulerGuidesSvc.onRulerPointerUp('v', $event)\"\n\t\t\t>\n\t\t\t\t<rect\n\t\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t\tfill=\"#1e293b\"\n\t\t\t\t/>\n\t\t\t\t@if (vRulerHighlight(); as highlight) {\n\t\t\t\t\t<rect\n\t\t\t\t\t\tx=\"0\"\n\t\t\t\t\t\t[attr.y]=\"highlight.start\"\n\t\t\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.height]=\"highlight.span\"\n\t\t\t\t\t\tfill=\"rgba(96,165,250,0.25)\"\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\t<line\n\t\t\t\t\t[attr.x1]=\"rulerThickness - 0.5\"\n\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t[attr.x2]=\"rulerThickness - 0.5\"\n\t\t\t\t\t[attr.y2]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t\tstroke=\"rgba(255,255,255,0.15)\"\n\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t/>\n\t\t\t\t@for (tick of vRulerTicks(); track $index) {\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.y1]=\"tick.position\"\n\t\t\t\t\t\t[attr.x2]=\"rulerThickness - rulerThickness * (tick.isMajor ? 0.6 : 0.3)\"\n\t\t\t\t\t\t[attr.y2]=\"tick.position\"\n\t\t\t\t\t\tstroke=\"rgba(156,163,175,0.7)\"\n\t\t\t\t\t\t[attr.stroke-width]=\"tick.isMajor ? 1 : 0.5\"\n\t\t\t\t\t/>\n\t\t\t\t\t@if (tick.label) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\tx=\"2\"\n\t\t\t\t\t\t\t[attr.y]=\"tick.position + rulerFontSize + 2\"\n\t\t\t\t\t\t\t[attr.font-size]=\"rulerFontSize\"\n\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.9)\"\n\t\t\t\t\t\t\tstyle=\"font-family: system-ui, sans-serif\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ tick.label }}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</svg>\n\t\t}\n\t</div>\n</div>\n", styles: [".pptx-ng-canvas-stage.is-editable{touch-action:none}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ElementRendererComponent, selector: "pptx-element-renderer", inputs: ["element", "mediaDataUrls", "zIndex", "obstacles", "canvasWidth", "canvasHeight", "interactive", "marked", "exposeElementId", "presenting", "editable", "fieldContext", "slideElements", "editTemplateMode", "parentGroupFill", "editingElementId"], outputs: ["cellCommit", "tableChange"] }, { kind: "component", type: AiFocusHighlightOverlayComponent, selector: "pptx-ai-focus-highlight-overlay", inputs: ["highlights", "elements", "activeSlideIndex"] }, { kind: "component", type: AiChangeOverlayComponent, selector: "pptx-ai-change-overlay", inputs: ["batch", "activeSlideIndex"] }, { kind: "component", type: ActiveXControlsOverlayComponent, selector: "pptx-activex-controls-overlay", inputs: ["controls", "canvasSize"] }, { kind: "component", type: InlineListEditorComponent, selector: "pptx-inline-list-editor", inputs: ["element", "seed", "spellCheck", "activationSelection"], outputs: ["textInput", "textCommit", "textCancel", "textFormat", "listSession"] }, { kind: "directive", type: RotateHandlePlacementDirective, selector: "[pptxRotateHandleFor]", inputs: ["pptxRotateHandleFor", "pptxRotateHandleStage"] }, { kind: "component", type: ChartQuickActionsOverlayComponent, selector: "pptx-chart-quick-actions-overlay", inputs: ["element", "canEdit", "zoom"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
130033
130445
  }
130034
130446
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: SlideCanvasComponent, decorators: [{
130035
130447
  type: Component,
@@ -130051,7 +130463,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
130051
130463
  InlineListEditorComponent,
130052
130464
  RotateHandlePlacementDirective,
130053
130465
  ChartQuickActionsOverlayComponent,
130054
- ], template: "<div\n\t#viewport\n\tclass=\"pptx-ng-canvas-viewport\"\n\t[attr.data-pptx-viewport]=\"interactive() ? '' : null\"\n\t(pointerdown)=\"onViewportPointerDown($event)\"\n>\n\t<div class=\"pptx-ng-canvas-wrapper\" [ngStyle]=\"wrapperStyle()\">\n\t\t<div\n\t\t\t#stage\n\t\t\tclass=\"pptx-ng-canvas-stage\"\n\t\t\t[class.is-editable]=\"editable()\"\n\t\t\trole=\"region\"\n\t\t\t[attr.aria-roledescription]=\"interactive() || presenting() ? 'slide' : null\"\n\t\t\t[attr.data-pptx-ai-active]=\"aiActive() ? 'true' : null\"\n\t\t\t[ngStyle]=\"stageStyle()\"\n\t\t\t(pointerdown)=\"onStagePointerDown($event)\"\n\t\t\t(contextmenu)=\"onContextMenu($event)\"\n\t\t\t(dblclick)=\"onDblClick($event)\"\n\t\t>\n\t\t\t<!--\n\t\t\t\tTemplate layer: inherited master/layout elements, rendered BEHIND\n\t\t\t\tthe slide's own elements (lower z-index). Interactive + given the\n\t\t\t\tamber editable affordance only while editTemplateMode is on; when\n\t\t\t\toff they render inertly with no affordance, exactly as core\n\t\t\t\tdelivered them. `marked` keeps the data-pptx-element contract on\n\t\t\t\tthem even while locked: the marker means \"rendered slide element\",\n\t\t\t\tnot \"editable right now\".\n\t\t\t-->\n\t\t\t@for (element of visibleTemplateElements(); track element.id; let i = $index) {\n\t\t\t\t<pptx-element-renderer\n\t\t\t\t\t[element]=\"element\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zIndex]=\"i\"\n\t\t\t\t\t[obstacles]=\"connectorObstacles()\"\n\t\t\t\t\t[canvasWidth]=\"canvasSize().width\"\n\t\t\t\t\t[canvasHeight]=\"canvasSize().height\"\n\t\t\t\t\t[interactive]=\"interactive() && editTemplateMode()\"\n\t\t\t\t\t[marked]=\"interactive()\"\n\t\t\t\t\t[exposeElementId]=\"exposeElementIds()\"\n\t\t\t\t\t[editable]=\"editable() && editTemplateMode()\"\n\t\t\t\t\t[fieldContext]=\"fieldContext()\"\n\t\t\t\t\t[slideElements]=\"allElements()\"\n\t\t\t\t\t[editTemplateMode]=\"editTemplateMode()\"\n\t\t\t\t\t[editingElementId]=\"editingId()\"\n\t\t\t\t\t(cellCommit)=\"cellCommit.emit($event)\"\n\t\t\t\t\t(tableChange)=\"tableChange.emit($event)\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@for (element of elements(); track element.id; let i = $index) {\n\t\t\t\t<pptx-element-renderer\n\t\t\t\t\t[element]=\"element\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zIndex]=\"visibleTemplateElements().length + i\"\n\t\t\t\t\t[obstacles]=\"connectorObstacles()\"\n\t\t\t\t\t[canvasWidth]=\"canvasSize().width\"\n\t\t\t\t\t[canvasHeight]=\"canvasSize().height\"\n\t\t\t\t\t[interactive]=\"interactive()\"\n\t\t\t\t\t[exposeElementId]=\"exposeElementIds()\"\n\t\t\t\t\t[presenting]=\"presenting()\"\n\t\t\t\t\t[editable]=\"editable()\"\n\t\t\t\t\t[fieldContext]=\"fieldContext()\"\n\t\t\t\t\t[slideElements]=\"allElements()\"\n\t\t\t\t\t[editTemplateMode]=\"false\"\n\t\t\t\t\t[editingElementId]=\"editingId()\"\n\t\t\t\t\t(cellCommit)=\"cellCommit.emit($event)\"\n\t\t\t\t\t(tableChange)=\"tableChange.emit($event)\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t<pptx-activex-controls-overlay\n\t\t\t\t[controls]=\"slide()?.activeXControls ?? []\"\n\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t/>\n\t\t\t@if (aiHighlights().length > 0 || aiActive()) {\n\t\t\t\t<pptx-ai-focus-highlight-overlay\n\t\t\t\t\t[highlights]=\"aiHighlights()\"\n\t\t\t\t\t[elements]=\"elements()\"\n\t\t\t\t\t[activeSlideIndex]=\"aiActiveSlideIndex()\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@if (aiChangeBatch(); as batch) {\n\t\t\t\t<pptx-ai-change-overlay [batch]=\"batch\" [activeSlideIndex]=\"aiActiveSlideIndex()\" />\n\t\t\t}\n\t\t\t@for (box of selectionBoxes(); track box.id) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-selection\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"box.x\"\n\t\t\t\t\t[style.top.px]=\"box.y\"\n\t\t\t\t\t[style.width.px]=\"box.width\"\n\t\t\t\t\t[style.height.px]=\"box.height\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (h of handleBoxes(); track h.handle) {\n\t\t\t\t@let controlArtwork = selectionControlArtwork(h, h.handle);\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"resize\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.resize' | translate: { handle: h.handle }\"\n\t\t\t\t\t[ngStyle]=\"controlArtwork.frame\"\n\t\t\t\t\t[style.cursor]=\"h.cursor\"\n\t\t\t\t\t[style.--pptx-selection-width.px]=\"singleSelected()?.width\"\n\t\t\t\t\t[style.--pptx-selection-height.px]=\"singleSelected()?.height\"\n\t\t\t\t\t(pointerdown)=\"onHandlePointerDown($event, h.handle)\"\n\t\t\t\t\t(keydown)=\"onResizeHandleKeydown($event, h.handle)\"\n\t\t\t\t>\n\t\t\t\t\t<span data-pptx-handle-hit [ngStyle]=\"resizeHitAreaStyle(h.handle)\"></span>\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"pptx-ng-control-artwork\"\n\t\t\t\t\t\tdata-pptx-handle-artwork\n\t\t\t\t\t\t[ngStyle]=\"controlArtwork.artwork\"\n\t\t\t\t\t></span>\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t@if (marqueeRect(); as mr) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-marquee\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"mr.x\"\n\t\t\t\t\t[style.top.px]=\"mr.y\"\n\t\t\t\t\t[style.width.px]=\"mr.width\"\n\t\t\t\t\t[style.height.px]=\"mr.height\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (g of snapGuides(); track $index) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-snap-guide\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos : g.start\"\n\t\t\t\t\t[style.top.px]=\"g.axis === 'x' ? g.start : g.pos\"\n\t\t\t\t\t[style.width.px]=\"g.axis === 'x' ? 0 : g.end - g.start\"\n\t\t\t\t\t[style.height.px]=\"g.axis === 'x' ? g.end - g.start : 0\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@if (rotateHandle(); as rh) {\n\t\t\t\t@let rotateArtwork = selectionControlArtwork(rh);\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-rotate-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.z-index]=\"editingBox() ? 10003 : null\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"rotate\"\n\t\t\t\t\t[pptxRotateHandleFor]=\"singleSelected()?.id\"\n\t\t\t\t\t[pptxRotateHandleStage]=\"stage\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.rotate' | translate\"\n\t\t\t\t\t[ngStyle]=\"rotateArtwork.frame\"\n\t\t\t\t\t(pointerdown)=\"onRotatePointerDown($event)\"\n\t\t\t\t\t(keydown)=\"onRotateHandleKeydown($event)\"\n\t\t\t\t>\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"pptx-ng-control-artwork\"\n\t\t\t\t\t\tdata-pptx-handle-artwork\n\t\t\t\t\t\t[ngStyle]=\"rotateArtwork.artwork\"\n\t\t\t\t\t></span>\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tShape-adjustment affordances (amber diamonds), ONE per `a:avLst`\n\t\t\t\tguide. Existence, position and cursor all come from the SHARED\n\t\t\t\t`getShapeAdjustmentHandleDescriptors`, so they appear only for a\n\t\t\t\tgeometry with an adjustable parameter, sit where the other four\n\t\t\t\tbindings put them, and are absent for a shape locked with\n\t\t\t\t`noAdjustHandles`. Dragging one writes `shapeAdjustments`; it does\n\t\t\t\tNOT resize the element.\n\t\t\t-->\n\t\t\t@for (ah of adjustHandles(); track ah.key) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-adjust-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"adjust\"\n\t\t\t\t\t[attr.data-pptx-adjust-key]=\"ah.key\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.adjust' | translate\"\n\t\t\t\t\t[style.left.px]=\"ah.left\"\n\t\t\t\t\t[style.top.px]=\"ah.top\"\n\t\t\t\t\t[style.width.px]=\"ah.size\"\n\t\t\t\t\t[style.height.px]=\"ah.size\"\n\t\t\t\t\t[style.cursor]=\"ah.cursor\"\n\t\t\t\t\t(pointerdown)=\"onAdjustPointerDown($event, ah)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tPowerPoint's floating \"Chart Elements\"/\"Chart Styles\"/\"Chart\n\t\t\t\tFilters\" quick-action icons, shown just outside the chart's\n\t\t\t\ttop-right corner when it is the single selected element.\n\t\t\t-->\n\t\t\t@if (editable() && quickActionsChartElement(); as chartEl) {\n\t\t\t\t<pptx-chart-quick-actions-overlay\n\t\t\t\t\t[element]=\"chartEl\"\n\t\t\t\t\t[canEdit]=\"editable()\"\n\t\t\t\t\t[zoom]=\"effectiveScalePublic()\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tConnector endpoint authoring: drag an end onto a shape's connection\n\t\t\t\tpoint to bind it (`a:stCxn` / `a:endCxn`), or off one to detach.\n\t\t\t\tCandidate sites are revealed only while an end is in flight so they\n\t\t\t\tnever obscure the deck at rest.\n\t\t\t-->\n\t\t\t@for (site of connectorSiteCandidates(); track site.key) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-connection-site\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[class.is-snapped]=\"site.snapped\"\n\t\t\t\t\tdata-pptx-connection-site\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[style.left.px]=\"site.left\"\n\t\t\t\t\t[style.top.px]=\"site.top\"\n\t\t\t\t\t[style.width.px]=\"site.size\"\n\t\t\t\t\t[style.height.px]=\"site.size\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (ep of connectorEndpoints(); track ep.kind) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-connector-endpoint\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[class.is-attached]=\"ep.attached\"\n\t\t\t\t\t[attr.data-pptx-connector-endpoint]=\"ep.kind\"\n\t\t\t\t\t[attr.data-pptx-connector-attached]=\"ep.attached\"\n\t\t\t\t\t[attr.aria-label]=\"\n\t\t\t\t\t\t(ep.kind === 'start'\n\t\t\t\t\t\t\t? 'pptx.canvas.connectorEndpointStart'\n\t\t\t\t\t\t\t: 'pptx.canvas.connectorEndpointEnd'\n\t\t\t\t\t\t) | translate\n\t\t\t\t\t\"\n\t\t\t\t\t[style.left.px]=\"ep.left\"\n\t\t\t\t\t[style.top.px]=\"ep.top\"\n\t\t\t\t\t[style.width.px]=\"ep.size\"\n\t\t\t\t\t[style.height.px]=\"ep.size\"\n\t\t\t\t\t(pointerdown)=\"onConnectorEndpointPointerDown($event, ep.kind)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t\t@if (editingBox(); as eb) {\n\t\t\t\t@if (inlineListSeed(); as seed) {\n\t\t\t\t\t@for (session of [seed]; track session) {\n\t\t\t\t\t\t<pptx-inline-list-editor\n\t\t\t\t\t\t\t[element]=\"eb.element\"\n\t\t\t\t\t\t\t[seed]=\"session\"\n\t\t\t\t\t\t\t[activationSelection]=\"listActivationSelection()\"\n\t\t\t\t\t\t\t[spellCheck]=\"spellCheck()\"\n\t\t\t\t\t\t\t(textInput)=\"textInput.emit($event)\"\n\t\t\t\t\t\t\t(textCommit)=\"textCommit.emit($event)\"\n\t\t\t\t\t\t\t(textCancel)=\"textCancel.emit()\"\n\t\t\t\t\t\t\t(textFormat)=\"textFormat.emit($event)\"\n\t\t\t\t\t\t\t(listSession)=\"listSession.emit($event)\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t} @else {\n\t\t\t\t\t<textarea\n\t\t\t\t\t\t#textEditor\n\t\t\t\t\t\tdata-inline-editor\n\t\t\t\t\t\tclass=\"pptx-ng-text-editor\"\n\t\t\t\t\t\taria-label=\"Edit slide text\"\n\t\t\t\t\t\t[spellcheck]=\"spellCheck()\"\n\t\t\t\t\t\t[style.left.px]=\"eb.x\"\n\t\t\t\t\t\t[style.top.px]=\"eb.y\"\n\t\t\t\t\t\t[style.width.px]=\"eb.width\"\n\t\t\t\t\t\t[style.height.px]=\"eb.height\"\n\t\t\t\t\t\t(pointerdown)=\"$event.stopPropagation()\"\n\t\t\t\t\t\t(input)=\"onTextInput($event, eb.id)\"\n\t\t\t\t\t\t(blur)=\"commitText($event, eb.id)\"\n\t\t\t\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t\t\t\t></textarea>\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tView overlays: editor aids only, never on thumbnails/preview/presentation.\n\t\t\t\tAll are pointer-events:none so they never break selection/drag.\n\t\t\t\tNone carry data-pptx-element / aria-roledescription / data-pptx-viewport.\n\t\t\t-->\n\t\t\t@if (interactive() && showGrid()) {\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-overlay-grid\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t>\n\t\t\t\t\t<defs>\n\t\t\t\t\t\t<pattern\n\t\t\t\t\t\t\t[attr.id]=\"gridPatternId\"\n\t\t\t\t\t\t\t[attr.width]=\"gridSpacingPx()\"\n\t\t\t\t\t\t\t[attr.height]=\"gridSpacingPx()\"\n\t\t\t\t\t\t\tpatternUnits=\"userSpaceOnUse\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t[attr.cx]=\"gridSpacingPx() / 2\"\n\t\t\t\t\t\t\t\t[attr.cy]=\"gridSpacingPx() / 2\"\n\t\t\t\t\t\t\t\tr=\"0.6\"\n\t\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.55)\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</pattern>\n\t\t\t\t\t</defs>\n\t\t\t\t\t<rect\n\t\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t\t\t[attr.fill]=\"'url(#' + gridPatternId + ')'\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t@if (interactive() && showGuides()) {\n\t\t\t\t<!--\n\t\t\t\t\tCenter crosshair: one horizontal line and one vertical line\n\t\t\t\t\tthrough the midpoint of the slide. Static; draggable guides\n\t\t\t\t\tare a follow-up.\n\t\t\t\t-->\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-overlay-guides\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t>\n\t\t\t\t\t<!-- Horizontal center guide -->\n\t\t\t\t\t<line\n\t\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t\t[attr.y1]=\"canvasSize().height / 2\"\n\t\t\t\t\t\t[attr.x2]=\"canvasSize().width\"\n\t\t\t\t\t\t[attr.y2]=\"canvasSize().height / 2\"\n\t\t\t\t\t\tstroke=\"rgba(99,102,241,0.7)\"\n\t\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t\t\tstroke-dasharray=\"6 3\"\n\t\t\t\t\t/>\n\t\t\t\t\t<!-- Vertical center guide -->\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"canvasSize().width / 2\"\n\t\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t\t[attr.x2]=\"canvasSize().width / 2\"\n\t\t\t\t\t\t[attr.y2]=\"canvasSize().height\"\n\t\t\t\t\t\tstroke=\"rgba(99,102,241,0.7)\"\n\t\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t\t\tstroke-dasharray=\"6 3\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tUser-created ruler guides.\n\t\t\t\tEach guide has a non-interactive line body and an interactive\n\t\t\t\tdrag handle. Double-click the handle to delete the guide.\n\n\t\t\t\tDeliberately OUTSIDE the showGuides() gate above (which owns the\n\t\t\t\tstatic centre crosshair): a guide the user just dragged off a ruler\n\t\t\t\tstrip must be visible immediately, as it is in Vue and Vanilla.\n\t\t\t\tWhile it lived under that gate, the drag-out gesture silently\n\t\t\t\tproduced an invisible guide unless View > Guides happened to be on.\n\t\t\t-->\n\t\t\t@if (interactive()) {\n\t\t\t\t@for (g of rulerGuidesSvc.rulerGuides(); track g.id) {\n\t\t\t\t\t<!-- Guide line body: pointer-events:none -->\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"pptx-ng-ruler-guide-line\"\n\t\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos : 0\"\n\t\t\t\t\t\t[style.top.px]=\"g.axis === 'y' ? g.pos : 0\"\n\t\t\t\t\t\t[style.width]=\"g.axis === 'x' ? '1px' : '100%'\"\n\t\t\t\t\t\t[style.height]=\"g.axis === 'y' ? '1px' : '100%'\"\n\t\t\t\t\t></div>\n\t\t\t\t\t<!-- Drag handle: pointer-events:auto -->\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"pptx-ng-ruler-guide-handle\"\n\t\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos - 4 : 0\"\n\t\t\t\t\t\t[style.top.px]=\"g.axis === 'y' ? g.pos - 4 : 0\"\n\t\t\t\t\t\t[style.width]=\"g.axis === 'x' ? '9px' : '100%'\"\n\t\t\t\t\t\t[style.height]=\"g.axis === 'y' ? '9px' : '100%'\"\n\t\t\t\t\t\t[style.cursor]=\"g.axis === 'x' ? 'col-resize' : 'row-resize'\"\n\t\t\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onGuidePointerDown($event, g.id, g.axis)\"\n\t\t\t\t\t\t(dblclick)=\"rulerGuidesSvc.onGuideDoubleClick($event, g.id)\"\n\t\t\t\t\t\t[title]=\"'pptx.canvas.guideTooltip' | translate\"\n\t\t\t\t\t></div>\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tLive ink stroke preview: shown while the user is drawing.\n\t\t\t\tpointer-events:none so it never intercepts element gestures.\n\t\t\t\tNo data-pptx-element / aria-roledescription / data-pptx-viewport.\n\n\t\t\t\tRenders `inkDrawing.liveStrokeView()`: the same plain-path /\n\t\t\t\tpressure-circle / tilt-nib decision `InkRendererComponent` makes for\n\t\t\t\ta committed stroke, so a calligraphic lean or pressure-variable\n\t\t\t\twidth shows up while the pointer is still down, not only after\n\t\t\t\trelease.\n\t\t\t-->\n\t\t\t@if (inkDrawing.active() && inkDrawing.liveStrokeView() && drawTool() !== 'select') {\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-ink-preview\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t\tstyle=\"position: absolute; inset: 0; pointer-events: none; z-index: 70\"\n\t\t\t\t>\n\t\t\t\t\t@if (inkDrawing.liveStrokeView()?.nibMarks; as nibMarks) {\n\t\t\t\t\t\t<g [attr.opacity]=\"inkDrawing.liveStrokeView()?.opacity\">\n\t\t\t\t\t\t\t@for (m of nibMarks; track $index) {\n\t\t\t\t\t\t\t\t<ellipse\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"m.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"m.cy\"\n\t\t\t\t\t\t\t\t\t[attr.rx]=\"m.rPerp\"\n\t\t\t\t\t\t\t\t\t[attr.ry]=\"m.rTilt\"\n\t\t\t\t\t\t\t\t\t[attr.transform]=\"'rotate(' + m.rotationDeg + ' ' + m.cx + ' ' + m.cy + ')'\"\n\t\t\t\t\t\t\t\t\t[attr.fill]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t} @else if (inkDrawing.liveStrokeView()?.circles; as circles) {\n\t\t\t\t\t\t<g [attr.opacity]=\"inkDrawing.liveStrokeView()?.opacity\">\n\t\t\t\t\t\t\t@for (c of circles; track $index) {\n\t\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"c.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"c.cy\"\n\t\t\t\t\t\t\t\t\t[attr.r]=\"c.r\"\n\t\t\t\t\t\t\t\t\t[attr.fill]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t[attr.d]=\"inkDrawing.liveStrokeView()?.d\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t[attr.stroke]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"inkDrawing.liveStrokeView()?.width\"\n\t\t\t\t\t\t\t[attr.stroke-opacity]=\"inkDrawing.liveStrokeView()?.opacity\"\n\t\t\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tProjected overlays (collaboration cursors + remote selections).\n\t\t\t\tThey live INSIDE the scaled stage so the stage's CSS\n\t\t\t\ttransform:scale() applies the on-screen scale exactly once and\n\t\t\t\tthey can be authored in raw, unscaled slide coordinates, exactly\n\t\t\t\tlike the React/Vue overlays.\n\t\t\t-->\n\t\t\t<ng-content />\n\t\t</div>\n\n\t\t<!--\n\t\t\tRuler strips: siblings to the scaled stage inside the wrapper div,\n\t\t\tabsolutely positioned at top:0/left:0 within the wrapper's padding area.\n\t\t\tThe wrapper's padding (RULER_THICKNESS px top+left) reserves space for\n\t\t\tthese strips so the stage content starts below/right of them.\n\t\t\tpointer-events:none so they never intercept element gestures.\n\t\t-->\n\t\t@if (interactive() && showRulers()) {\n\t\t\t<!-- Corner square at the intersection of the two ruler strips -->\n\t\t\t<div class=\"pptx-ng-ruler-corner\" aria-hidden=\"true\"></div>\n\n\t\t\t<!--\n\t\t\t\tHorizontal ruler: spans the top padding row of the wrapper.\n\t\t\t\tA guide is dropped on pointer-UP (not down), resolved by the shared\n\t\t\t\trulerDragToGuidePosition, so a stray click on the strip cannot\n\t\t\t\tdrop a guide the user never dragged out - the rule React, Vue,\n\t\t\t\tSvelte and Vanilla all follow.\n\t\t\t-->\n\t\t\t<svg\n\t\t\t\tclass=\"pptx-ng-ruler-h\"\n\t\t\t\tdata-pptx-ruler=\"h\"\n\t\t\t\trole=\"presentation\"\n\t\t\t\t[attr.width]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t[style.cursor]=\"editable() ? 'row-resize' : null\"\n\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onRulerPointerDown('h', $event)\"\n\t\t\t\t(pointerup)=\"rulerGuidesSvc.onRulerPointerUp('h', $event)\"\n\t\t\t>\n\t\t\t\t<rect\n\t\t\t\t\t[attr.width]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t\tfill=\"#1e293b\"\n\t\t\t\t/>\n\t\t\t\t@if (hRulerHighlight(); as highlight) {\n\t\t\t\t\t<rect\n\t\t\t\t\t\t[attr.x]=\"highlight.start\"\n\t\t\t\t\t\ty=\"0\"\n\t\t\t\t\t\t[attr.width]=\"highlight.span\"\n\t\t\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t\t\tfill=\"rgba(96,165,250,0.25)\"\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\t<line\n\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t[attr.y1]=\"rulerThickness - 0.5\"\n\t\t\t\t\t[attr.x2]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t\t[attr.y2]=\"rulerThickness - 0.5\"\n\t\t\t\t\tstroke=\"rgba(255,255,255,0.15)\"\n\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t/>\n\t\t\t\t@for (tick of hRulerTicks(); track $index) {\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"tick.position\"\n\t\t\t\t\t\t[attr.y1]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.x2]=\"tick.position\"\n\t\t\t\t\t\t[attr.y2]=\"rulerThickness - rulerThickness * (tick.isMajor ? 0.6 : 0.3)\"\n\t\t\t\t\t\tstroke=\"rgba(156,163,175,0.7)\"\n\t\t\t\t\t\t[attr.stroke-width]=\"tick.isMajor ? 1 : 0.5\"\n\t\t\t\t\t/>\n\t\t\t\t\t@if (tick.label) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t[attr.x]=\"tick.position + 2\"\n\t\t\t\t\t\t\t[attr.y]=\"rulerFontSize + 1\"\n\t\t\t\t\t\t\t[attr.font-size]=\"rulerFontSize\"\n\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.9)\"\n\t\t\t\t\t\t\tstyle=\"font-family: system-ui, sans-serif\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ tick.label }}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</svg>\n\n\t\t\t<!-- Vertical ruler: spans the left padding column of the wrapper -->\n\t\t\t<svg\n\t\t\t\tclass=\"pptx-ng-ruler-v\"\n\t\t\t\tdata-pptx-ruler=\"v\"\n\t\t\t\trole=\"presentation\"\n\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t[attr.height]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t[style.cursor]=\"editable() ? 'col-resize' : null\"\n\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onRulerPointerDown('v', $event)\"\n\t\t\t\t(pointerup)=\"rulerGuidesSvc.onRulerPointerUp('v', $event)\"\n\t\t\t>\n\t\t\t\t<rect\n\t\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t\tfill=\"#1e293b\"\n\t\t\t\t/>\n\t\t\t\t@if (vRulerHighlight(); as highlight) {\n\t\t\t\t\t<rect\n\t\t\t\t\t\tx=\"0\"\n\t\t\t\t\t\t[attr.y]=\"highlight.start\"\n\t\t\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.height]=\"highlight.span\"\n\t\t\t\t\t\tfill=\"rgba(96,165,250,0.25)\"\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\t<line\n\t\t\t\t\t[attr.x1]=\"rulerThickness - 0.5\"\n\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t[attr.x2]=\"rulerThickness - 0.5\"\n\t\t\t\t\t[attr.y2]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t\tstroke=\"rgba(255,255,255,0.15)\"\n\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t/>\n\t\t\t\t@for (tick of vRulerTicks(); track $index) {\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.y1]=\"tick.position\"\n\t\t\t\t\t\t[attr.x2]=\"rulerThickness - rulerThickness * (tick.isMajor ? 0.6 : 0.3)\"\n\t\t\t\t\t\t[attr.y2]=\"tick.position\"\n\t\t\t\t\t\tstroke=\"rgba(156,163,175,0.7)\"\n\t\t\t\t\t\t[attr.stroke-width]=\"tick.isMajor ? 1 : 0.5\"\n\t\t\t\t\t/>\n\t\t\t\t\t@if (tick.label) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\tx=\"2\"\n\t\t\t\t\t\t\t[attr.y]=\"tick.position + rulerFontSize + 2\"\n\t\t\t\t\t\t\t[attr.font-size]=\"rulerFontSize\"\n\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.9)\"\n\t\t\t\t\t\t\tstyle=\"font-family: system-ui, sans-serif\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ tick.label }}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</svg>\n\t\t}\n\t</div>\n</div>\n", styles: [".pptx-ng-canvas-stage.is-editable{touch-action:none}\n"] }]
130466
+ ], template: "<div\n\t#viewport\n\tclass=\"pptx-ng-canvas-viewport\"\n\t[attr.data-pptx-viewport]=\"interactive() ? '' : null\"\n\t(pointerdown)=\"onViewportPointerDown($event)\"\n>\n\t<div class=\"pptx-ng-canvas-wrapper\" [ngStyle]=\"wrapperStyle()\">\n\t\t<div\n\t\t\t#stage\n\t\t\tclass=\"pptx-ng-canvas-stage\"\n\t\t\t[class.is-editable]=\"editable()\"\n\t\t\trole=\"region\"\n\t\t\t[attr.aria-roledescription]=\"interactive() || presenting() ? 'slide' : null\"\n\t\t\t[attr.data-pptx-ai-active]=\"aiActive() ? 'true' : null\"\n\t\t\t[ngStyle]=\"stageStyle()\"\n\t\t\t(pointerdown)=\"onStagePointerDown($event)\"\n\t\t\t(contextmenu)=\"onContextMenu($event)\"\n\t\t\t(dblclick)=\"onDblClick($event)\"\n\t\t>\n\t\t\t<!--\n\t\t\t\tTemplate layer: inherited master/layout elements, rendered BEHIND\n\t\t\t\tthe slide's own elements (lower z-index). Interactive + given the\n\t\t\t\tamber editable affordance only while editTemplateMode is on; when\n\t\t\t\toff they render inertly with no affordance, exactly as core\n\t\t\t\tdelivered them. `marked` keeps the data-pptx-element contract on\n\t\t\t\tthem even while locked: the marker means \"rendered slide element\",\n\t\t\t\tnot \"editable right now\".\n\t\t\t-->\n\t\t\t@for (element of visibleTemplateElements(); track element.id; let i = $index) {\n\t\t\t\t<pptx-element-renderer\n\t\t\t\t\t[element]=\"element\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zIndex]=\"i\"\n\t\t\t\t\t[obstacles]=\"connectorObstacles()\"\n\t\t\t\t\t[canvasWidth]=\"canvasSize().width\"\n\t\t\t\t\t[canvasHeight]=\"canvasSize().height\"\n\t\t\t\t\t[interactive]=\"interactive() && editTemplateMode()\"\n\t\t\t\t\t[marked]=\"interactive()\"\n\t\t\t\t\t[exposeElementId]=\"exposeElementIds()\"\n\t\t\t\t\t[editable]=\"editable() && editTemplateMode()\"\n\t\t\t\t\t[fieldContext]=\"fieldContext()\"\n\t\t\t\t\t[slideElements]=\"allElements()\"\n\t\t\t\t\t[editTemplateMode]=\"editTemplateMode()\"\n\t\t\t\t\t[editingElementId]=\"editable() ? editingId() : null\"\n\t\t\t\t\t(cellCommit)=\"cellCommit.emit($event)\"\n\t\t\t\t\t(tableChange)=\"tableChange.emit($event)\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@for (element of elements(); track element.id; let i = $index) {\n\t\t\t\t<pptx-element-renderer\n\t\t\t\t\t[element]=\"element\"\n\t\t\t\t\t[mediaDataUrls]=\"mediaDataUrls()\"\n\t\t\t\t\t[zIndex]=\"visibleTemplateElements().length + i\"\n\t\t\t\t\t[obstacles]=\"connectorObstacles()\"\n\t\t\t\t\t[canvasWidth]=\"canvasSize().width\"\n\t\t\t\t\t[canvasHeight]=\"canvasSize().height\"\n\t\t\t\t\t[interactive]=\"interactive()\"\n\t\t\t\t\t[exposeElementId]=\"exposeElementIds()\"\n\t\t\t\t\t[presenting]=\"presenting()\"\n\t\t\t\t\t[editable]=\"editable()\"\n\t\t\t\t\t[fieldContext]=\"fieldContext()\"\n\t\t\t\t\t[slideElements]=\"allElements()\"\n\t\t\t\t\t[editTemplateMode]=\"false\"\n\t\t\t\t\t[editingElementId]=\"editable() ? editingId() : null\"\n\t\t\t\t\t(cellCommit)=\"cellCommit.emit($event)\"\n\t\t\t\t\t(tableChange)=\"tableChange.emit($event)\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t<pptx-activex-controls-overlay\n\t\t\t\t[controls]=\"slide()?.activeXControls ?? []\"\n\t\t\t\t[canvasSize]=\"canvasSize()\"\n\t\t\t/>\n\t\t\t@if (aiHighlights().length > 0 || aiActive()) {\n\t\t\t\t<pptx-ai-focus-highlight-overlay\n\t\t\t\t\t[highlights]=\"aiHighlights()\"\n\t\t\t\t\t[elements]=\"elements()\"\n\t\t\t\t\t[activeSlideIndex]=\"aiActiveSlideIndex()\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@if (aiChangeBatch(); as batch) {\n\t\t\t\t<pptx-ai-change-overlay [batch]=\"batch\" [activeSlideIndex]=\"aiActiveSlideIndex()\" />\n\t\t\t}\n\t\t\t@for (box of selectionBoxes(); track box.id) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-selection\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"box.x\"\n\t\t\t\t\t[style.top.px]=\"box.y\"\n\t\t\t\t\t[style.width.px]=\"box.width\"\n\t\t\t\t\t[style.height.px]=\"box.height\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (h of handleBoxes(); track h.handle) {\n\t\t\t\t@let controlArtwork = selectionControlArtwork(h, h.handle);\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"resize\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.resize' | translate: { handle: h.handle }\"\n\t\t\t\t\t[ngStyle]=\"controlArtwork.frame\"\n\t\t\t\t\t[style.cursor]=\"h.cursor\"\n\t\t\t\t\t[style.--pptx-selection-width.px]=\"singleSelected()?.width\"\n\t\t\t\t\t[style.--pptx-selection-height.px]=\"singleSelected()?.height\"\n\t\t\t\t\t(pointerdown)=\"onHandlePointerDown($event, h.handle)\"\n\t\t\t\t\t(keydown)=\"onResizeHandleKeydown($event, h.handle)\"\n\t\t\t\t>\n\t\t\t\t\t<span data-pptx-handle-hit [ngStyle]=\"resizeHitAreaStyle(h.handle)\"></span>\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"pptx-ng-control-artwork\"\n\t\t\t\t\t\tdata-pptx-handle-artwork\n\t\t\t\t\t\t[ngStyle]=\"controlArtwork.artwork\"\n\t\t\t\t\t></span>\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t@if (marqueeRect(); as mr) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-marquee\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"mr.x\"\n\t\t\t\t\t[style.top.px]=\"mr.y\"\n\t\t\t\t\t[style.width.px]=\"mr.width\"\n\t\t\t\t\t[style.height.px]=\"mr.height\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (g of snapGuides(); track $index) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-snap-guide\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos : g.start\"\n\t\t\t\t\t[style.top.px]=\"g.axis === 'x' ? g.start : g.pos\"\n\t\t\t\t\t[style.width.px]=\"g.axis === 'x' ? 0 : g.end - g.start\"\n\t\t\t\t\t[style.height.px]=\"g.axis === 'x' ? g.end - g.start : 0\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@if (rotateHandle(); as rh) {\n\t\t\t\t@let rotateArtwork = selectionControlArtwork(rh);\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-rotate-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[style.z-index]=\"editingBox() ? 10003 : null\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"rotate\"\n\t\t\t\t\t[pptxRotateHandleFor]=\"singleSelected()?.id\"\n\t\t\t\t\t[pptxRotateHandleStage]=\"stage\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.rotate' | translate\"\n\t\t\t\t\t[ngStyle]=\"rotateArtwork.frame\"\n\t\t\t\t\t(pointerdown)=\"onRotatePointerDown($event)\"\n\t\t\t\t\t(keydown)=\"onRotateHandleKeydown($event)\"\n\t\t\t\t>\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"pptx-ng-control-artwork\"\n\t\t\t\t\t\tdata-pptx-handle-artwork\n\t\t\t\t\t\t[ngStyle]=\"rotateArtwork.artwork\"\n\t\t\t\t\t></span>\n\t\t\t\t</button>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tShape-adjustment affordances (amber diamonds), ONE per `a:avLst`\n\t\t\t\tguide. Existence, position and cursor all come from the SHARED\n\t\t\t\t`getShapeAdjustmentHandleDescriptors`, so they appear only for a\n\t\t\t\tgeometry with an adjustable parameter, sit where the other four\n\t\t\t\tbindings put them, and are absent for a shape locked with\n\t\t\t\t`noAdjustHandles`. Dragging one writes `shapeAdjustments`; it does\n\t\t\t\tNOT resize the element.\n\t\t\t-->\n\t\t\t@for (ah of adjustHandles(); track ah.key) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-adjust-handle\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\tdata-pptx-handle-kind=\"adjust\"\n\t\t\t\t\t[attr.data-pptx-adjust-key]=\"ah.key\"\n\t\t\t\t\t[attr.aria-label]=\"'pptx.selectionOverlay.adjust' | translate\"\n\t\t\t\t\t[style.left.px]=\"ah.left\"\n\t\t\t\t\t[style.top.px]=\"ah.top\"\n\t\t\t\t\t[style.width.px]=\"ah.size\"\n\t\t\t\t\t[style.height.px]=\"ah.size\"\n\t\t\t\t\t[style.cursor]=\"ah.cursor\"\n\t\t\t\t\t(pointerdown)=\"onAdjustPointerDown($event, ah)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tPowerPoint's floating \"Chart Elements\"/\"Chart Styles\"/\"Chart\n\t\t\t\tFilters\" quick-action icons, shown just outside the chart's\n\t\t\t\ttop-right corner when it is the single selected element.\n\t\t\t-->\n\t\t\t@if (editable() && quickActionsChartElement(); as chartEl) {\n\t\t\t\t<pptx-chart-quick-actions-overlay\n\t\t\t\t\t[element]=\"chartEl\"\n\t\t\t\t\t[canEdit]=\"editable()\"\n\t\t\t\t\t[zoom]=\"effectiveScalePublic()\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t<!--\n\t\t\t\tConnector endpoint authoring: drag an end onto a shape's connection\n\t\t\t\tpoint to bind it (`a:stCxn` / `a:endCxn`), or off one to detach.\n\t\t\t\tCandidate sites are revealed only while an end is in flight so they\n\t\t\t\tnever obscure the deck at rest.\n\t\t\t-->\n\t\t\t@for (site of connectorSiteCandidates(); track site.key) {\n\t\t\t\t<div\n\t\t\t\t\tclass=\"pptx-ng-connection-site\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[class.is-snapped]=\"site.snapped\"\n\t\t\t\t\tdata-pptx-connection-site\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[style.left.px]=\"site.left\"\n\t\t\t\t\t[style.top.px]=\"site.top\"\n\t\t\t\t\t[style.width.px]=\"site.size\"\n\t\t\t\t\t[style.height.px]=\"site.size\"\n\t\t\t\t></div>\n\t\t\t}\n\t\t\t@for (ep of connectorEndpoints(); track ep.kind) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-connector-endpoint\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t[class.is-attached]=\"ep.attached\"\n\t\t\t\t\t[attr.data-pptx-connector-endpoint]=\"ep.kind\"\n\t\t\t\t\t[attr.data-pptx-connector-attached]=\"ep.attached\"\n\t\t\t\t\t[attr.aria-label]=\"\n\t\t\t\t\t\t(ep.kind === 'start'\n\t\t\t\t\t\t\t? 'pptx.canvas.connectorEndpointStart'\n\t\t\t\t\t\t\t: 'pptx.canvas.connectorEndpointEnd'\n\t\t\t\t\t\t) | translate\n\t\t\t\t\t\"\n\t\t\t\t\t[style.left.px]=\"ep.left\"\n\t\t\t\t\t[style.top.px]=\"ep.top\"\n\t\t\t\t\t[style.width.px]=\"ep.size\"\n\t\t\t\t\t[style.height.px]=\"ep.size\"\n\t\t\t\t\t(pointerdown)=\"onConnectorEndpointPointerDown($event, ep.kind)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t\t@if (editingBox(); as eb) {\n\t\t\t\t@if (inlineListSeed(); as seed) {\n\t\t\t\t\t@for (session of [seed]; track session) {\n\t\t\t\t\t\t<pptx-inline-list-editor\n\t\t\t\t\t\t\t[element]=\"eb.element\"\n\t\t\t\t\t\t\t[seed]=\"session\"\n\t\t\t\t\t\t\t[activationSelection]=\"listActivationSelection()\"\n\t\t\t\t\t\t\t[spellCheck]=\"spellCheck()\"\n\t\t\t\t\t\t\t(textInput)=\"textInput.emit($event)\"\n\t\t\t\t\t\t\t(textCommit)=\"textCommit.emit($event)\"\n\t\t\t\t\t\t\t(textCancel)=\"textCancel.emit()\"\n\t\t\t\t\t\t\t(textFormat)=\"textFormat.emit($event)\"\n\t\t\t\t\t\t\t(listSession)=\"listSession.emit($event)\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t} @else {\n\t\t\t\t\t<textarea\n\t\t\t\t\t\t#textEditor\n\t\t\t\t\t\tdata-inline-editor\n\t\t\t\t\t\tclass=\"pptx-ng-text-editor\"\n\t\t\t\t\t\taria-label=\"Edit slide text\"\n\t\t\t\t\t\t[spellcheck]=\"spellCheck()\"\n\t\t\t\t\t\t[style.left.px]=\"eb.x\"\n\t\t\t\t\t\t[style.top.px]=\"eb.y\"\n\t\t\t\t\t\t[style.width.px]=\"eb.width\"\n\t\t\t\t\t\t[style.height.px]=\"eb.height\"\n\t\t\t\t\t\t(pointerdown)=\"$event.stopPropagation()\"\n\t\t\t\t\t\t(input)=\"onTextInput($event, eb.id)\"\n\t\t\t\t\t\t(blur)=\"commitText($event, eb.id)\"\n\t\t\t\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t\t\t\t></textarea>\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tView overlays: editor aids only, never on thumbnails/preview/presentation.\n\t\t\t\tAll are pointer-events:none so they never break selection/drag.\n\t\t\t\tNone carry data-pptx-element / aria-roledescription / data-pptx-viewport.\n\t\t\t-->\n\t\t\t@if (interactive() && showGrid()) {\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-overlay-grid\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t>\n\t\t\t\t\t<defs>\n\t\t\t\t\t\t<pattern\n\t\t\t\t\t\t\t[attr.id]=\"gridPatternId\"\n\t\t\t\t\t\t\t[attr.width]=\"gridSpacingPx()\"\n\t\t\t\t\t\t\t[attr.height]=\"gridSpacingPx()\"\n\t\t\t\t\t\t\tpatternUnits=\"userSpaceOnUse\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t[attr.cx]=\"gridSpacingPx() / 2\"\n\t\t\t\t\t\t\t\t[attr.cy]=\"gridSpacingPx() / 2\"\n\t\t\t\t\t\t\t\tr=\"0.6\"\n\t\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.55)\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</pattern>\n\t\t\t\t\t</defs>\n\t\t\t\t\t<rect\n\t\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t\t\t[attr.fill]=\"'url(#' + gridPatternId + ')'\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t@if (interactive() && showGuides()) {\n\t\t\t\t<!--\n\t\t\t\t\tCenter crosshair: one horizontal line and one vertical line\n\t\t\t\t\tthrough the midpoint of the slide. Static; draggable guides\n\t\t\t\t\tare a follow-up.\n\t\t\t\t-->\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-overlay-guides\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t>\n\t\t\t\t\t<!-- Horizontal center guide -->\n\t\t\t\t\t<line\n\t\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t\t[attr.y1]=\"canvasSize().height / 2\"\n\t\t\t\t\t\t[attr.x2]=\"canvasSize().width\"\n\t\t\t\t\t\t[attr.y2]=\"canvasSize().height / 2\"\n\t\t\t\t\t\tstroke=\"rgba(99,102,241,0.7)\"\n\t\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t\t\tstroke-dasharray=\"6 3\"\n\t\t\t\t\t/>\n\t\t\t\t\t<!-- Vertical center guide -->\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"canvasSize().width / 2\"\n\t\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t\t[attr.x2]=\"canvasSize().width / 2\"\n\t\t\t\t\t\t[attr.y2]=\"canvasSize().height\"\n\t\t\t\t\t\tstroke=\"rgba(99,102,241,0.7)\"\n\t\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t\t\tstroke-dasharray=\"6 3\"\n\t\t\t\t\t/>\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tUser-created ruler guides.\n\t\t\t\tEach guide has a non-interactive line body and an interactive\n\t\t\t\tdrag handle. Double-click the handle to delete the guide.\n\n\t\t\t\tDeliberately OUTSIDE the showGuides() gate above (which owns the\n\t\t\t\tstatic centre crosshair): a guide the user just dragged off a ruler\n\t\t\t\tstrip must be visible immediately, as it is in Vue and Vanilla.\n\t\t\t\tWhile it lived under that gate, the drag-out gesture silently\n\t\t\t\tproduced an invisible guide unless View > Guides happened to be on.\n\t\t\t-->\n\t\t\t@if (interactive()) {\n\t\t\t\t@for (g of rulerGuidesSvc.rulerGuides(); track g.id) {\n\t\t\t\t\t<!-- Guide line body: pointer-events:none -->\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"pptx-ng-ruler-guide-line\"\n\t\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos : 0\"\n\t\t\t\t\t\t[style.top.px]=\"g.axis === 'y' ? g.pos : 0\"\n\t\t\t\t\t\t[style.width]=\"g.axis === 'x' ? '1px' : '100%'\"\n\t\t\t\t\t\t[style.height]=\"g.axis === 'y' ? '1px' : '100%'\"\n\t\t\t\t\t></div>\n\t\t\t\t\t<!-- Drag handle: pointer-events:auto -->\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"pptx-ng-ruler-guide-handle\"\n\t\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\t\t[style.left.px]=\"g.axis === 'x' ? g.pos - 4 : 0\"\n\t\t\t\t\t\t[style.top.px]=\"g.axis === 'y' ? g.pos - 4 : 0\"\n\t\t\t\t\t\t[style.width]=\"g.axis === 'x' ? '9px' : '100%'\"\n\t\t\t\t\t\t[style.height]=\"g.axis === 'y' ? '9px' : '100%'\"\n\t\t\t\t\t\t[style.cursor]=\"g.axis === 'x' ? 'col-resize' : 'row-resize'\"\n\t\t\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onGuidePointerDown($event, g.id, g.axis)\"\n\t\t\t\t\t\t(dblclick)=\"rulerGuidesSvc.onGuideDoubleClick($event, g.id)\"\n\t\t\t\t\t\t[title]=\"'pptx.canvas.guideTooltip' | translate\"\n\t\t\t\t\t></div>\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tLive ink stroke preview: shown while the user is drawing.\n\t\t\t\tpointer-events:none so it never intercepts element gestures.\n\t\t\t\tNo data-pptx-element / aria-roledescription / data-pptx-viewport.\n\n\t\t\t\tRenders `inkDrawing.liveStrokeView()`: the same plain-path /\n\t\t\t\tpressure-circle / tilt-nib decision `InkRendererComponent` makes for\n\t\t\t\ta committed stroke, so a calligraphic lean or pressure-variable\n\t\t\t\twidth shows up while the pointer is still down, not only after\n\t\t\t\trelease.\n\t\t\t-->\n\t\t\t@if (inkDrawing.active() && inkDrawing.liveStrokeView() && drawTool() !== 'select') {\n\t\t\t\t<svg\n\t\t\t\t\tclass=\"pptx-ng-ink-preview\"\n\t\t\t\t\tdata-export-ignore=\"true\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t[attr.width]=\"canvasSize().width\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height\"\n\t\t\t\t\tstyle=\"position: absolute; inset: 0; pointer-events: none; z-index: 70\"\n\t\t\t\t>\n\t\t\t\t\t@if (inkDrawing.liveStrokeView()?.nibMarks; as nibMarks) {\n\t\t\t\t\t\t<g [attr.opacity]=\"inkDrawing.liveStrokeView()?.opacity\">\n\t\t\t\t\t\t\t@for (m of nibMarks; track $index) {\n\t\t\t\t\t\t\t\t<ellipse\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"m.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"m.cy\"\n\t\t\t\t\t\t\t\t\t[attr.rx]=\"m.rPerp\"\n\t\t\t\t\t\t\t\t\t[attr.ry]=\"m.rTilt\"\n\t\t\t\t\t\t\t\t\t[attr.transform]=\"'rotate(' + m.rotationDeg + ' ' + m.cx + ' ' + m.cy + ')'\"\n\t\t\t\t\t\t\t\t\t[attr.fill]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t} @else if (inkDrawing.liveStrokeView()?.circles; as circles) {\n\t\t\t\t\t\t<g [attr.opacity]=\"inkDrawing.liveStrokeView()?.opacity\">\n\t\t\t\t\t\t\t@for (c of circles; track $index) {\n\t\t\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"c.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"c.cy\"\n\t\t\t\t\t\t\t\t\t[attr.r]=\"c.r\"\n\t\t\t\t\t\t\t\t\t[attr.fill]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</g>\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t[attr.d]=\"inkDrawing.liveStrokeView()?.d\"\n\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t[attr.stroke]=\"inkDrawing.liveStrokeView()?.color\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"inkDrawing.liveStrokeView()?.width\"\n\t\t\t\t\t\t\t[attr.stroke-opacity]=\"inkDrawing.liveStrokeView()?.opacity\"\n\t\t\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t</svg>\n\t\t\t}\n\n\t\t\t<!--\n\t\t\t\tProjected overlays (collaboration cursors + remote selections).\n\t\t\t\tThey live INSIDE the scaled stage so the stage's CSS\n\t\t\t\ttransform:scale() applies the on-screen scale exactly once and\n\t\t\t\tthey can be authored in raw, unscaled slide coordinates, exactly\n\t\t\t\tlike the React/Vue overlays.\n\t\t\t-->\n\t\t\t<ng-content />\n\t\t</div>\n\n\t\t<!--\n\t\t\tRuler strips: siblings to the scaled stage inside the wrapper div,\n\t\t\tabsolutely positioned at top:0/left:0 within the wrapper's padding area.\n\t\t\tThe wrapper's padding (RULER_THICKNESS px top+left) reserves space for\n\t\t\tthese strips so the stage content starts below/right of them.\n\t\t\tpointer-events:none so they never intercept element gestures.\n\t\t-->\n\t\t@if (interactive() && showRulers()) {\n\t\t\t<!-- Corner square at the intersection of the two ruler strips -->\n\t\t\t<div class=\"pptx-ng-ruler-corner\" aria-hidden=\"true\"></div>\n\n\t\t\t<!--\n\t\t\t\tHorizontal ruler: spans the top padding row of the wrapper.\n\t\t\t\tA guide is dropped on pointer-UP (not down), resolved by the shared\n\t\t\t\trulerDragToGuidePosition, so a stray click on the strip cannot\n\t\t\t\tdrop a guide the user never dragged out - the rule React, Vue,\n\t\t\t\tSvelte and Vanilla all follow.\n\t\t\t-->\n\t\t\t<svg\n\t\t\t\tclass=\"pptx-ng-ruler-h\"\n\t\t\t\tdata-pptx-ruler=\"h\"\n\t\t\t\trole=\"presentation\"\n\t\t\t\t[attr.width]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t[style.cursor]=\"editable() ? 'row-resize' : null\"\n\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onRulerPointerDown('h', $event)\"\n\t\t\t\t(pointerup)=\"rulerGuidesSvc.onRulerPointerUp('h', $event)\"\n\t\t\t>\n\t\t\t\t<rect\n\t\t\t\t\t[attr.width]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t\tfill=\"#1e293b\"\n\t\t\t\t/>\n\t\t\t\t@if (hRulerHighlight(); as highlight) {\n\t\t\t\t\t<rect\n\t\t\t\t\t\t[attr.x]=\"highlight.start\"\n\t\t\t\t\t\ty=\"0\"\n\t\t\t\t\t\t[attr.width]=\"highlight.span\"\n\t\t\t\t\t\t[attr.height]=\"rulerThickness\"\n\t\t\t\t\t\tfill=\"rgba(96,165,250,0.25)\"\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\t<line\n\t\t\t\t\tx1=\"0\"\n\t\t\t\t\t[attr.y1]=\"rulerThickness - 0.5\"\n\t\t\t\t\t[attr.x2]=\"canvasSize().width * effectiveScalePublic()\"\n\t\t\t\t\t[attr.y2]=\"rulerThickness - 0.5\"\n\t\t\t\t\tstroke=\"rgba(255,255,255,0.15)\"\n\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t/>\n\t\t\t\t@for (tick of hRulerTicks(); track $index) {\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"tick.position\"\n\t\t\t\t\t\t[attr.y1]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.x2]=\"tick.position\"\n\t\t\t\t\t\t[attr.y2]=\"rulerThickness - rulerThickness * (tick.isMajor ? 0.6 : 0.3)\"\n\t\t\t\t\t\tstroke=\"rgba(156,163,175,0.7)\"\n\t\t\t\t\t\t[attr.stroke-width]=\"tick.isMajor ? 1 : 0.5\"\n\t\t\t\t\t/>\n\t\t\t\t\t@if (tick.label) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t[attr.x]=\"tick.position + 2\"\n\t\t\t\t\t\t\t[attr.y]=\"rulerFontSize + 1\"\n\t\t\t\t\t\t\t[attr.font-size]=\"rulerFontSize\"\n\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.9)\"\n\t\t\t\t\t\t\tstyle=\"font-family: system-ui, sans-serif\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ tick.label }}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</svg>\n\n\t\t\t<!-- Vertical ruler: spans the left padding column of the wrapper -->\n\t\t\t<svg\n\t\t\t\tclass=\"pptx-ng-ruler-v\"\n\t\t\t\tdata-pptx-ruler=\"v\"\n\t\t\t\trole=\"presentation\"\n\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t[attr.height]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t[style.cursor]=\"editable() ? 'col-resize' : null\"\n\t\t\t\t(pointerdown)=\"rulerGuidesSvc.onRulerPointerDown('v', $event)\"\n\t\t\t\t(pointerup)=\"rulerGuidesSvc.onRulerPointerUp('v', $event)\"\n\t\t\t>\n\t\t\t\t<rect\n\t\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t\t[attr.height]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t\tfill=\"#1e293b\"\n\t\t\t\t/>\n\t\t\t\t@if (vRulerHighlight(); as highlight) {\n\t\t\t\t\t<rect\n\t\t\t\t\t\tx=\"0\"\n\t\t\t\t\t\t[attr.y]=\"highlight.start\"\n\t\t\t\t\t\t[attr.width]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.height]=\"highlight.span\"\n\t\t\t\t\t\tfill=\"rgba(96,165,250,0.25)\"\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\t<line\n\t\t\t\t\t[attr.x1]=\"rulerThickness - 0.5\"\n\t\t\t\t\ty1=\"0\"\n\t\t\t\t\t[attr.x2]=\"rulerThickness - 0.5\"\n\t\t\t\t\t[attr.y2]=\"canvasSize().height * effectiveScalePublic()\"\n\t\t\t\t\tstroke=\"rgba(255,255,255,0.15)\"\n\t\t\t\t\tstroke-width=\"1\"\n\t\t\t\t/>\n\t\t\t\t@for (tick of vRulerTicks(); track $index) {\n\t\t\t\t\t<line\n\t\t\t\t\t\t[attr.x1]=\"rulerThickness\"\n\t\t\t\t\t\t[attr.y1]=\"tick.position\"\n\t\t\t\t\t\t[attr.x2]=\"rulerThickness - rulerThickness * (tick.isMajor ? 0.6 : 0.3)\"\n\t\t\t\t\t\t[attr.y2]=\"tick.position\"\n\t\t\t\t\t\tstroke=\"rgba(156,163,175,0.7)\"\n\t\t\t\t\t\t[attr.stroke-width]=\"tick.isMajor ? 1 : 0.5\"\n\t\t\t\t\t/>\n\t\t\t\t\t@if (tick.label) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\tx=\"2\"\n\t\t\t\t\t\t\t[attr.y]=\"tick.position + rulerFontSize + 2\"\n\t\t\t\t\t\t\t[attr.font-size]=\"rulerFontSize\"\n\t\t\t\t\t\t\tfill=\"rgba(156,163,175,0.9)\"\n\t\t\t\t\t\t\tstyle=\"font-family: system-ui, sans-serif\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ tick.label }}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</svg>\n\t\t}\n\t</div>\n</div>\n", styles: [".pptx-ng-canvas-stage.is-editable{touch-action:none}\n"] }]
130055
130467
  }], ctorParameters: () => [], propDecorators: { slide: [{ type: i0.Input, args: [{ isSignal: true, alias: "slide", required: false }] }], canvasSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "canvasSize", required: true }] }], mediaDataUrls: [{ type: i0.Input, args: [{ isSignal: true, alias: "mediaDataUrls", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], fitPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "fitPadding", required: false }] }], maxFitScale: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFitScale", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], showGrid: [{ type: i0.Input, args: [{ isSignal: true, alias: "showGrid", required: false }] }], showRulers: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRulers", required: false }] }], rulerUnit: [{ type: i0.Input, args: [{ isSignal: true, alias: "rulerUnit", required: false }] }], showGuides: [{ type: i0.Input, args: [{ isSignal: true, alias: "showGuides", required: false }] }], snapToGrid: [{ type: i0.Input, args: [{ isSignal: true, alias: "snapToGrid", required: false }] }], gridSpacing: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridSpacing", required: false }] }], snapToShape: [{ type: i0.Input, args: [{ isSignal: true, alias: "snapToShape", required: false }] }], guideCommand: [{ type: i0.Input, args: [{ isSignal: true, alias: "guideCommand", required: false }] }], spellCheck: [{ type: i0.Input, args: [{ isSignal: true, alias: "spellCheck", required: false }] }], snapToGuides: [{ type: i0.Input, args: [{ isSignal: true, alias: "snapToGuides", required: false }] }], autoFit: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoFit", required: false }] }], transparentBackground: [{ type: i0.Input, args: [{ isSignal: true, alias: "transparentBackground", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], exposeElementIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "exposeElementIds", required: false }] }], presenting: [{ type: i0.Input, args: [{ isSignal: true, alias: "presenting", required: false }] }], selectedIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedIds", required: false }] }], editingId: [{ type: i0.Input, args: [{ isSignal: true, alias: "editingId", required: false }] }], editTemplateMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "editTemplateMode", required: false }] }], templateElements: [{ type: i0.Input, args: [{ isSignal: true, alias: "templateElements", required: false }] }], aiHighlights: [{ type: i0.Input, args: [{ isSignal: true, alias: "aiHighlights", required: false }] }], aiActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "aiActive", required: false }] }], aiActiveSlideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "aiActiveSlideIndex", required: false }] }], aiChangeBatch: [{ type: i0.Input, args: [{ isSignal: true, alias: "aiChangeBatch", required: false }] }], aiPickMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "aiPickMode", required: false }] }], drawTool: [{ type: i0.Input, args: [{ isSignal: true, alias: "drawTool", required: false }] }], drawColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "drawColor", required: false }] }], drawWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "drawWidth", required: false }] }], elementSelect: [{ type: i0.Output, args: ["elementSelect"] }], backgroundClick: [{ type: i0.Output, args: ["backgroundClick"] }], transformStart: [{ type: i0.Output, args: ["transformStart"] }], transformUpdate: [{ type: i0.Output, args: ["transformUpdate"] }], transformEnd: [{ type: i0.Output, args: ["transformEnd"] }], adjustUpdate: [{ type: i0.Output, args: ["adjustUpdate"] }], connectorEndpointUpdate: [{ type: i0.Output, args: ["connectorEndpointUpdate"] }], contextMenu: [{ type: i0.Output, args: ["contextMenu"] }], textEditStart: [{ type: i0.Output, args: ["textEditStart"] }], textCommit: [{ type: i0.Output, args: ["textCommit"] }], textInput: [{ type: i0.Output, args: ["textInput"] }], textCancel: [{ type: i0.Output, args: ["textCancel"] }], textFormat: [{ type: i0.Output, args: ["textFormat"] }], listSession: [{ type: i0.Output, args: ["listSession"] }], rotateUpdate: [{ type: i0.Output, args: ["rotateUpdate"] }], marqueeSelect: [{ type: i0.Output, args: ["marqueeSelect"] }], inkStrokeComplete: [{ type: i0.Output, args: ["inkStrokeComplete"] }], eraserHit: [{ type: i0.Output, args: ["eraserHit"] }], cellCommit: [{ type: i0.Output, args: ["cellCommit"] }], tableChange: [{ type: i0.Output, args: ["tableChange"] }], textEditor: [{ type: i0.ViewChild, args: ['textEditor', { isSignal: true }] }], listEditor: [{ type: i0.ViewChild, args: [i0.forwardRef(() => InlineListEditorComponent), { isSignal: true }] }], stageRef: [{ type: i0.ViewChild, args: ['stage', { isSignal: true }] }], viewportRef: [{ type: i0.ViewChild, args: ['viewport', { isSignal: true }] }], onPointerMove: [{
130056
130468
  type: HostListener,
130057
130469
  args: ['document:pointermove', ['$event']]
@@ -134078,6 +134490,20 @@ class ViewerCanvasEditingService {
134078
134490
  endInlineListSession() {
134079
134491
  this.listSession.end();
134080
134492
  }
134493
+ /** Keep the last accepted input visible after a readiness or permission loss. */
134494
+ suspendInlineEdit() {
134495
+ const id = this.editingId();
134496
+ if (id && this.acceptedText !== undefined) {
134497
+ const host = this.requireHost();
134498
+ const patch = buildInlineTextCommitPatch(this.findElement(host, id), this.acceptedText, this.inlineSnapshot);
134499
+ if (patch) {
134500
+ this.editor.updateElement(host.activeSlideIndex(), id, patch);
134501
+ }
134502
+ }
134503
+ this.acceptedText = undefined;
134504
+ this.inlineSnapshot = undefined;
134505
+ this.listSession.end();
134506
+ }
134081
134507
  readInlineSnapshot() {
134082
134508
  const current = this.listSession.read();
134083
134509
  if (current && this.editingId()) {
@@ -134110,7 +134536,11 @@ class ViewerCanvasEditingService {
134110
134536
  */
134111
134537
  onTextEditStart(id) {
134112
134538
  this.inlineSnapshot = undefined;
134539
+ this.acceptedText = undefined;
134113
134540
  const host = this.requireHost();
134541
+ if (!host.canEdit()) {
134542
+ return;
134543
+ }
134114
134544
  const element = this.findElement(host, id);
134115
134545
  const segments = element && 'textSegments' in element ? element.textSegments : undefined;
134116
134546
  const equation = segments?.find((segment) => segment.equationXml);
@@ -134146,6 +134576,9 @@ class ViewerCanvasEditingService {
134146
134576
  * collaborating.
134147
134577
  */
134148
134578
  onTextInput(event) {
134579
+ if (!this.requireHost().canEdit()) {
134580
+ return;
134581
+ }
134149
134582
  const current = this.listSession.read();
134150
134583
  if (event.id !== this.editingId()) {
134151
134584
  return;
@@ -134153,6 +134586,7 @@ class ViewerCanvasEditingService {
134153
134586
  if (current) {
134154
134587
  event = { id: event.id, text: current.text, snapshot: current };
134155
134588
  }
134589
+ this.acceptedText = event.text;
134156
134590
  this.inlineSnapshot =
134157
134591
  event.id === this.editingId() &&
134158
134592
  event.snapshot?.elementId === event.id &&
@@ -134165,6 +134599,10 @@ class ViewerCanvasEditingService {
134165
134599
  /** Commit an inline text edit without flattening its rich-text runs. */
134166
134600
  onTextCommit(event) {
134167
134601
  const host = this.requireHost();
134602
+ if (!host.canEdit()) {
134603
+ this.suspendInlineEdit();
134604
+ return;
134605
+ }
134168
134606
  // Push any queued interim frame out first so it cannot land after the
134169
134607
  // committed text and revert it.
134170
134608
  this.collab.livePatcher.flush();
@@ -148743,7 +149181,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
148743
149181
  }], propDecorators: { canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], slideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideIndex", required: false }] }], selectedElement: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedElement", required: false }] }] } });
148744
149182
 
148745
149183
  // Generated by scripts/inline-shared.mjs from package.json. Do not edit.
148746
- const PPTX_ANGULAR_VIEWER_VERSION = "3.19.2";
149184
+ const PPTX_ANGULAR_VIEWER_VERSION = "3.20.1";
148747
149185
 
148748
149186
  /**
148749
149187
  * account-page.component.ts: File > Account content.
@@ -155029,8 +155467,8 @@ class AnimationTimelineComponent {
155029
155467
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: AnimationTimelineComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
155030
155468
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: AnimationTimelineComponent, isStandalone: true, selector: "pptx-animation-timeline", inputs: { animations: { classPropertyName: "animations", publicName: "animations", isSignal: true, isRequired: true, transformFunction: null }, elements: { classPropertyName: "elements", publicName: "elements", isSignal: true, isRequired: false, transformFunction: null }, animationTimelineAnchors: { classPropertyName: "animationTimelineAnchors", publicName: "animationTimelineAnchors", isSignal: true, isRequired: false, transformFunction: null }, selectedElementId: { classPropertyName: "selectedElementId", publicName: "selectedElementId", isSignal: true, isRequired: false, transformFunction: null }, canEdit: { classPropertyName: "canEdit", publicName: "canEdit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { animationsChange: "animationsChange" }, ngImport: i0, template: `
155031
155469
  @if (rows().length) {
155032
- <section class="timeline" aria-label="Animation timeline">
155033
- <h4>Timeline</h4>
155470
+ <section class="timeline" [attr.aria-label]="'pptx.animation.timeline' | translate">
155471
+ <h4>{{ 'pptx.animation.timeline' | translate }}</h4>
155034
155472
  <div class="bar" aria-hidden="true">
155035
155473
  @for (item of bars(); track item.elementId) {
155036
155474
  <span
@@ -155083,8 +155521,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
155083
155521
  type: Component,
155084
155522
  args: [{ selector: 'pptx-animation-timeline', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [TranslatePipe], template: `
155085
155523
  @if (rows().length) {
155086
- <section class="timeline" aria-label="Animation timeline">
155087
- <h4>Timeline</h4>
155524
+ <section class="timeline" [attr.aria-label]="'pptx.animation.timeline' | translate">
155525
+ <h4>{{ 'pptx.animation.timeline' | translate }}</h4>
155088
155526
  <div class="bar" aria-hidden="true">
155089
155527
  @for (item of bars(); track item.elementId) {
155090
155528
  <span
@@ -180887,7 +181325,7 @@ class PowerPointViewerComponent {
180887
181325
  ...(ngDevMode ? [{ debugName: "hasEditPermission" }] : /* istanbul ignore next */ []));
180888
181326
  this.editingRequested = signal(true, /* @ts-ignore */
180889
181327
  ...(ngDevMode ? [{ debugName: "editingRequested" }] : /* istanbul ignore next */ []));
180890
- this.canEdit = computed(() => this.hasEditPermission() && this.editingRequested(), /* @ts-ignore */
181328
+ this.canEdit = computed(() => this.hasEditPermission() && this.editingRequested() && !this.collab.readOnly(), /* @ts-ignore */
180891
181329
  ...(ngDevMode ? [{ debugName: "canEdit" }] : /* istanbul ignore next */ []));
180892
181330
  /** Whether the Protected View banner should show: host allows editing, the option still blocks it, and the user hasn't dismissed it yet. */
180893
181331
  this.protectedViewActive = computed(() => this.canEditInput() &&
@@ -180924,8 +181362,8 @@ class PowerPointViewerComponent {
180924
181362
  */
180925
181363
  this.audienceSessionEnded = signal(false, /* @ts-ignore */
180926
181364
  ...(ngDevMode ? [{ debugName: "audienceSessionEnded" }] : /* istanbul ignore next */ []));
180927
- /** Preview changes interaction, not which permitted live deck is displayed. */
180928
- this.displaySlides = computed(() => this.hasEditPermission() ? this.editor.slides() : this.loader.slides(), /* @ts-ignore */
181365
+ /** Read-only interaction does not hide an active collaboration's live deck. */
181366
+ this.displaySlides = computed(() => this.hasEditPermission() || this.collab.active() ? this.editor.slides() : this.loader.slides(), /* @ts-ignore */
180929
181367
  ...(ngDevMode ? [{ debugName: "displaySlides" }] : /* istanbul ignore next */ []));
180930
181368
  this.slideCount = computed(() => this.displaySlides().length, /* @ts-ignore */
180931
181369
  ...(ngDevMode ? [{ debugName: "slideCount" }] : /* istanbul ignore next */ []));
@@ -180935,7 +181373,7 @@ class PowerPointViewerComponent {
180935
181373
  * consumer that needs the COMPLETE slide (export, print, slide thumbnails,
180936
181374
  * accessibility) renders this instead so template elements are not lost.
180937
181375
  */
180938
- this.mergedSlides = computed(() => this.hasEditPermission()
181376
+ this.mergedSlides = computed(() => this.hasEditPermission() || this.collab.active()
180939
181377
  ? buildSaveSlides(this.editor.slides(), this.editor.templateElementsBySlideId())
180940
181378
  : this.loader.slides(), /* @ts-ignore */
180941
181379
  ...(ngDevMode ? [{ debugName: "mergedSlides" }] : /* istanbul ignore next */ []));
@@ -180944,10 +181382,10 @@ class PowerPointViewerComponent {
180944
181382
  ...(ngDevMode ? [{ debugName: "displaySlidesMut" }] : /* istanbul ignore next */ []));
180945
181383
  this.activeSlide = computed(() => this.displaySlides()[this.activeSlideIndex()], /* @ts-ignore */
180946
181384
  ...(ngDevMode ? [{ debugName: "activeSlide" }] : /* istanbul ignore next */ []));
180947
- /** Inherited template (master/layout) elements for the active slide, when editing. */
181385
+ /** Inherited template elements for the live editor or collaboration slide source. */
180948
181386
  this.activeTemplateElements = computed(() => {
180949
181387
  const slide = this.activeSlide();
180950
- if (!this.hasEditPermission() || !slide) {
181388
+ if ((!this.hasEditPermission() && !this.collab.active()) || !slide) {
180951
181389
  return [];
180952
181390
  }
180953
181391
  return this.editor.templateElementsBySlideId()[slide.id] ?? [];
@@ -181745,7 +182183,8 @@ class PowerPointViewerComponent {
181745
182183
  // component (canEdit, the host `content` input, the File ▸ Open override,
181746
182184
  // the editor's slides + template elements, and the contentChange emitter).
181747
182185
  this.fileIO.bind({
181748
- canEdit: () => this.hasEditPermission(),
182186
+ // This callback selects the save source, not interaction permission.
182187
+ canEdit: () => this.hasEditPermission() || this.collab.active(),
181749
182188
  content: () => this.content(),
181750
182189
  onOpenFile: () => this.onOpenFile(),
181751
182190
  slides: () => this.editor.slides(),
@@ -181822,6 +182261,11 @@ class PowerPointViewerComponent {
181822
182261
  activeSlideIndex: () => this.activeSlideIndex(),
181823
182262
  activeTemplateElements: () => this.activeTemplateElements(),
181824
182263
  });
182264
+ effect(() => {
182265
+ if (!this.canEdit()) {
182266
+ untracked(() => this.canvasEditing.suspendInlineEdit());
182267
+ }
182268
+ });
181825
182269
  const tableSelection = inject(TableSelectionService);
181826
182270
  setupViewerImagePaste({
181827
182271
  rootElement: () => this.viewerRoot()?.nativeElement,
@@ -187008,4 +187452,4 @@ function cn(...values) {
187008
187452
  */
187009
187453
 
187010
187454
  export { CollaborationCursorsComponent as $, AFTER_ANIMATION_VALUES as A, AnimationAuthorPanelComponent as B, AnimationPanelComponent as C, AnimationPlaybackService as D, AutosaveRecoveryDialogComponent as E, AutosaveService as F, BroadcastDialogComponent as G, CHART_EDITOR_STYLES as H, CINEMATIC_TRANSITION_KEYFRAMES as I, CURSOR_PALETTE as J, CanvasFitService as K, ChartAxisOptionsComponent as L, ChartAxisStyleOptionsComponent as M, ChartComboTypeOptionsComponent as N, ChartDataEditorComponent as O, ChartDataLabelOptionsComponent as P, ChartDatapointMarkerOptionsComponent as Q, ChartDatapointOptionsComponent as R, ChartDisplayOptionsComponent as S, ChartElementViewComponent as T, ChartErrorBarOptionsComponent as U, ChartMarkerOptionsComponent as V, ChartPartSelectionService as W, ChartPrimitivesComponent as X, ChartRendererComponent as Y, ChartTrendlineOptionsComponent as Z, ChartTypeSelectorComponent as _, ALIGN_OPTIONS as a, INSTANT as a$, CollaborationService as a0, ColorChangedImageComponent as a1, CommentMarkersOverlayComponent as a2, CommentsPanelComponent as a3, CommentsService as a4, ComparePanelComponent as a5, ConnectorRendererComponent as a6, ConnectorTextOverlayComponent as a7, CustomShowsComponent as a8, DEFAULT_BOUNDS as a9, EditorContextMenuComponent as aA, EditorHistory as aB, EditorStateService as aC, EditorToolbarComponent as aD, EffectsPanelComponent as aE, ElementRendererComponent as aF, EmbeddedFontsService as aG, EncryptedFileDialogComponent as aH, EquationEditorDialogComponent as aI, EquationRendererComponent as aJ, EquationTemplateGalleryComponent as aK, ExportProgressModalComponent as aL, ExportService as aM, FieldContextService as aN, FindBarComponent as aO, FindReplaceBarComponent as aP, FollowModeBarComponent as aQ, FontEmbeddingListComponent as aR, FontEmbeddingPanelComponent as aS, GALLERY_THEME_PRESETS as aT, GOOGLE_WEBFONTS_LINK_ID as aU, GRIDLINE_COLOR$1 as aV, GoogleWebfontsService as aW, GradientPickerComponent as aX, HANDOUT_OPTIONS as aY, HeaderFooterDialogComponent as aZ, HyperlinkDialogComponent as a_, DEFAULT_BROADCAST_SERVER_URL as aa, DEFAULT_CANVAS_HEIGHT as ab, DEFAULT_CANVAS_WIDTH as ac, DEFAULT_COLOR_SCHEME as ad, DEFAULT_FILL_COLOR$1 as ae, DEFAULT_LAYOUT as af, DEFAULT_MORPH_DURATION_MS as ag, DEFAULT_PALETTE$1 as ah, DEFAULT_PATTERN_FILL_PRESET as ai, DEFAULT_PRINT_SETTINGS as aj, DEFAULT_SLIDE_BACKGROUND as ak, DEFAULT_STROKE_COLOR as al, DEFAULT_STYLE as am, DEFAULT_TABLE_ROW_HEIGHT as an, DEFAULT_TEXT_COLOR$2 as ao, DEFAULT_VIEWER_PROFILE as ap, DIRECTIONAL_PRESETS as aq, DIRECTION_OPTIONS as ar, DocumentPropertiesCardComponent as as, EASE as at, EFFECT_SOUND_CATALOGUE as au, EMBEDDED_FONTS_STYLE_ID as av, EMPHASIS_PRESETS as aw, ENTRANCE_PRESETS as ax, TEMPLATES as ay, EXIT_PRESETS as az, ANIMATION_PRESET_CATEGORIES as b, REPEAT_MODE_OPTIONS as b$, ImagePropertiesPanelComponent as b0, InkDrawingService as b1, InkRendererComponent as b2, InsertSmartArtDialogComponent as b3, InspectorPaneHeaderComponent as b4, InspectorPanelComponent as b5, IsMobileService as b6, KeepAnnotationsDialogComponent as b7, LOCALE_CATALOG as b8, LONG_PRESS_DURATION_MS as b9, POWER_POINT_VIEWER_PROVIDERS as bA, PPTX_OPEN_ACCEPT as bB, PRESENTATION_OPEN_EXTENSIONS as bC, PRESENTER_CHANNEL_NAME as bD, PRESENTER_MSG_ORIGIN as bE, PRESENTER_TIMER_SEGMENT_MS as bF, PX_PER_CM as bG, PX_PER_INCH as bH, PasswordProtectionDialogComponent as bI, PasswordStrengthMeterComponent as bJ, PowerPointViewerComponent as bK, PresentToolbarAutoHide as bL, PresentationAnnotationOverlayComponent as bM, PresentationAnnotationsService as bN, PresentationOverlayComponent as bO, PresentationPropertiesPanelComponent as bP, PresentationSettingsCardComponent as bQ, PresentationSubtitleBarComponent as bR, PresentationToolbarComponent as bS, PresentationTransitionOverlayComponent as bT, PresenterViewComponent as bU, PresenterWindowService as bV, PrintDialogComponent as bW, PrintService as bX, PrintSettingsPanelComponent as bY, PropertiesDialogComponent as bZ, RANDOM_ELIGIBLE_TYPES as b_, LONG_PRESS_MOVE_TOLERANCE_PX as ba, LoadContentService as bb, LocalPresencePublisher as bc, MAX_ZOOM_SCALE as bd, MIN_ZOOM_SCALE as be, MOTION_PATH_COLUMNS as bf, MediaPreviewComponent as bg, MediaPropertiesPanelComponent as bh, MediaRendererComponent as bi, MediaTrimTimelineComponent as bj, MobileBottomBarComponent as bk, MobileMenuSheetComponent as bl, MobilePresenterViewComponent as bm, MobileSheetComponent as bn, MobileSlidesSheetComponent as bo, MobileToolbarComponent as bp, ModalDialogComponent as bq, Model3DRendererComponent as br, NotesHandoutCardComponent as bs, NotesPanelComponent as bt, NotesToolbarComponent as bu, OleRendererComponent as bv, OutlineViewOverlayComponent as bw, P14_TRANSITION_KEYFRAMES as bx, P14_TRANSITION_KEYFRAMES_2 as by, P14_TRANSITION_KEYFRAMES_ALL as bz, AUDIENCE_HASH as c, SlideSorterOverlayComponent as c$, RESIZE_HANDLES as c0, RULER_FONT_SIZE as c1, RULER_THICKNESS as c2, ReadingViewOverlayComponent as c3, RemoteSelectionOverlayComponent as c4, RibbonAnimationGalleryComponent as c5, RibbonAnimationsSectionComponent as c6, RibbonArrangeSectionComponent as c7, RibbonColorPopoverComponent as c8, RibbonComponent as c9, DEFAULT_PALETTE as cA, PALETTES$1 as cB, SMART_ART_COLOR_SCHEMES as cC, SMART_ART_STYLE_OPTIONS as cD, SUB_ITEM_LABEL as cE, SVG_WARP_PRESETS as cF, SWIPE_MAX_VERTICAL_PX as cG, SWIPE_THRESHOLD_PX as cH, SelectionPaneComponent as cI, SetUpSlideShowDialogComponent as cJ, SettingsAppearanceTabComponent as cK, SettingsDialogComponent as cL, SettingsLanguageTabComponent as cM, ShareDialogComponent as cN, ShortcutPanelComponent as cO, ShowOptionsFieldsetComponent as cP, ShowSlidesFieldsetComponent as cQ, SignatureStrippedDialogComponent as cR, SignaturesPanelComponent as cS, SignaturesService as cT, SlideBackgroundCardComponent as cU, SlideCanvasComponent as cV, SlideDefaultInspectorComponent as cW, SlideDiffChangesComponent as cX, SlideDiffRowComponent as cY, SlideDiffThumbnailsComponent as cZ, SlideSizeCardComponent as c_, RibbonDesignSectionComponent as ca, RibbonDrawSectionComponent as cb, RibbonDrawingGroupComponent as cc, RibbonEditingSectionComponent as cd, RibbonFileSectionComponent as ce, RibbonFontControlsComponent as cf, RibbonHomeSectionComponent as cg, RibbonHyperlinkButtonComponent as ch, RibbonInsertFieldsComponent as ci, RibbonInsertSectionComponent as cj, RibbonMotionPathGalleryComponent as ck, RibbonParagraphControlsComponent as cl, RibbonPrimaryRowComponent as cm, RibbonReviewSectionComponent as cn, RibbonShapeExtrasComponent as co, RibbonSlideshowSectionComponent as cp, RibbonTransitionsSectionComponent as cq, RibbonViewSectionComponent as cr, RulerGuidesService as cs, SEQUENCE_OPTIONS as ct, SEVERITY_GROUPS as cu, SEVERITY_LABELS as cv, DEFAULT_TRANSITION_DURATION_MS$1 as cw, SHORTCUT_REFERENCE_ITEMS as cx, SLIDE_TRANSITION_KEYFRAMES as cy, SLIDE_TRANSITION_KEYFRAMES_CSS as cz, AUDIENCE_NONCE_KEY as d, ZoomNavigationService as d$, SlideThemeOverridePanelComponent as d0, SlideTransitionCardComponent as d1, SlidesPanelComponent as d2, SmartArt3DRendererComponent as d3, SmartArt3DService as d4, SmartArtPreviewComponent as d5, SmartArtPropertiesComponent as d6, SmartArtRendererComponent as d7, StatusBarComponent as d8, TABLE_STRUCTURE_TOGGLES as d9, TransitionPreviewComponent as dA, VALIGN_OPTIONS as dB, VIEWER_THEME as dC, VersionHistoryPanelComponent as dD, ViewerCanvasEditingService as dE, ViewerCollabCursorService as dF, ViewerCollaborationSessionService as dG, ViewerCompareService as dH, ViewerCustomShowsService as dI, ViewerDialogsService as dJ, ViewerDocumentPropertiesService as dK, ViewerExportService as dL, ViewerExtraDialogsComponent as dM, ViewerFileIOService as dN, ViewerFindReplaceService as dO, ViewerFormatPainterService as dP, ViewerInspectorPanelService as dQ, ViewerKeyboardService as dR, ViewerMobileSheetService as dS, ViewerPresentationModeService as dT, ViewerThemeGalleryService as dU, ViewerTouchGesturesService as dV, ViewerZoomService as dW, WEBM_MIME_CANDIDATES as dX, WHEEL_SPOKE_COUNTS as dY, WriteBackScheduler as dZ, ZERO_LINE_COLOR as d_, TEXT_3D_BOTTOM_BEVEL_KEYS as da, TEXT_3D_TOP_BEVEL_KEYS as db, TEXT_DIRECTION_OPTIONS$1 as dc, THEME_CATALOG as dd, TIMING_CURVE_OPTIONS as de, TRANSITION_SPEED_DURATION_MS as df, TRIGGER_OPTIONS as dg, TYPE_LABELS as dh, TableCellAdvancedFillComponent as di, TableCellFormattingComponent as dj, TableDataEditorComponent as dk, TablePropertiesComponent as dl, TableRendererComponent as dm, TableResizeOverlayComponent as dn, TableSelectionService as dp, TagsCardComponent as dq, Text3DBevelSectionComponent as dr, Text3DPanelComponent as ds, TextAdvancedPanelComponent as dt, ThemeEditorFieldsComponent as du, ThemeGalleryComponent as dv, ThemeSelectorCardComponent as dw, TitleBarComponent as dx, TitleBarSearchComponent as dy, TransitionDirectionPickerComponent as dz, AVATAR_COLOR_SWATCHES as e, buildModel3DViewModel as e$, ZoomRendererComponent as e0, ZoomTargetService as e1, addCategory as e2, addCommentToList as e3, addGradientStopPatch as e4, addItem as e5, addSeries as e6, addSubItem as e7, advanceStep as e8, affordanceElements as e9, buildCellParagraphs as eA, buildChartViewModel as eB, buildChatLogExport as eC, buildChatLogMarkdown as eD, buildChromeStyle as eE, buildClearHyperlinkPatch as eF, buildClickGroups as eG, buildColStyles as eH, buildCollaborationConfig as eI, buildComboViewModel as eJ, buildCssGradientFromShapeStyle as eK, buildDuotoneFilter as eL, buildDuotoneFilterId as eM, buildEmbeddedFontStyles as eN, buildEquationElement as eO, buildEquationSegment as eP, buildFallbackViewModel as eQ, buildFontFaceRule as eR, buildGradientFillCss as eS, buildGridlinesAndLabels as eT, buildHyperlinkPatch as eU, buildInkContainerStyle as eV, buildInkStrokes as eW, buildLegend as eX, buildLiveInkStrokeView as eY, buildMarkTooltip as eZ, buildModel3DContainerStyle as e_, aiToggleVisible as ea, alignPatch as eb, animationFor as ec, animationPresetLabelKey as ed, annotationMapToInkInserts as ee, applyAcceptedDiff as ef, applyAnimationPreset as eg, applyFindReplacements as eh, applyFormatToElement as ei, applyMove as ej, applyResize as ek, asMediaElement as el, assignUserColor as em, attachShowVisibilityPause as en, attachTouchGestures as eo, axisTickValues as ep, beginDrawingNodeEdit as eq, beginNodeEdit as er, bevelSizePatch as es, boolFromEvent as et, bringForward as eu, bringToFront as ev, buildBarActions as ew, buildBroadcastConfig as ex, buildBroadcastViewerUrl as ey, buildCategoryLabels as ez, AXIS_LABEL_COLOR as f, computeErrorBarPrimitives as f$, buildOleActionModel as f0, buildOleInfoRows as f1, buildPatternFillCss as f2, buildPieViewModel as f3, buildPrintHtmlDocument as f4, buildPropertiesPatch as f5, buildRadarViewModel as f6, buildRegionMapViewModel as f7, buildSaveSlides as f8, buildShareUrl as f9, changeIcon as fA, characterSpacingPatch as fB, chartPreserveAspectRatio as fC, checkFontAvailable as fD, clampCursorPosition as fE, clampGifDimensions as fF, clampIndex as fG, clampNotesFontSize as fH, clampScale as fI, clampStep as fJ, clearAllLocalViewerData as fK, clearAudienceContent as fL, cn as fM, collectAccessibilityIssues as fN, collectElementText as fO, collectSlideText as fP, collectStoredChats as fQ, collectUsedFontFamilies as fR, columnWidthStyle as fS, commitNodeText as fT, computeAlign as fU, computeAxisTitlePrimitives as fV, computeBarRects as fW, computeBubbleRadius as fX, computeCornerHandle as fY, computeDistribute as fZ, computeDrawingViewBox as f_, buildSmartArtInsertElement as fa, buildSmartArtNodes as fb, buildStockViewModel as fc, buildSurfaceViewModel as fd, buildTableViewModel as fe, buildTreemapViewModel as ff, buildTrimFragment as fg, buildWaterfallViewModel as fh, buildZeroLine as fi, buildZoomContainerStyle as fj, buildZoomViewModel as fk, bulletIndentPx as fl, canAddTopLevelNode as fm, canEditSmartArtNodes as fn, canGroupSelection as fo, canRemoveTopLevelNode as fp, canSetStrokeWidth as fq, canStartBroadcast as fr, canStartShare as fs, canUngroupSelection as ft, canUseClipboard as fu, captionDisplayText as fv, cellRunStyle as fw, cellStyleToStyleMap as fx, cellTdStyle as fy, changeCountLabel as fz, AccessibilityPanelComponent as g, encodeGif as g$, computeFocusTargets as g0, computeGridSpacingPx as g1, computeHandleBoxes as g2, computeHandoutLayout as g3, computeIsMobile as g4, computeIsTablet as g5, computeLinePoints as g6, computeLinearRegression as g7, computePageCount as g8, computePieLayout as g9, createWebsocketBundle as gA, cssObjectToStyleMap as gB, currentColorScheme as gC, currentLayout as gD, currentStyle as gE, defaultCssVars as gF, defaultRadius as gG, defaultThemeColors as gH, deleteElementsByIds as gI, deleteVersion as gJ, demoteNode as gK, deriveModel3DBlobUrl as gL, derivePresenceList as gM, describeSmartArtBounds as gN, disableGlowPatch as gO, disableInnerShadowPatch as gP, disableOuterShadowPatch as gQ, disableReflectionPatch as gR, disableSoftEdgePatch as gS, duplicateElementById as gT, durationOf as gU, effectsStateOf as gV, enableGlowPatch as gW, enableInnerShadowPatch as gX, enableOuterShadowPatch as gY, enableReflectionPatch as gZ, enableSoftEdgePatch as g_, computePieSlicePath as ga, computePieSlices as gb, computePlotLayout as gc, computeRSquared as gd, computeRadarPoints as ge, computeResizeHandleBoxes as gf, computeRotateHandleBox as gg, computeScatterDots as gh, computeScatterXDomain as gi, computeSelectionBoxes as gj, computeSingleSelected as gk, computeSlideIndices as gl, computeSnap as gm, computeStackedBarRects as gn, computeStackedValueRange as go, computeTrendlinePrimitives as gp, computeValueRange as gq, convertOmmlToMathMl as gr, copyFormatFromElement as gs, countAccessibilityIssues as gt, countAnnotationStrokes as gu, createAngularAiBridge as gv, createCustomShow as gw, createImageElementFromFile as gx, createSwipeDismissDrag as gy, createWebrtcBundle as gz, AccessibilityService as h, gradientStateFromStyle as h$, endShowMediaCleanup as h0, estimatePageCount as h1, exitPresentationFullscreen as h2, exportAiChatLogs as h3, extractPathPoints as h4, eyedropperAvailable as h5, fillColorOf$1 as h6, findInSlides as h7, findOwningSlideIndex as h8, findSlideIndexByElementId as h9, getEffectSoundState as hA, getImageSrc as hB, getLocalStorageUsageSummary as hC, getOleAriaLabel as hD, getOleBadgeLabel as hE, getOleDisplayName as hF, getOleDownloadFileName as hG, getOleTypeColor as hH, getOleTypeLabel as hI, getP14TransitionAnimations as hJ, getPasswordStrength as hK, getPatternSvg as hL, getPlaceholderStyle as hM, getVersions as hN, getResolvedShapeClipPath as hO, getResolvedShapeClipPathFor as hP, getSessionTabId as hQ, getShapeFillStrokeStyle as hR, getSlideBackgroundStyle as hS, getSlideTransitionAnimations as hT, getSmartArtNodeBounds as hU, getSpeechRecognitionCtor as hV, getTextBlockStyle as hW, getTextWarp as hX, getTouchDistance as hY, getWarpCategory as hZ, getWarpPath as h_, firstVisibleIndex as ha, fitPolynomial as hb, fitZoom as hc, focusTargetChips as hd, fontMimeForFormat as he, fontSizeOf as hf, forgetSessionDeck as hg, formatAxisValue as hh, formatBytes as hi, formatCursorLabel as hj, formatElapsed as hk, formatFileSize as hl, formatInlineListStyle as hm, formatPropertyDate as hn, formatTime as ho, fpsToFrameIntervalMs as hp, generateBroadcastRoomId as hq, generateCommentId as hr, generateCustomShowId as hs, generatePressureCircles as ht, generateTicks as hu, getCinematicTransitionAnimations as hv, getClrChangeParams as hw, getContainerStyle as hx, getDuotoneFilterDef as hy, getEffectSoundAsset as hz, AccessibilityTextPanelComponent as i, narrowToRect as i$, gradientStateOf as i0, gradientStatePatch as i1, gradientStopColorCommitPatch as i2, gridColumns as i3, groupIssuesBySeverity as i4, hasAnimation as i5, hasCopyableFormat as i6, hasExistingLink as i7, hasExitedFullscreen as i8, hasGradientFill as i9, isValidRoomId as iA, isViewportBackgroundPressTarget as iB, isZoomActivationKey as iC, issueTrackKey as iD, issueTypeLabel as iE, keyToLabel as iF, lastVisibleIndex as iG, latexToMathml as iH, layoutConnectorPaints as iI, layoutNodeLabels as iJ, linePointsToSvgString as iK, lineSpacingPatch as iL, loadAudienceContent as iM, loadSessionDeck as iN, mediaFallbackFor as iO, mediaSurfaceFor as iP, mergeCaptionResults as iQ, mergeDown as iR, mergeRight as iS, mergeSelection as iT, mergeTablesDirective as iU, moveElementBy as iV, moveNodeDown as iW, moveNodeUp as iX, msToFrameDelayCs as iY, narrowToCircle as iZ, narrowToPolygon as i_, hasPressureVariation as ia, hasVisibleSlideAfter as ib, headerLabel as ic, imageDimensions as id, inkViewBox as ie, insertTableElementColumn as ig, insertTableElementRow as ih, interpolateWidth as ii, isAudienceTab as ij, isBold as ik, isBrowserOpenableMime as il, isChildNode as im, isElementInteractive as io, isInjectableUrl as ip, isItalic as iq, isLegacyBinaryPresentation as ir, isPpactionUrl as is, isPresenterMessage as it, isSigned as iu, isSupportedPresentationFile as iv, isTextElement as iw, isTwoTableFocus as ix, isUnderline as iy, isUrlSafe as iz, AccountPageComponent as j, removeNode as j$, newChartElement as j0, newEquationElement as j1, newPresetShapeElement as j2, newShapeElement as j3, newSmartArtElement as j4, newTableElement as j5, newTextElement as j6, nextVisibleIndex as j7, nodeBold as j8, nodeEditBox as j9, planGifFrames as jA, planVideoSegments as jB, pointFromPointerEvent as jC, pointsToSvgPathD as jD, presenceToCursors as jE, presentationBaseName as jF, presentationStageStyle as jG, presenterTimerProgress as jH, presetByLayout as jI, presetsForCategory as jJ, pressuresToWidths as jK, prevVisibleIndex as jL, projectDrawingShapes as jM, promoteNode as jN, provideViewerTheme as jO, radarAngle as jP, radarRingPoints as jQ, readAsDataUrl as jR, recordWebm as jS, registerCrossSlideAudio as jT, rememberSessionDeck as jU, removeAnimation as jV, removeCategory as jW, removeTableElementColumn as jX, removeCommentFromList as jY, removeElementAnimation as jZ, removeGradientStopPatch as j_, nodeFillColor as ja, nodeFontColor as jb, nodeIdFromKey as jc, nodeItalic as jd, nodeStyle as je, normalizeFontFormat as jf, normalizeSlidesPerPage as jg, normalizeValue as jh, numFromEvent as ji, ommlToMathml as jj, ooxmlDashToCssBorderStyle as jk, openNativeEyeDropper as jl, overallStatus as jm, paletteColor as jn, parseAudienceNonce as jo, parseNodeTextarea as jp, partitionSlides as jq, patchChartData as jr, patchChartStyle as js, patchTableData as jt, patchTextStyle as ju, patternPresetOptions as jv, pendingElementStyles as jw, pickColorByClickFallback as jx, pickFile as jy, pickSupportedMimeType as jz, ActionSettingsPanelComponent as k, setAxisLogScale as k$, removeTableElementRow as k0, removeSeries as k1, renderToCanvas as k2, reorderAnimationDown as k3, reorderAnimationUp as k4, replaceInSlides as k5, replaceMatch as k6, requestPresentationFullscreen as k7, resizeElement as k8, resolveCaptionTracks as k9, rulerHighlight as kA, rulerStripTicks as kB, sampleColorFromSlide as kC, sanitizeColor as kD, sanitizeSlideIndex as kE, sanitizeUserName as kF, saveViewerProfile as kG, savedPresentationFileName as kH, scanAvailableFonts as kI, searchSlides as kJ, seedBroadcastFields as kK, seedHyperlinkDraft as kL, seedPropertiesDraft as kM, seedShareFields as kN, segmentFrameCount as kO, selectValue$3 as kP, sendBackward as kQ, sendToBack as kR, sequentialColorScale as kS, serializeWriteBack as kT, seriesColor as kU, setAfterAnimation as kV, setAfterAnimationColor as kW, setAnimationEmphasis as kX, setAnimationEntrance as kY, setAnimationExit as kZ, setAxis as k_, resolveChartKind as ka, resolveDirection as kb, resolveDirection8 as kc, resolveFontVariant as kd, resolveHyperlinkHref as ke, resolveInteractiveElementId as kf, resolveMediaSrc as kg, resolveOleType as kh, resolveOrientation as ki, resolveParagraphBullet as kj, resolvePresenterNotes as kk, resolveProfileInitial as kl, resolveRegionCode as km, resolveRibbonCanGroup as kn, resolveSlideAutoAdvanceMs as ko, resolveSlideTransition as kp, resolvePalette as kq, resolveThemeCatalogEntry as kr, resolveTransitionDuration as ks, resolveTransitionDurationMs as kt, resolveWheelSpokeCount as ku, restoreSessionDeck as kv, revealedElementStyles as kw, routeOrthogonalConnector as kx, rowStyle as ky, rulerDragToGuidePosition as kz, AdvancedChartEditorComponent as l, textAdvancedStateFromStyle as l$, setAxisTitleStyle as l0, setCategoryLabel as l1, setCellText as l2, setColorScheme as l3, setDataLabels as l4, setDataPointExplosion as l5, setDataPointFill as l6, setDataPointLabel as l7, setDataPointMarker as l8, setDelay as l9, sheetAfterNavigate as lA, shouldBlockClickAdvance as lB, shouldUseSvgWarp as lC, showDirectionPicker as lD, showsTemplateAffordance as lE, signatureCountLabel as lF, signatureKey as lG, signatureTimestamp as lH, signerName as lI, statusLabel as lJ, slideNumberOf as lK, slidesWithReappliedLayout as lL, smartArtNodes as lM, paletteColour as lN, snapToGridStep as lO, splitCursorCell as lP, splitMergedCell as lQ, statusKind as lR, statusLabel$1 as lS, storeAudienceContent as lT, stringFromEvent$5 as lU, strokeColorOf as lV, strokeToInkElement as lW, strokeWidthOf as lX, styleShadowFilter as lY, surfaceColor as lZ, textAdvancedPatch as l_, setDirection as la, setDuration as lb, setEffectSound as lc, setEffectStockSound as ld, setElementPosition as le, setGridlineStyle as lf, setLayout as lg, setLegend as lh, setNodeStyle as li, setNodeText as lj, setRepeatCount as lk, setRepeatMode as ll, setSequence as lm, setSeriesChartType as ln, setSeriesColor as lo, setSeriesErrorBars as lp, setSeriesMarker as lq, setSeriesName as lr, setSeriesTrendline as ls, setSeriesValue as lt, setStyle as lu, setTimingCurve as lv, setTitle as lw, setTrigger as lx, setTriggerShapeId as ly, shapeStylePatch$1 as lz, AiChangeOverlayComponent as m, textAdvancedStateOf as m0, textColorOf as m1, textDirectionPatch as m2, textFontSizePatch as m3, textStyleOf as m4, textStylePatch as m5, themeStyle as m6, themeToCssVars as m7, thumbnailHeight as m8, thumbnailZoom as m9, zoomTargetSlideIndex as mA, toggleCommentResolvedInList as ma, toggleNodeBold as mb, toggleNodeItalic as mc, toggleSheet as md, topLevelNodeCount as me, transformSelectedTextCase as mf, translationsEn as mg, updateElementById as mh, updateGlowPatch as mi, updateGradientStopPatch as mj, updateInnerShadowPatch as mk, updateOuterShadowPatch as ml, updateReflectionPatch as mm, vAlignPatch as mn, validatePassword as mo, validatePrintSettings as mp, validateRoomId as mq, valueToY as mr, vermilionDarkColors as ms, vermilionDarkTheme as mt, vermilionLightColors as mu, vermilionLightTheme as mv, vermilionRadius as mw, waypointsToPathD as mx, withManualLayouts as my, worstStatus as mz, AiChatPanelComponent as n, AiChatService as o, AiComposerComponent as p, AiFocusBarComponent as q, AiFocusHighlightOverlayComponent as r, AiHistoryMenuComponent as s, toChatSummary as t, AiHistoryService as u, AiMessageListComponent as v, AiPanelStore as w, AiProposalCardComponent as x, AiSettingsSectionComponent as y, AiToolCallCardComponent as z };
187011
- //# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-DP0uS-ib.mjs.map
187455
+ //# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-vihD7wHf.mjs.map