pptx-angular-viewer 3.20.1 → 4.0.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.
- package/CHANGELOG.md +26 -0
- package/fesm2022/{pptx-angular-viewer-chat-history-idb-DGdVQ5kC.mjs → pptx-angular-viewer-chat-history-idb-hykKkAfr.mjs} +2 -2
- package/fesm2022/{pptx-angular-viewer-chat-history-idb-DGdVQ5kC.mjs.map → pptx-angular-viewer-chat-history-idb-hykKkAfr.mjs.map} +1 -1
- package/fesm2022/{pptx-angular-viewer-pptx-angular-viewer-De1uOCxq.mjs → pptx-angular-viewer-pptx-angular-viewer-Btf_cx1D.mjs} +922 -486
- package/fesm2022/{pptx-angular-viewer-pptx-angular-viewer-De1uOCxq.mjs.map → pptx-angular-viewer-pptx-angular-viewer-Btf_cx1D.mjs.map} +1 -1
- package/fesm2022/pptx-angular-viewer.mjs +1 -1
- package/package.json +3 -3
- package/types/pptx-angular-viewer.d.ts +208 -136
- package/types/pptx-angular-viewer.d.ts.map +1 -1
|
@@ -69017,279 +69017,103 @@ function asSelectionIds(value) {
|
|
|
69017
69017
|
}
|
|
69018
69018
|
|
|
69019
69019
|
/**
|
|
69020
|
-
*
|
|
69021
|
-
* initial
|
|
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
|
|
69024
|
-
|
|
69025
|
-
|
|
69026
|
-
|
|
69027
|
-
let
|
|
69028
|
-
|
|
69029
|
-
|
|
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
|
-
|
|
69058
|
-
|
|
69059
|
-
|
|
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
|
-
|
|
69081
|
-
|
|
69082
|
-
}
|
|
69083
|
-
|
|
69084
|
-
if (
|
|
69085
|
-
|
|
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
|
-
|
|
69090
|
-
|
|
69091
|
-
|
|
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
|
|
69203
|
-
const other = b[index];
|
|
69204
|
-
return other !== undefined && presenceEntriesEqual(entry, other);
|
|
69205
|
-
});
|
|
69055
|
+
return dispose;
|
|
69206
69056
|
}
|
|
69207
|
-
|
|
69208
69057
|
/**
|
|
69209
|
-
*
|
|
69210
|
-
*
|
|
69211
|
-
*
|
|
69212
|
-
*
|
|
69213
|
-
*
|
|
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
|
|
69229
|
-
let
|
|
69230
|
-
let
|
|
69231
|
-
let
|
|
69232
|
-
let
|
|
69233
|
-
|
|
69234
|
-
|
|
69235
|
-
|
|
69236
|
-
|
|
69237
|
-
|
|
69238
|
-
|
|
69239
|
-
|
|
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
|
-
|
|
69242
|
-
|
|
69243
|
-
|
|
69244
|
-
|
|
69245
|
-
|
|
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
|
-
|
|
69248
|
-
|
|
69249
|
-
|
|
69250
|
-
|
|
69251
|
-
|
|
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
|
|
69661
|
-
if (
|
|
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
|
-
|
|
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
|
|
71012
|
-
*
|
|
71013
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
107928
|
+
const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-hykKkAfr.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
|
-
|
|
110709
|
-
|
|
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
|
-
|
|
110714
|
-
|
|
110715
|
-
|
|
110716
|
-
|
|
110717
|
-
|
|
110718
|
-
|
|
110719
|
-
|
|
110720
|
-
|
|
110721
|
-
|
|
110722
|
-
|
|
110723
|
-
|
|
110724
|
-
|
|
110725
|
-
|
|
110726
|
-
|
|
110727
|
-
|
|
110728
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
111037
|
-
this.
|
|
111038
|
-
this.timer = null;
|
|
111471
|
+
this.ydoc = ydoc;
|
|
111472
|
+
this.serialize = async (isCurrent) => {
|
|
111039
111473
|
const bytes = getSourceBytes?.();
|
|
111040
|
-
if (!bytes
|
|
111041
|
-
return;
|
|
111474
|
+
if (!bytes) {
|
|
111475
|
+
return null;
|
|
111042
111476
|
}
|
|
111043
|
-
|
|
111044
|
-
|
|
111045
|
-
|
|
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
|
-
|
|
111052
|
-
|
|
111053
|
-
|
|
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
|
|
111062
|
-
*
|
|
111063
|
-
*
|
|
111064
|
-
*
|
|
111065
|
-
*
|
|
111066
|
-
*
|
|
111067
|
-
*
|
|
111068
|
-
* -
|
|
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
|
-
|
|
111089
|
-
|
|
111090
|
-
|
|
111091
|
-
|
|
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
|
-
|
|
111095
|
-
this.
|
|
111096
|
-
|
|
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
|
-
//
|
|
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
|
|
111133
|
-
*
|
|
111134
|
-
*
|
|
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
|
-
|
|
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
|
-
//
|
|
111185
|
-
//
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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.
|
|
111266
|
-
this.
|
|
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
|
|
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.
|
|
149184
|
+
const PPTX_ANGULAR_VIEWER_VERSION = "3.21.0";
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
|
|
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-
|
|
187455
|
+
//# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-Btf_cx1D.mjs.map
|