pptx-angular-viewer 3.20.1 → 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.
@@ -69017,279 +69017,103 @@ function asSelectionIds(value) {
69017
69017
  }
69018
69018
 
69019
69019
  /**
69020
- * Create a throttled presence publisher bound to an awareness instance. The
69021
- * 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.
69022
69022
  */
69023
- function createPresencePublisher(awareness, identity) {
69024
- const local = { activeSlideIndex: 0, cursorX: 0, cursorY: 0 };
69025
- // Starts at 0 so the first real update publishes immediately (the initial
69026
- // announce below does not consume the throttle budget).
69027
- let lastBroadcast = 0;
69028
- let pending = null;
69029
- function publish() {
69030
- awareness.setLocalStateField('presence', {
69031
- userName: identity.userName,
69032
- userColor: identity.userColor,
69033
- userAvatar: identity.userAvatar,
69034
- role: identity.role,
69035
- activeSlideIndex: local.activeSlideIndex,
69036
- cursorX: local.cursorX,
69037
- cursorY: local.cursorY,
69038
- selectedElementId: local.selectedElementId,
69039
- lastUpdated: new Date().toISOString(),
69040
- });
69041
- }
69042
- /** Whether `patch` would actually change any field of the local state. */
69043
- function changesLocalState(patch) {
69044
- return Object.keys(patch).some((key) => patch[key] !== local[key]);
69045
- }
69046
- function update(patch) {
69047
- // A patch that says nothing new is dropped before it reaches the wire.
69048
- // Bindings call this from pointer-move and selection handlers that
69049
- // re-fire with identical values (a cursor resting inside one slide pixel,
69050
- // a re-click on the already-selected element). Publishing anyway re-stamps
69051
- // `lastUpdated`, which every peer sees as a presence change and turns into
69052
- // a re-render, so an idle room kept re-rendering all five bindings.
69053
- // `flush` is exempt: the heartbeat MUST keep publishing or peers time us out.
69054
- 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) {
69055
69030
  return;
69056
69031
  }
69057
- Object.assign(local, patch);
69058
- const elapsed = Date.now() - lastBroadcast;
69059
- if (elapsed >= BROADCAST_THROTTLE_MS) {
69060
- if (pending !== null) {
69061
- clearTimeout(pending);
69062
- pending = null;
69063
- }
69064
- lastBroadcast = Date.now();
69065
- publish();
69066
- }
69067
- else if (pending === null) {
69068
- pending = setTimeout(() => {
69069
- pending = null;
69070
- lastBroadcast = Date.now();
69071
- publish();
69072
- }, BROADCAST_THROTTLE_MS - elapsed);
69073
- }
69074
- }
69075
- function flush() {
69076
- if (pending !== null) {
69077
- clearTimeout(pending);
69078
- pending = null;
69032
+ const snapshot = session.getSnapshot();
69033
+ if (previous?.status === snapshot.status && previous.synced === snapshot.synced) {
69034
+ return;
69079
69035
  }
69080
- lastBroadcast = Date.now();
69081
- publish();
69082
- }
69083
- function dispose() {
69084
- if (pending !== null) {
69085
- clearTimeout(pending);
69086
- pending = null;
69036
+ previous = { status: snapshot.status, synced: snapshot.synced };
69037
+ onSnapshot(previous);
69038
+ };
69039
+ const dispose = () => {
69040
+ if (!active) {
69041
+ return;
69087
69042
  }
69043
+ active = false;
69044
+ unsubscribe?.();
69045
+ };
69046
+ try {
69047
+ unsubscribe = session.subscribe(notify);
69048
+ subscribing = false;
69049
+ notify();
69088
69050
  }
69089
- // Announce initial presence immediately (without consuming the throttle).
69090
- publish();
69091
- return { update, flush, dispose };
69092
- }
69093
-
69094
- /**
69095
- * Shallow, order-sensitive array equality, for selector subscriptions whose
69096
- * slice is an array.
69097
- *
69098
- * Needed more often than it looks. Several shared stores rebuild their state by
69099
- * deep-cloning it on every write (`cloneViewerOptions` is one), so a nested
69100
- * array is a NEW array after any change anywhere in the object, even when its
69101
- * contents did not move. Under the default `Object.is` a selector reading that
69102
- * array would therefore fire on every unrelated write, which is exactly the
69103
- * over-notification a selector is supposed to prevent.
69104
- */
69105
- function sameArray(a, b, isEqual = Object.is) {
69106
- if (a === b) {
69107
- return true;
69108
- }
69109
- if (a.length !== b.length) {
69110
- return false;
69111
- }
69112
- return a.every((entry, index) => {
69113
- const other = b[index];
69114
- return other !== undefined && isEqual(entry, other);
69115
- });
69116
- }
69117
- function zoomEqual(a, b) {
69118
- if (a === b) {
69119
- return true;
69120
- }
69121
- if (!a || !b) {
69122
- return false;
69123
- }
69124
- return a.scale === b.scale && a.originX === b.originX && a.originY === b.originY;
69125
- }
69126
- function pointerEqual(a, b) {
69127
- if (a === b) {
69128
- return true;
69129
- }
69130
- if (!a || !b) {
69131
- return false;
69132
- }
69133
- return a.tool === b.tool && a.x === b.x && a.y === b.y && a.color === b.color;
69134
- }
69135
- /**
69136
- * Whether two presentation snapshots convey the same state.
69137
- *
69138
- * `sequence` is deliberately EXCLUDED. It is a monotonic change counter that is
69139
- * bumped by construction on every merge, so including it would make every
69140
- * snapshot unequal to every other and defeat the entire guard. Nothing compares
69141
- * sequences for ordering; the audience-side validator only type-checks it.
69142
- *
69143
- * `inkStrokes` is compared by reference on purpose. The shared ink helpers are
69144
- * copy-on-write (`appendPresentationInkPoint` returns a new array), so identity
69145
- * is an exact change signal, and a deep walk would run per pointer move while a
69146
- * presenter is drawing, which is the one place we can least afford it.
69147
- */
69148
- function presentationSnapshotsEqual(a, b) {
69149
- if (a === b) {
69150
- return true;
69151
- }
69152
- return (a.slideIndex === b.slideIndex &&
69153
- a.buildStep === b.buildStep &&
69154
- a.blackout === b.blackout &&
69155
- a.paused === b.paused &&
69156
- a.elapsedMs === b.elapsedMs &&
69157
- a.caption === b.caption &&
69158
- a.subtitlesVisible === b.subtitlesVisible &&
69159
- a.inkMarkupVisible === b.inkMarkupVisible &&
69160
- a.inkStrokes === b.inkStrokes &&
69161
- zoomEqual(a.zoom, b.zoom) &&
69162
- pointerEqual(a.pointer, b.pointer));
69163
- }
69164
- /**
69165
- * Whether two sanitised presence entries describe the same collaborator in the
69166
- * same place.
69167
- *
69168
- * `lastUpdated` is deliberately EXCLUDED, and that exclusion is the whole point:
69169
- * every peer re-stamps it on each heartbeat, so comparing it would mean an
69170
- * idle room still re-rendered every client on a fixed interval forever. Dropping
69171
- * it does not weaken staleness handling, because a peer that genuinely goes
69172
- * stale is removed from the list by `derivePresenceList` and the resulting
69173
- * length change is caught here.
69174
- */
69175
- function presenceEntriesEqual(a, b) {
69176
- if (a === b) {
69177
- return true;
69178
- }
69179
- return (a.clientId === b.clientId &&
69180
- a.userName === b.userName &&
69181
- a.userColor === b.userColor &&
69182
- a.userAvatar === b.userAvatar &&
69183
- a.role === b.role &&
69184
- a.activeSlideIndex === b.activeSlideIndex &&
69185
- a.cursorX === b.cursorX &&
69186
- a.cursorY === b.cursorY &&
69187
- a.selectedElementId === b.selectedElementId);
69188
- }
69189
- /**
69190
- * Whether two derived presence lists are interchangeable, so a binding can keep
69191
- * its existing array (and skip the re-render) instead of adopting a fresh one.
69192
- * Order is significant, which is safe because `derivePresenceList` walks the
69193
- * awareness map in insertion order.
69194
- */
69195
- function presenceListsEqual(a, b) {
69196
- if (a === b) {
69197
- return true;
69198
- }
69199
- if (a.length !== b.length) {
69200
- return false;
69051
+ catch (error) {
69052
+ dispose();
69053
+ throw error;
69201
69054
  }
69202
- return a.every((entry, index) => {
69203
- const other = b[index];
69204
- return other !== undefined && presenceEntriesEqual(entry, other);
69205
- });
69055
+ return dispose;
69206
69056
  }
69207
-
69208
69057
  /**
69209
- * collaboration-presence-projector.ts: a memoising projection from a raw Yjs
69210
- * awareness map to the presence + cursor view-models every binding renders.
69211
- *
69212
- * WHY this is shared rather than five local guards: awareness fires on every
69213
- * peer heartbeat, and each peer re-stamps `lastUpdated` on a fixed interval, so
69214
- * an idle room emits a steady stream of events that carry no visible change.
69215
- * `derivePresenceList` allocates a fresh array per call, so each of those events
69216
- * looked like new state to React (`setState`), Vue (`ref.value =`), Angular
69217
- * (`signal.set`), Svelte (`$state =`) and Vanilla (an imperative repaint) alike.
69218
- * Five bindings were each re-rendering their collaboration layer on a timer.
69219
- *
69220
- * The projector holds the previous result and returns it BY IDENTITY when
69221
- * nothing a user could see has changed, along with an explicit `changed` flag
69222
- * for the imperative bindings that need to skip work rather than skip a render.
69223
- *
69224
- * The memo key is the derived presence list plus the local active slide, since
69225
- * cursors are filtered to the slide the local user is on: a peer that did not
69226
- * 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.
69227
69063
  */
69228
- function createPresenceProjector() {
69229
- let lastList = [];
69230
- let lastCursors = [];
69231
- let lastActiveSlideIndex;
69232
- let primed = false;
69233
- return {
69234
- project(states, localClientId, canvasWidth, canvasHeight, localActiveSlideIndex, now) {
69235
- const list = derivePresenceList(states, localClientId, canvasWidth, canvasHeight, now ?? Date.now());
69236
- if (primed &&
69237
- localActiveSlideIndex === lastActiveSlideIndex &&
69238
- presenceListsEqual(lastList, list)) {
69239
- 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;
69240
69078
  }
69241
- lastList = list;
69242
- lastCursors = presenceToCursors(list, localActiveSlideIndex);
69243
- lastActiveSlideIndex = localActiveSlideIndex;
69244
- primed = true;
69245
- 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);
69246
69092
  },
69247
- reset() {
69248
- lastList = [];
69249
- lastCursors = [];
69250
- lastActiveSlideIndex = undefined;
69251
- 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);
69252
69113
  },
69253
69114
  };
69254
69115
  }
69255
69116
 
69256
- const DEFAULT_LOCAL_COLOR = '#6366f1';
69257
- /**
69258
- * Two-letter initials for an avatar-circle fallback: first+last initial for a
69259
- * multi-word name, otherwise the first two characters.
69260
- */
69261
- function getUserInitials(name) {
69262
- const trimmed = name.trim(), parts = trimmed.split(/\s+/u).filter(Boolean), first = parts[0], last = parts[parts.length - 1];
69263
- if (parts.length >= 2 && first && last) {
69264
- return (first[0] + last[0]).toUpperCase();
69265
- }
69266
- return trimmed.slice(0, 2).toUpperCase();
69267
- }
69268
- /**
69269
- * Local user first, then remote users in their existing order - the order
69270
- * every binding's connected-users list has rendered since React's original.
69271
- */
69272
- function buildActiveSessionUsers(params) {
69273
- const local = {
69274
- id: 'local',
69275
- name: params.localUserName,
69276
- initials: params.localUserInitials?.trim()
69277
- ? params.localUserInitials.trim().slice(0, 2).toUpperCase()
69278
- : getUserInitials(params.localUserName),
69279
- color: params.localUserColor ?? DEFAULT_LOCAL_COLOR,
69280
- isLocal: true,
69281
- }, remote = params.remoteUsers.map((user) => ({
69282
- id: String(user.clientId),
69283
- name: user.userName,
69284
- initials: getUserInitials(user.userName),
69285
- color: user.userColor,
69286
- avatarUrl: user.userAvatar,
69287
- isLocal: false,
69288
- slideNumber: user.activeSlideIndex + 1,
69289
- }));
69290
- return [local, ...remote];
69291
- }
69292
-
69293
69117
  /**
69294
69118
  * collaboration-assets.ts: binary payload sync for the pptx-viewer
69295
69119
  * collaboration stack.
@@ -69657,8 +69481,16 @@ function decodeDelta(delta) {
69657
69481
  // attribute; a literal newline TEXT run (no pb/lb) must keep its text or
69658
69482
  // runs like "Project\nAtlas" collapse to "ProjectAtlas" on decode. The
69659
69483
  // zero-width space is always the empty-run attribute holder.
69660
- const isBreakMarker = op.insert === '\n' && (a.pb === '1' || a.lb === '1');
69661
- 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 !== '​') {
69662
69494
  seg.text = op.insert;
69663
69495
  }
69664
69496
  segments.push(seg);
@@ -70059,28 +69891,6 @@ function observeYDocSlides(ydoc, onChange) {
70059
69891
  return () => arr.unobserveDeep(onChange);
70060
69892
  }
70061
69893
 
70062
- /**
70063
- * collaboration-broadcast-follow.ts: the single, framework-agnostic rule for
70064
- * whether the local user should auto-follow a broadcaster's active slide.
70065
- *
70066
- * A "broadcast" session has one presenter that joins with `role: 'owner'`.
70067
- * Auto-follow is one-way: only a local `viewer` is pulled along to the
70068
- * owner's slide. A `collaborator` (two-way editing peer) and the `owner`
70069
- * itself navigate freely and are never yanked.
70070
- *
70071
- * Every binding (React/Vue/Angular/Svelte/Vanilla) must consult this helper
70072
- * instead of hand-rolling the guard, so the policy cannot drift per binding
70073
- * (previously React/Angular gated on `localRole === 'viewer'` while Vue
70074
- * followed the owner regardless of the local role).
70075
- */
70076
- /**
70077
- * True when the local user should auto-follow the given broadcaster's active
70078
- * slide: the local user is a `viewer` and the broadcaster is the `owner`.
70079
- */
70080
- function shouldAutoFollowBroadcaster(input) {
70081
- return input.localRole === 'viewer' && input.broadcasterRole === 'owner';
70082
- }
70083
-
70084
69894
  /**
70085
69895
  * collaboration-text-merge.ts: character-level in-place merging of a desired
70086
69896
  * text state into a live Y.Text.
@@ -70448,6 +70258,432 @@ function reconcileSlidesInYDoc(slides, ydoc, factories, origin = LOCAL_SYNC_ORIG
70448
70258
  }, origin);
70449
70259
  }
70450
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
+
70451
70687
  /**
70452
70688
  * collaboration-live-patch-target.ts: locating an element inside the shared
70453
70689
  * Y.Doc and writing one interim patch into it.
@@ -70541,7 +70777,8 @@ function applyLivePatch(doc, factories, patch, textPatchLimit) {
70541
70777
  * pass uses (`reconcileElementTextBody`), so concurrent typing on one
70542
70778
  * element still merges instead of last-write-wins
70543
70779
  * - writes are throttled (~1 per 50ms) with a trailing write, plus an
70544
- * 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
70545
70782
  * - every transaction is tagged with LOCAL_SYNC_ORIGIN, exactly like
70546
70783
  * `reconcileSlidesInYDoc`, so the local observer skips the echo
70547
70784
  *
@@ -70560,6 +70797,7 @@ function createCollaborationLivePatcher(options = {}) {
70560
70797
  let factories = null;
70561
70798
  let timer = null;
70562
70799
  let lastWriteAt = Number.NEGATIVE_INFINITY;
70800
+ let immediate = false;
70563
70801
  const cancelTimer = () => {
70564
70802
  if (timer !== null) {
70565
70803
  clearTimeout(timer);
@@ -70588,6 +70826,10 @@ function createCollaborationLivePatcher(options = {}) {
70588
70826
  }
70589
70827
  };
70590
70828
  const schedule = () => {
70829
+ if (immediate) {
70830
+ writePending();
70831
+ return;
70832
+ }
70591
70833
  if (timer !== null) {
70592
70834
  return;
70593
70835
  }
@@ -70614,14 +70856,15 @@ function createCollaborationLivePatcher(options = {}) {
70614
70856
  return entry;
70615
70857
  };
70616
70858
  return {
70617
- configure(nextDoc, nextFactories) {
70618
- if (nextDoc === doc && nextFactories === factories) {
70859
+ configure(nextDoc, nextFactories, nextImmediate = false) {
70860
+ if (nextDoc === doc && nextFactories === factories && nextImmediate === immediate) {
70619
70861
  return;
70620
70862
  }
70621
70863
  cancelTimer();
70622
70864
  pending.clear();
70623
70865
  doc = nextDoc;
70624
70866
  factories = nextFactories;
70867
+ immediate = nextImmediate;
70625
70868
  lastWriteAt = Number.NEGATIVE_INFINITY;
70626
70869
  },
70627
70870
  isActive() {
@@ -70854,7 +71097,12 @@ function registerCollaborationTeardown(options) {
70854
71097
  doLeave();
70855
71098
  };
70856
71099
  const onBeforeUnload = () => {
70857
- doLeave();
71100
+ const allowed = typeof options.leaveOnBeforeUnload === 'function'
71101
+ ? options.leaveOnBeforeUnload()
71102
+ : options.leaveOnBeforeUnload !== false;
71103
+ if (allowed) {
71104
+ doLeave();
71105
+ }
70858
71106
  };
70859
71107
  const onPageShow = (event) => {
70860
71108
  if (event.persisted !== true || !left || !rejoin) {
@@ -71008,51 +71256,90 @@ function createDepartureChannel(roomId, awareness, factory) {
71008
71256
  * edits survive), re-serializes to PPTX bytes, and hands them to
71009
71257
  * `config.onWriteBack`.
71010
71258
  *
71011
- * Framework-agnostic: every binding (Vue, Svelte, Vanilla) shares this single
71012
- * implementation instead of maintaining its own near-identical copy. A
71013
- * binding without template-mode editing simply omits `getTemplateElements`/
71014
- * `mergeTemplateElements`. Angular keeps its own class-based
71015
- * `WriteBackScheduler` (a different calling convention tied to its DI style),
71016
- * 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.
71017
71262
  */
71018
71263
  const DEFAULT_DEBOUNCE_MS = 5_000;
71019
71264
  function createWriteBackScheduler(deps) {
71020
71265
  let timer = null;
71266
+ let generation = 0;
71267
+ let unsubscribe = null;
71021
71268
  function cancel() {
71269
+ generation++;
71270
+ unsubscribe?.();
71271
+ unsubscribe = null;
71022
71272
  if (timer !== null) {
71023
71273
  clearTimeout(timer);
71024
71274
  timer = null;
71025
71275
  }
71026
71276
  }
71027
71277
  function schedule(config) {
71278
+ const isReady = () => config.externalSession?.getSnapshot().synced ?? true;
71028
71279
  if (!config.onWriteBack || config.role !== 'owner' || !deps.getYDoc()) {
71029
71280
  return;
71030
71281
  }
71031
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
+ }
71032
71300
  const debounceMs = config.writeBackDebounceMs ?? DEFAULT_DEBOUNCE_MS;
71033
71301
  timer = setTimeout(async () => {
71034
71302
  timer = null;
71035
71303
  const ydoc = deps.getYDoc();
71036
- if (!ydoc || !config.onWriteBack) {
71037
- return;
71038
- }
71039
- const sourceBytes = deps.getSourceBytes?.();
71040
- if (!sourceBytes) {
71041
- return;
71042
- }
71304
+ const isCurrent = () => token === generation && isReady() && deps.getYDoc() === ydoc;
71043
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
+ }
71044
71320
  const handler = new PptxHandler();
71045
71321
  await handler.load(sourceBytes.buffer);
71322
+ if (!isCurrent()) {
71323
+ return;
71324
+ }
71046
71325
  const slides = readSlidesFromYDoc(ydoc);
71047
71326
  const merged = deps.mergeTemplateElements
71048
71327
  ? deps.mergeTemplateElements(slides, deps.getTemplateElements?.() ?? {})
71049
71328
  : slides;
71050
71329
  const bytes = await handler.save(merged, deps.getSaveOptions?.());
71051
- config.onWriteBack(bytes);
71330
+ if (isCurrent()) {
71331
+ await config.onWriteBack(bytes);
71332
+ }
71052
71333
  }
71053
71334
  catch {
71054
71335
  /* non-fatal */
71055
71336
  }
71337
+ finally {
71338
+ if (token === generation) {
71339
+ unsubscribe?.();
71340
+ unsubscribe = null;
71341
+ }
71342
+ }
71056
71343
  }, debounceMs);
71057
71344
  }
71058
71345
  return { schedule, cancel };
@@ -107638,7 +107925,7 @@ function createLocalStorageBackend(namespace) {
107638
107925
  /** Try IndexedDB first; fall back to localStorage on any failure. */
107639
107926
  async function resolveBackend(dbName, namespace) {
107640
107927
  try {
107641
- const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-DGdVQ5kC.mjs');
107928
+ const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-DewzErjQ.mjs');
107642
107929
  const db = await openChatDb(dbName);
107643
107930
  return createIdbBackend(db);
107644
107931
  }
@@ -110483,6 +110770,153 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
110483
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"] }]
110484
110771
  }], propDecorators: { cursors: [{ type: i0.Input, args: [{ isSignal: true, alias: "cursors", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }] } });
110485
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
+
110486
110920
  /**
110487
110921
  * collaboration-providers.ts: transport factories for the Angular
110488
110922
  * collaboration service.
@@ -110606,51 +111040,6 @@ function wireConnectionStatus(deps) {
110606
111040
  return { cancelConnectTimer };
110607
111041
  }
110608
111042
 
110609
- /**
110610
- * collaboration-local-presence.ts: publishes the local user's presence into a
110611
- * single awareness `presence` field.
110612
- *
110613
- * Cursor, selection, and active-slide updates all merge into one record so a
110614
- * later cursor move never clobbers the selection (or vice versa). The remote
110615
- * side reads this shape via the shared `derivePresenceList`.
110616
- */
110617
- class LocalPresencePublisher {
110618
- constructor(awareness, identity) {
110619
- this.awareness = awareness;
110620
- this.identity = identity;
110621
- this.activeSlide = 0;
110622
- this.cursor = { x: 0, y: 0 };
110623
- }
110624
- /** Re-emit the merged presence record (also used as a heartbeat). */
110625
- publish() {
110626
- this.awareness.setLocalStateField('presence', {
110627
- userName: this.identity.userName,
110628
- userColor: this.identity.userColor,
110629
- userAvatar: this.identity.userAvatar,
110630
- role: this.identity.role,
110631
- activeSlideIndex: this.activeSlide,
110632
- cursorX: this.cursor.x,
110633
- cursorY: this.cursor.y,
110634
- selectedElementId: this.selection,
110635
- lastUpdated: new Date().toISOString(),
110636
- });
110637
- }
110638
- setCursor(x, y, activeSlideIndex = this.activeSlide) {
110639
- this.cursor = { x, y };
110640
- this.activeSlide = activeSlideIndex;
110641
- this.publish();
110642
- }
110643
- setSelection(selectedElementId, activeSlideIndex = this.activeSlide) {
110644
- this.selection = selectedElementId;
110645
- this.activeSlide = activeSlideIndex;
110646
- this.publish();
110647
- }
110648
- setActiveSlide(index) {
110649
- this.activeSlide = Math.max(0, Math.floor(index));
110650
- this.publish();
110651
- }
110652
- }
110653
-
110654
111043
  /**
110655
111044
  * collaboration-session-setup.ts: build up and tear down a live collaboration
110656
111045
  * session for the Angular `CollaborationService`.
@@ -110666,10 +111055,12 @@ class LocalPresencePublisher {
110666
111055
  * connection-status + remote-slide changes, then open the first-write gate.
110667
111056
  */
110668
111057
  function activateSession(bundle, config, transport, deps) {
110669
- 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');
110670
111060
  deps.slideSync.bind({
110671
111061
  ydoc: bundle.doc,
110672
111062
  factories: bundle.factories,
111063
+ readOnly: config.role === 'viewer',
110673
111064
  onRemoteSlides: deps.onRemoteSlides,
110674
111065
  scheduleWriteBack: deps.scheduleWriteBack,
110675
111066
  });
@@ -110699,33 +111090,46 @@ function activateSession(bundle, config, transport, deps) {
110699
111090
  const unobserve = observeYDocSlides(bundle.doc, (_events, transaction) => deps.slideSync.onRemoteChange(transaction));
110700
111091
  return {
110701
111092
  ydoc: bundle.doc,
110702
- provider: bundle.provider,
110703
111093
  awareness: bundle.awareness,
110704
- departure: bundle.departure,
110705
- factories: bundle.factories,
110706
111094
  selfId: bundle.awareness.clientID ?? -1,
110707
111095
  localPresence,
110708
- connection,
110709
- 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
+ },
110710
111108
  };
110711
111109
  }
110712
- /**
110713
- * Dispose a live session. Announces the departure synchronously first: the
110714
- * provider's own awareness removal is broadcast a microtask later and would be
110715
- * dropped when this runs from a document being destroyed, leaving a ghost
110716
- * collaborator until the 30s awareness timeout.
110717
- */
110718
- function teardownSession(session, refreshPresence) {
110719
- session.connection.cancelConnectTimer();
110720
- session.unobserve();
110721
- session.awareness.off?.('change', refreshPresence);
110722
- session.awareness.off?.('update', refreshPresence);
110723
- session.departure.announce();
110724
- session.departure.dispose();
110725
- clearLocalAwareness(session.awareness);
110726
- session.provider.disconnect();
110727
- session.provider.destroy();
110728
- 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);
110729
111133
  }
110730
111134
 
110731
111135
  /**
@@ -110751,11 +111155,13 @@ class SlideSyncEngine {
110751
111155
  this.#lastSynced = '';
110752
111156
  this.#applyingRemote = false;
110753
111157
  this.#pending = null;
111158
+ this.#current = [];
110754
111159
  }
110755
111160
  #binding;
110756
111161
  #lastSynced;
110757
111162
  #applyingRemote;
110758
111163
  #pending;
111164
+ #current;
110759
111165
  /** Attach the engine to a freshly connected session's doc. */
110760
111166
  bind(binding) {
110761
111167
  this.#binding = binding;
@@ -110767,6 +111173,7 @@ class SlideSyncEngine {
110767
111173
  this.#pending = null;
110768
111174
  this.#lastSynced = '';
110769
111175
  this.#applyingRemote = false;
111176
+ this.#current = [];
110770
111177
  }
110771
111178
  /**
110772
111179
  * Record the current local deck as the sync baseline so the first (unchanged)
@@ -110775,8 +111182,33 @@ class SlideSyncEngine {
110775
111182
  * overwrites the shared document before receiving it.
110776
111183
  */
110777
111184
  seedBaseline(slides) {
111185
+ this.#current = slides;
110778
111186
  this.#lastSynced = JSON.stringify(slides);
110779
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
+ }
110780
111212
  /**
110781
111213
  * Open the first-write gate on the provider's initial-sync confirmation.
110782
111214
  * y-websocket emits 'sync' with a boolean; y-webrtc emits 'synced' with an
@@ -110807,8 +111239,11 @@ class SlideSyncEngine {
110807
111239
  * skipped, and while the gate is shut the deck is held as pending.
110808
111240
  */
110809
111241
  broadcast(slides) {
111242
+ if (!this.#applyingRemote) {
111243
+ this.#current = slides;
111244
+ }
110810
111245
  const b = this.#binding;
110811
- if (!b || this.#applyingRemote || slides.length === 0) {
111246
+ if (!b || b.readOnly || this.#applyingRemote || (slides.length === 0 && !b.external)) {
110812
111247
  return;
110813
111248
  }
110814
111249
  if (!this.gate.isOpen()) {
@@ -110835,11 +111270,7 @@ class SlideSyncEngine {
110835
111270
  }
110836
111271
  // Suppress the echo: record what we just applied so the subsequent local
110837
111272
  // broadcast (driven by the editor signal) is a no-op.
110838
- this.#lastSynced = JSON.stringify(remote);
110839
- this.#applyingRemote = true;
110840
- b.onRemoteSlides?.(remote);
110841
- this.#applyingRemote = false;
110842
- b.scheduleWriteBack();
111273
+ this.adoptSlides(remote);
110843
111274
  }
110844
111275
  /**
110845
111276
  * Re-adopt the shared document's slides after a local content load committed
@@ -110865,10 +111296,7 @@ class SlideSyncEngine {
110865
111296
  if (!shouldRoomSlidesReplaceLoad(origin, docSlides.length)) {
110866
111297
  return false;
110867
111298
  }
110868
- this.#lastSynced = JSON.stringify(docSlides);
110869
- this.#applyingRemote = true;
110870
- b.onRemoteSlides?.(docSlides);
110871
- this.#applyingRemote = false;
111299
+ this.adoptSlides(docSlides);
110872
111300
  return true;
110873
111301
  }
110874
111302
  /**
@@ -111002,7 +111430,6 @@ function showsTemplateAffordance(element, editTemplateMode) {
111002
111430
  * into `.pptx` bytes for `config.onWriteBack`. Kept out of the service so the
111003
111431
  * service stays within the repo's per-file size budget.
111004
111432
  */
111005
- const WRITE_BACK_DEBOUNCE_MS = 5_000;
111006
111433
  /**
111007
111434
  * Serialize the current Y.Doc slide state (with the separated master/layout
111008
111435
  * template elements merged back) into `.pptx` bytes. `sourceBytes` seeds the
@@ -111013,9 +111440,12 @@ const WRITE_BACK_DEBOUNCE_MS = 5_000;
111013
111440
  * Save/Export path (`buildDeckSaveOptions`). Without it the write-back
111014
111441
  * dropped every session-level edit outside `slides`.
111015
111442
  */
111016
- async function serializeWriteBack(ydoc, sourceBytes, templateElements, saveOptions) {
111443
+ async function serializeWriteBack(ydoc, sourceBytes, templateElements, saveOptions, isCurrent = () => true) {
111017
111444
  const handler = new PptxHandler();
111018
111445
  await handler.load(sourceBytes.buffer);
111446
+ if (!isCurrent()) {
111447
+ throw new Error('Collaboration session changed during serialization');
111448
+ }
111019
111449
  const slides = buildSaveSlides(readSlidesFromYDoc(ydoc), templateElements);
111020
111450
  return handler.save(slides, saveOptions);
111021
111451
  }
@@ -111026,48 +111456,46 @@ async function serializeWriteBack(ydoc, sourceBytes, templateElements, saveOptio
111026
111456
  */
111027
111457
  class WriteBackScheduler {
111028
111458
  constructor() {
111029
- 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
+ });
111030
111465
  }
111031
111466
  schedule(config, ydoc, getSourceBytes, getTemplateElements, getSaveOptions) {
111032
111467
  if (!config?.onWriteBack || config.role !== 'owner' || !ydoc) {
111033
111468
  return;
111034
111469
  }
111035
111470
  this.cancel();
111036
- const ms = config.writeBackDebounceMs ?? WRITE_BACK_DEBOUNCE_MS;
111037
- this.timer = setTimeout(() => {
111038
- this.timer = null;
111471
+ this.ydoc = ydoc;
111472
+ this.serialize = async (isCurrent) => {
111039
111473
  const bytes = getSourceBytes?.();
111040
- if (!bytes || !config.onWriteBack) {
111041
- return;
111474
+ if (!bytes) {
111475
+ return null;
111042
111476
  }
111043
- void serializeWriteBack(ydoc, bytes, getTemplateElements?.() ?? {}, getSaveOptions?.())
111044
- .then((out) => config.onWriteBack?.(out))
111045
- .catch(() => {
111046
- /* non-fatal */
111047
- });
111048
- }, ms);
111477
+ return serializeWriteBack(ydoc, bytes, getTemplateElements?.() ?? {}, getSaveOptions?.(), isCurrent);
111478
+ };
111479
+ this.scheduler.schedule(config);
111049
111480
  }
111050
111481
  cancel() {
111051
- if (this.timer !== null) {
111052
- clearTimeout(this.timer);
111053
- this.timer = null;
111054
- }
111482
+ this.scheduler.cancel();
111483
+ this.serialize = null;
111484
+ this.ydoc = null;
111055
111485
  }
111056
111486
  }
111057
111487
 
111058
111488
  /**
111059
111489
  * CollaborationService: Angular real-time collaboration (Yjs) service.
111060
111490
  *
111061
- * Owns the provider lifecycle and the reactive collaboration state consumed by
111062
- * the viewer component:
111063
- * - Transport is `y-websocket` (default) or serverless `y-webrtc`
111064
- * (`config.transport === 'webrtc'`), created via `./collaboration-providers`.
111065
- * - Local edits sync via the granular `reconcileSlidesInYDoc` (only changed
111066
- * slides/elements/fields mutate) in a transaction tagged `LOCAL_SYNC_ORIGIN`;
111067
- * the remote observer skips its own local-origin writes.
111068
- * - Websocket connections fail fast on mixed content and time out to `'error'`
111069
- * after `CONNECTION_TIMEOUT_MS`; `retry()` reconnects with the last config.
111070
- * - 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.
111071
111499
  *
111072
111500
  * Provide at the component level: `@Component({ providers: [CollaborationService] })`.
111073
111501
  */
@@ -111085,36 +111513,24 @@ class CollaborationService {
111085
111513
  /** Role of the local user in the active session, or undefined when idle. */
111086
111514
  this.activeRole = signal(undefined, /* @ts-ignore */
111087
111515
  ...(ngDevMode ? [{ debugName: "activeRole" }] : /* istanbul ignore next */ []));
111088
- this.presence = signal([], /* @ts-ignore */
111089
- ...(ngDevMode ? [{ debugName: "presence" }] : /* istanbul ignore next */ []));
111090
- this.cursors = computed(() => presenceToCursors(this.presence()), /* @ts-ignore */
111091
- ...(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;
111092
111522
  this.connectedCount = computed(() => this.presence().length + (this.active() ? 1 : 0), /* @ts-ignore */
111093
111523
  ...(ngDevMode ? [{ debugName: "connectedCount" }] : /* istanbul ignore next */ []));
111094
- /** The client id the local user is currently following (null when free). */
111095
- this.followedClientId = signal(null, /* @ts-ignore */
111096
- ...(ngDevMode ? [{ debugName: "followedClientId" }] : /* istanbul ignore next */ []));
111097
- /** Active-slide index of the followed peer, or null when not following. */
111098
- this.followedSlideIndex = computed(() => {
111099
- const id = this.followedClientId();
111100
- if (id === null) {
111101
- return null;
111102
- }
111103
- return this.presence().find((p) => p.clientId === id)?.activeSlideIndex ?? null;
111104
- }, /* @ts-ignore */
111105
- ...(ngDevMode ? [{ debugName: "followedSlideIndex" }] : /* istanbul ignore next */ []));
111106
- /** Active-slide index of the first `owner` peer (the broadcaster), or null. */
111107
- this.broadcasterSlideIndex = computed(() => this.presence().find((p) => p.role === 'owner')?.activeSlideIndex ?? null, /* @ts-ignore */
111108
- ...(ngDevMode ? [{ debugName: "broadcasterSlideIndex" }] : /* istanbul ignore next */ []));
111524
+ this.followedClientId = this.presenceState.followedClientId;
111525
+ this.followedSlideIndex = this.presenceState.followedSlideIndex;
111526
+ this.broadcasterSlideIndex = this.presenceState.broadcasterSlideIndex;
111109
111527
  /**
111110
111528
  * Interim ("live preview") Y.Doc write channel: publishes in-flight inline
111111
111529
  * editor text that has not reached the slides state yet, so peers see typing
111112
111530
  * as it happens instead of on commit. Dormant outside a session.
111113
111531
  */
111114
111532
  this.livePatcher = createCollaborationLivePatcher();
111115
- // The live session's transport objects + wiring handles, owned as one atomic
111116
- // unit: null when disconnected, assigned by connect(), disposed by
111117
- // disconnect(). See collaboration-session-setup.ts.
111533
+ // One handle owns viewer wiring, but external transport resources stay borrowed.
111118
111534
  this.session = null;
111119
111535
  this.writeBack = new WriteBackScheduler();
111120
111536
  /** Granular local<->doc slide sync (gate + echo dedupe + broadcast/adopt). */
@@ -111129,38 +111545,20 @@ class CollaborationService {
111129
111545
  this.lastConfig = null;
111130
111546
  this.lastOptions = {};
111131
111547
  /**
111132
- * Reentrancy token for {@link connect}: bumped by every connect() and
111133
- * disconnect(). A connect() whose token no longer matches after an await was
111134
- * superseded, so it tears down whatever it just created and leaves the
111135
- * service state to the newer call (a second provider join on the same room
111136
- * 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).
111137
111551
  */
111138
111552
  this.connectToken = 0;
111139
- /** Memoises the awareness -> presence projection so idle heartbeats are dropped. */
111140
- this.projector = createPresenceProjector();
111141
111553
  this.refreshPresence = () => {
111142
- const s = this.session;
111143
- if (!s) {
111144
- // Leaving the room clears the memo, so a re-join is never mistaken for
111145
- // "nothing changed" against the previous session's peers.
111146
- this.projector.reset();
111147
- this.presence.set([]);
111148
- return;
111149
- }
111150
- // A signal notifies on every `set` with a fresh array, and awareness fires
111151
- // on each peer heartbeat as well as on our own writes, so this used to
111152
- // re-run the collaboration overlay's computeds on a timer. The shared
111153
- // projector reports whether anything visible actually moved (issue #145).
111154
- const { list, changed } = this.projector.project(s.awareness.getStates(), s.selfId, this.canvasWidth, this.canvasHeight);
111155
- if (changed) {
111156
- this.presence.set(list);
111157
- }
111554
+ this.presenceState.refresh(this.session, this.canvasWidth, this.canvasHeight);
111158
111555
  };
111159
111556
  // Service destruction is not the only way a session ends: a tab close, a
111160
111557
  // navigation, or an embedding page detaching the viewer's iframe destroys
111161
111558
  // the document without running Angular teardown, leaving a ghost peer in
111162
111559
  // everyone else's presence list. Leave the room from `pagehide` too.
111163
111560
  const disposeTeardown = registerCollaborationTeardown({
111561
+ leaveOnBeforeUnload: () => !this.currentConfig?.externalSession,
111164
111562
  leave: () => this.disconnect(),
111165
111563
  rejoin: () => void this.retry(),
111166
111564
  });
@@ -111181,14 +111579,14 @@ class CollaborationService {
111181
111579
  this.status.set('error');
111182
111580
  return;
111183
111581
  }
111184
- // Falls back from a blank serverUrl the same way Vue's session layer
111185
- // already does, so a bare CollaborationConfig behaves identically
111186
- // regardless of which binding's session layer receives it directly (not
111187
- // 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.
111188
111584
  const transport = config.transport ?? resolveTransportForServerUrl(config.serverUrl);
111189
111585
  // Fail fast on mixed content (websocket only): an https page cannot open a
111190
111586
  // ws:// socket, so surface the error rather than hanging until the timeout.
111191
- if (transport !== 'webrtc' && isMixedContentBlocked(config.serverUrl)) {
111587
+ if (!config.externalSession &&
111588
+ transport !== 'webrtc' &&
111589
+ isMixedContentBlocked(config.serverUrl)) {
111192
111590
  this.status.set('error');
111193
111591
  return;
111194
111592
  }
@@ -111200,27 +111598,17 @@ class CollaborationService {
111200
111598
  this.getSaveOptions = options.getSaveOptions ?? null;
111201
111599
  this.currentConfig = config;
111202
111600
  this.activeRole.set(config.role);
111601
+ this.readOnly.set(Boolean(config.externalSession) || config.role === 'viewer');
111203
111602
  this.status.set('connecting');
111204
111603
  try {
111205
- const bundle = transport === 'webrtc'
111206
- ? await createWebrtcBundle(config)
111207
- : await createWebsocketBundle(config);
111208
- if (token !== this.connectToken) {
111209
- // Superseded by a newer connect() or a disconnect() while awaiting
111210
- // the transport: destroy the just-created bundle and bail without
111211
- // touching the (newer call's) service state.
111212
- bundle.departure.dispose();
111213
- bundle.provider.disconnect();
111214
- bundle.provider.destroy();
111215
- bundle.doc.destroy();
111216
- return;
111217
- }
111218
- this.session = activateSession(bundle, config, transport, {
111604
+ const session = await connectSession(config, transport, {
111219
111605
  slideSync: this.slideSync,
111220
111606
  livePatcher: this.livePatcher,
111221
111607
  onRemoteSlides: this.onRemoteSlides,
111222
111608
  refreshPresence: this.refreshPresence,
111223
111609
  scheduleWriteBack: () => this.scheduleWriteBack(),
111610
+ cancelWriteBack: () => this.writeBack.cancel(),
111611
+ setReadOnly: (readOnly) => this.readOnly.set(readOnly),
111224
111612
  setStatus: (status) => this.status.set(status),
111225
111613
  getStatus: () => this.status(),
111226
111614
  isActive: () => this.active(),
@@ -111228,9 +111616,19 @@ class CollaborationService {
111228
111616
  this.disconnect();
111229
111617
  this.status.set('error');
111230
111618
  },
111231
- });
111619
+ }, () => token === this.connectToken);
111620
+ if (!session || token !== this.connectToken) {
111621
+ session?.dispose();
111622
+ return;
111623
+ }
111624
+ this.session = session;
111232
111625
  this.active.set(true);
111233
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
+ }
111234
111632
  }
111235
111633
  catch {
111236
111634
  if (token !== this.connectToken) {
@@ -111253,7 +111651,7 @@ class CollaborationService {
111253
111651
  this.slideSync.reset();
111254
111652
  this.writeBack.cancel();
111255
111653
  if (this.session) {
111256
- teardownSession(this.session, this.refreshPresence);
111654
+ this.session.dispose();
111257
111655
  this.session = null;
111258
111656
  }
111259
111657
  this.livePatcher.configure(null, null);
@@ -111262,8 +111660,8 @@ class CollaborationService {
111262
111660
  this.status.set('disconnected');
111263
111661
  this.active.set(false);
111264
111662
  this.activeRole.set(undefined);
111265
- this.presence.set([]);
111266
- this.followedClientId.set(null);
111663
+ this.readOnly.set(false);
111664
+ this.presenceState.reset();
111267
111665
  }
111268
111666
  /**
111269
111667
  * Record the current local deck as the sync baseline so the first (unchanged)
@@ -111279,6 +111677,9 @@ class CollaborationService {
111279
111677
  * Returns true when the room's slides were adopted over the loaded deck.
111280
111678
  */
111281
111679
  adoptDocSlidesAfterLoad(origin = 'user') {
111680
+ if (this.session?.readiness) {
111681
+ return this.session.readiness.handleLoad(origin);
111682
+ }
111282
111683
  return this.connected() ? this.slideSync.adoptDocAfterLoad(origin) : false;
111283
111684
  }
111284
111685
  /**
@@ -111299,11 +111700,14 @@ class CollaborationService {
111299
111700
  setActiveSlide(index) {
111300
111701
  this.session?.localPresence.setActiveSlide(index);
111301
111702
  }
111302
- /** 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. */
111303
111704
  followUser(clientId) {
111304
111705
  this.followedClientId.set(clientId);
111305
111706
  }
111306
111707
  scheduleWriteBack() {
111708
+ if (this.currentConfig?.externalSession && !this.slideSync.gate.isOpen()) {
111709
+ return;
111710
+ }
111307
111711
  this.writeBack.schedule(this.currentConfig, this.session?.ydoc ?? null, this.getSourceBytes, this.getTemplateElements, this.getSaveOptions);
111308
111712
  }
111309
111713
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: CollaborationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -130037,7 +130441,7 @@ class SlideCanvasComponent {
130037
130441
  // SmartArt), so template (master/layout) element commits can resolve
130038
130442
  // their owning slide; template elements are absent from slides[].elements.
130039
130443
  { provide: SLIDE_CONTEXT, useExisting: forwardRef(() => SlideCanvasComponent) },
130040
- ], 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 }); }
130041
130445
  }
130042
130446
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: SlideCanvasComponent, decorators: [{
130043
130447
  type: Component,
@@ -130059,7 +130463,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
130059
130463
  InlineListEditorComponent,
130060
130464
  RotateHandlePlacementDirective,
130061
130465
  ChartQuickActionsOverlayComponent,
130062
- ], 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"] }]
130063
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: [{
130064
130468
  type: HostListener,
130065
130469
  args: ['document:pointermove', ['$event']]
@@ -134086,6 +134490,20 @@ class ViewerCanvasEditingService {
134086
134490
  endInlineListSession() {
134087
134491
  this.listSession.end();
134088
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
+ }
134089
134507
  readInlineSnapshot() {
134090
134508
  const current = this.listSession.read();
134091
134509
  if (current && this.editingId()) {
@@ -134118,7 +134536,11 @@ class ViewerCanvasEditingService {
134118
134536
  */
134119
134537
  onTextEditStart(id) {
134120
134538
  this.inlineSnapshot = undefined;
134539
+ this.acceptedText = undefined;
134121
134540
  const host = this.requireHost();
134541
+ if (!host.canEdit()) {
134542
+ return;
134543
+ }
134122
134544
  const element = this.findElement(host, id);
134123
134545
  const segments = element && 'textSegments' in element ? element.textSegments : undefined;
134124
134546
  const equation = segments?.find((segment) => segment.equationXml);
@@ -134154,6 +134576,9 @@ class ViewerCanvasEditingService {
134154
134576
  * collaborating.
134155
134577
  */
134156
134578
  onTextInput(event) {
134579
+ if (!this.requireHost().canEdit()) {
134580
+ return;
134581
+ }
134157
134582
  const current = this.listSession.read();
134158
134583
  if (event.id !== this.editingId()) {
134159
134584
  return;
@@ -134161,6 +134586,7 @@ class ViewerCanvasEditingService {
134161
134586
  if (current) {
134162
134587
  event = { id: event.id, text: current.text, snapshot: current };
134163
134588
  }
134589
+ this.acceptedText = event.text;
134164
134590
  this.inlineSnapshot =
134165
134591
  event.id === this.editingId() &&
134166
134592
  event.snapshot?.elementId === event.id &&
@@ -134173,6 +134599,10 @@ class ViewerCanvasEditingService {
134173
134599
  /** Commit an inline text edit without flattening its rich-text runs. */
134174
134600
  onTextCommit(event) {
134175
134601
  const host = this.requireHost();
134602
+ if (!host.canEdit()) {
134603
+ this.suspendInlineEdit();
134604
+ return;
134605
+ }
134176
134606
  // Push any queued interim frame out first so it cannot land after the
134177
134607
  // committed text and revert it.
134178
134608
  this.collab.livePatcher.flush();
@@ -148751,7 +149181,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
148751
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 }] }] } });
148752
149182
 
148753
149183
  // Generated by scripts/inline-shared.mjs from package.json. Do not edit.
148754
- const PPTX_ANGULAR_VIEWER_VERSION = "3.20.0";
149184
+ const PPTX_ANGULAR_VIEWER_VERSION = "3.20.1";
148755
149185
 
148756
149186
  /**
148757
149187
  * account-page.component.ts: File > Account content.
@@ -180895,7 +181325,7 @@ class PowerPointViewerComponent {
180895
181325
  ...(ngDevMode ? [{ debugName: "hasEditPermission" }] : /* istanbul ignore next */ []));
180896
181326
  this.editingRequested = signal(true, /* @ts-ignore */
180897
181327
  ...(ngDevMode ? [{ debugName: "editingRequested" }] : /* istanbul ignore next */ []));
180898
- this.canEdit = computed(() => this.hasEditPermission() && this.editingRequested(), /* @ts-ignore */
181328
+ this.canEdit = computed(() => this.hasEditPermission() && this.editingRequested() && !this.collab.readOnly(), /* @ts-ignore */
180899
181329
  ...(ngDevMode ? [{ debugName: "canEdit" }] : /* istanbul ignore next */ []));
180900
181330
  /** Whether the Protected View banner should show: host allows editing, the option still blocks it, and the user hasn't dismissed it yet. */
180901
181331
  this.protectedViewActive = computed(() => this.canEditInput() &&
@@ -180932,8 +181362,8 @@ class PowerPointViewerComponent {
180932
181362
  */
180933
181363
  this.audienceSessionEnded = signal(false, /* @ts-ignore */
180934
181364
  ...(ngDevMode ? [{ debugName: "audienceSessionEnded" }] : /* istanbul ignore next */ []));
180935
- /** Preview changes interaction, not which permitted live deck is displayed. */
180936
- 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 */
180937
181367
  ...(ngDevMode ? [{ debugName: "displaySlides" }] : /* istanbul ignore next */ []));
180938
181368
  this.slideCount = computed(() => this.displaySlides().length, /* @ts-ignore */
180939
181369
  ...(ngDevMode ? [{ debugName: "slideCount" }] : /* istanbul ignore next */ []));
@@ -180943,7 +181373,7 @@ class PowerPointViewerComponent {
180943
181373
  * consumer that needs the COMPLETE slide (export, print, slide thumbnails,
180944
181374
  * accessibility) renders this instead so template elements are not lost.
180945
181375
  */
180946
- this.mergedSlides = computed(() => this.hasEditPermission()
181376
+ this.mergedSlides = computed(() => this.hasEditPermission() || this.collab.active()
180947
181377
  ? buildSaveSlides(this.editor.slides(), this.editor.templateElementsBySlideId())
180948
181378
  : this.loader.slides(), /* @ts-ignore */
180949
181379
  ...(ngDevMode ? [{ debugName: "mergedSlides" }] : /* istanbul ignore next */ []));
@@ -180952,10 +181382,10 @@ class PowerPointViewerComponent {
180952
181382
  ...(ngDevMode ? [{ debugName: "displaySlidesMut" }] : /* istanbul ignore next */ []));
180953
181383
  this.activeSlide = computed(() => this.displaySlides()[this.activeSlideIndex()], /* @ts-ignore */
180954
181384
  ...(ngDevMode ? [{ debugName: "activeSlide" }] : /* istanbul ignore next */ []));
180955
- /** Inherited template (master/layout) elements for the active slide, when editing. */
181385
+ /** Inherited template elements for the live editor or collaboration slide source. */
180956
181386
  this.activeTemplateElements = computed(() => {
180957
181387
  const slide = this.activeSlide();
180958
- if (!this.hasEditPermission() || !slide) {
181388
+ if ((!this.hasEditPermission() && !this.collab.active()) || !slide) {
180959
181389
  return [];
180960
181390
  }
180961
181391
  return this.editor.templateElementsBySlideId()[slide.id] ?? [];
@@ -181753,7 +182183,8 @@ class PowerPointViewerComponent {
181753
182183
  // component (canEdit, the host `content` input, the File ▸ Open override,
181754
182184
  // the editor's slides + template elements, and the contentChange emitter).
181755
182185
  this.fileIO.bind({
181756
- canEdit: () => this.hasEditPermission(),
182186
+ // This callback selects the save source, not interaction permission.
182187
+ canEdit: () => this.hasEditPermission() || this.collab.active(),
181757
182188
  content: () => this.content(),
181758
182189
  onOpenFile: () => this.onOpenFile(),
181759
182190
  slides: () => this.editor.slides(),
@@ -181830,6 +182261,11 @@ class PowerPointViewerComponent {
181830
182261
  activeSlideIndex: () => this.activeSlideIndex(),
181831
182262
  activeTemplateElements: () => this.activeTemplateElements(),
181832
182263
  });
182264
+ effect(() => {
182265
+ if (!this.canEdit()) {
182266
+ untracked(() => this.canvasEditing.suspendInlineEdit());
182267
+ }
182268
+ });
181833
182269
  const tableSelection = inject(TableSelectionService);
181834
182270
  setupViewerImagePaste({
181835
182271
  rootElement: () => this.viewerRoot()?.nativeElement,
@@ -187016,4 +187452,4 @@ function cn(...values) {
187016
187452
  */
187017
187453
 
187018
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 };
187019
- //# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-De1uOCxq.mjs.map
187455
+ //# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-vihD7wHf.mjs.map