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