pptx-angular-viewer 1.1.59 → 1.1.60

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 CHANGED
@@ -4,6 +4,16 @@ All notable changes to this project are documented here.
4
4
  This file is generated from [Conventional Commits](https://www.conventionalcommits.org)
5
5
  by [git-cliff](https://git-cliff.org); do not edit it by hand.
6
6
 
7
+ ## [1.1.59](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.1.59) - 2026-07-03
8
+
9
+ ### Features
10
+
11
+ - **angular:** Wire collaboration end-to-end (by @ChristopherVR) ([0498cea](https://github.com/ChristopherVR/pptx-viewer/commit/0498cea40ac10e08069f560be0a1cea6f92a8721))
12
+
13
+ ### Dependencies
14
+
15
+ - **deps:** Declare yjs, y-websocket, and y-webrtc across bindings (by @ChristopherVR) ([27a2849](https://github.com/ChristopherVR/pptx-viewer/commit/27a2849da755a0902296dcd59557c1329a1cbadf))
16
+
7
17
  ## [1.1.57](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.1.57) - 2026-07-03
8
18
 
9
19
  ### Features
@@ -22215,6 +22215,14 @@ function getDraggedShapeAdjustmentValue(state, deltaX) {
22215
22215
  */
22216
22216
  /** Maximum time (ms) to wait for an initial WebSocket connection before giving up. */
22217
22217
  const CONNECTION_TIMEOUT_MS = 30_000;
22218
+ /**
22219
+ * Grace period (ms) before the first local doc write when the provider has not
22220
+ * signalled initial sync. Websocket providers emit 'synced' reliably; webrtc
22221
+ * only syncs once a peer is present, so a lone (fresh-room) peer seeds the doc
22222
+ * after this delay instead. Gating the first write prevents a late joiner's
22223
+ * bootstrap deck from merging into a room whose real content has not arrived.
22224
+ */
22225
+ const INITIAL_SYNC_GRACE_MS = 3_000;
22218
22226
  /** Heartbeat interval (ms): re-publish presence so peers don't time us out. */
22219
22227
  const PRESENCE_HEARTBEAT_MS = 10_000;
22220
22228
  /** Minimum interval (ms) between outgoing presence broadcasts (rate limiting). */
@@ -23174,6 +23182,58 @@ function reconcileSlidesInYDoc(slides, ydoc, factories, origin = LOCAL_SYNC_ORIG
23174
23182
  }, origin);
23175
23183
  }
23176
23184
 
23185
+ /**
23186
+ * collaboration-sync-gate.ts: first-write gate for collaborative sessions.
23187
+ *
23188
+ * Until a provider confirms its initial document sync, local state must not
23189
+ * be written into the shared doc: a late joiner bootstrapped with a
23190
+ * placeholder deck would otherwise merge that placeholder into the room's
23191
+ * real content. Websocket providers emit a reliable 'synced' event; y-webrtc
23192
+ * only syncs when a peer is present, so a lone fresh-room peer opens the gate
23193
+ * after {@link INITIAL_SYNC_GRACE_MS} instead and seeds the empty doc.
23194
+ *
23195
+ * Usage per binding: create the gate with an `onOpen` callback that performs
23196
+ * the deferred first write, call `arm()` when the provider is created (and on
23197
+ * (re)connect), `open()` from the provider's sync event, gate local writes on
23198
+ * `isOpen()`, and `reset()` on session teardown.
23199
+ */
23200
+ /**
23201
+ * Create a first-write gate. `onOpen` fires exactly once per session (until
23202
+ * `reset()`), from either the provider sync signal or the grace timer.
23203
+ */
23204
+ function createSyncGate(onOpen, graceMs = INITIAL_SYNC_GRACE_MS) {
23205
+ let opened = false;
23206
+ let timer = null;
23207
+ const clear = () => {
23208
+ if (timer !== null) {
23209
+ clearTimeout(timer);
23210
+ timer = null;
23211
+ }
23212
+ };
23213
+ const open = () => {
23214
+ clear();
23215
+ if (opened) {
23216
+ return;
23217
+ }
23218
+ opened = true;
23219
+ onOpen();
23220
+ };
23221
+ return {
23222
+ isOpen: () => opened,
23223
+ arm: () => {
23224
+ clear();
23225
+ if (!opened) {
23226
+ timer = setTimeout(open, graceMs);
23227
+ }
23228
+ },
23229
+ open,
23230
+ reset: () => {
23231
+ clear();
23232
+ opened = false;
23233
+ },
23234
+ };
23235
+ }
23236
+
23177
23237
  // ---------------------------------------------------------------------------
23178
23238
  // Tuning constants (px tolerances for position / size changes)
23179
23239
  // ---------------------------------------------------------------------------
@@ -35818,6 +35878,14 @@ class CollaborationService {
35818
35878
  connectTimer = null;
35819
35879
  unobserveSlides = null;
35820
35880
  writeBack = new WriteBackScheduler();
35881
+ /**
35882
+ * First-write gate: local broadcasts are suppressed (captured as pending)
35883
+ * until the provider confirms its initial sync or the grace period lifts
35884
+ * the gate, so a late joiner never seeds its placeholder deck into a room
35885
+ * whose real content has not arrived yet.
35886
+ */
35887
+ syncGate = createSyncGate(() => this.flushPendingBroadcast());
35888
+ pendingBroadcast = null;
35821
35889
  onRemoteSlides = null;
35822
35890
  canvasWidth = DEFAULT_CANVAS_BOUND;
35823
35891
  canvasHeight = DEFAULT_CANVAS_BOUND;
@@ -35881,6 +35949,8 @@ class CollaborationService {
35881
35949
  this.awareness.on('change', this.refreshPresence);
35882
35950
  this.awareness.on('update', this.refreshPresence);
35883
35951
  this.wireStatus(config);
35952
+ this.syncGate.reset();
35953
+ this.wireSynced();
35884
35954
  this.unobserveSlides = observeYDocSlides(this.ydoc, (_events, transaction) => this.onRemoteChange(transaction));
35885
35955
  this.active.set(true);
35886
35956
  this.refreshPresence();
@@ -35937,6 +36007,50 @@ class CollaborationService {
35937
36007
  }
35938
36008
  }, CONNECTION_TIMEOUT_MS);
35939
36009
  }
36010
+ /**
36011
+ * Open the first-write gate on the provider's initial-sync confirmation.
36012
+ * y-websocket emits 'sync' with a boolean; y-webrtc emits 'synced' with an
36013
+ * object carrying a `synced` flag (and only once a peer syncs, hence the
36014
+ * grace timer). Listen to both; opening is idempotent.
36015
+ */
36016
+ wireSynced() {
36017
+ const provider = this.provider;
36018
+ if (!provider) {
36019
+ return;
36020
+ }
36021
+ const handle = (payload) => {
36022
+ const flag = payload;
36023
+ const isSynced = typeof flag === 'boolean' ? flag : flag?.synced !== false;
36024
+ if (isSynced) {
36025
+ this.syncGate.open();
36026
+ }
36027
+ };
36028
+ provider.on('sync', handle);
36029
+ provider.on('synced', handle);
36030
+ if (provider.synced === true) {
36031
+ this.syncGate.open();
36032
+ }
36033
+ else {
36034
+ this.syncGate.arm();
36035
+ }
36036
+ }
36037
+ /**
36038
+ * Perform the deferred first broadcast once the gate opens. When the doc is
36039
+ * still empty (fresh room, or nobody else present), clear the baseline so
36040
+ * the pending deck actually seeds it; when remote content already arrived,
36041
+ * the pending deck matches the applied baseline and the write is a no-op.
36042
+ */
36043
+ flushPendingBroadcast() {
36044
+ const pending = this.pendingBroadcast;
36045
+ this.pendingBroadcast = null;
36046
+ if (!pending || !this.ydoc || !this.yFactories) {
36047
+ return;
36048
+ }
36049
+ if (this.ydoc.getArray(YDOC_SLIDES_KEY).length === 0) {
36050
+ this.lastSynced = '';
36051
+ }
36052
+ this.broadcastSlides(pending);
36053
+ }
35940
36054
  /** Handle a remote Y.Doc change, skipping our own local-origin transactions. */
35941
36055
  onRemoteChange(transaction) {
35942
36056
  if (transaction?.origin === LOCAL_SYNC_ORIGIN || this.applyingRemote || !this.ydoc) {
@@ -35956,6 +36070,8 @@ class CollaborationService {
35956
36070
  }
35957
36071
  disconnect() {
35958
36072
  this.clearConnectTimer();
36073
+ this.syncGate.reset();
36074
+ this.pendingBroadcast = null;
35959
36075
  this.writeBack.cancel();
35960
36076
  this.unobserveSlides?.();
35961
36077
  this.unobserveSlides = null;
@@ -35999,6 +36115,12 @@ class CollaborationService {
35999
36115
  if (!this.ydoc || !this.yFactories || this.applyingRemote || slides.length === 0) {
36000
36116
  return;
36001
36117
  }
36118
+ if (!this.syncGate.isOpen()) {
36119
+ // Defer until the initial sync confirms; the gate flushes the latest
36120
+ // pending deck when it opens.
36121
+ this.pendingBroadcast = slides;
36122
+ return;
36123
+ }
36002
36124
  const s = JSON.stringify(slides);
36003
36125
  if (s === this.lastSynced) {
36004
36126
  return;
@@ -57243,7 +57365,7 @@ class ElementRendererComponent {
57243
57365
  }, /* @ts-ignore */
57244
57366
  ...(ngDevMode ? [{ debugName: "placeholderLabel" }] : /* istanbul ignore next */ []));
57245
57367
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: ElementRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
57246
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: ElementRendererComponent, isStandalone: true, selector: "pptx-element-renderer", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, mediaDataUrls: { classPropertyName: "mediaDataUrls", publicName: "mediaDataUrls", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, obstacles: { classPropertyName: "obstacles", publicName: "obstacles", isSignal: true, isRequired: false, transformFunction: null }, canvasWidth: { classPropertyName: "canvasWidth", publicName: "canvasWidth", isSignal: true, isRequired: false, transformFunction: null }, canvasHeight: { classPropertyName: "canvasHeight", publicName: "canvasHeight", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, fieldContext: { classPropertyName: "fieldContext", publicName: "fieldContext", isSignal: true, isRequired: false, transformFunction: null }, editTemplateMode: { classPropertyName: "editTemplateMode", publicName: "editTemplateMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cellCommit: "cellCommit", tableChange: "tableChange" }, ngImport: i0, template: `
57368
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: ElementRendererComponent, isStandalone: true, selector: "pptx-element-renderer", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, mediaDataUrls: { classPropertyName: "mediaDataUrls", publicName: "mediaDataUrls", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, obstacles: { classPropertyName: "obstacles", publicName: "obstacles", isSignal: true, isRequired: false, transformFunction: null }, canvasWidth: { classPropertyName: "canvasWidth", publicName: "canvasWidth", isSignal: true, isRequired: false, transformFunction: null }, canvasHeight: { classPropertyName: "canvasHeight", publicName: "canvasHeight", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, fieldContext: { classPropertyName: "fieldContext", publicName: "fieldContext", isSignal: true, isRequired: false, transformFunction: null }, editTemplateMode: { classPropertyName: "editTemplateMode", publicName: "editTemplateMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cellCommit: "cellCommit", tableChange: "tableChange" }, host: { classAttribute: "contents" }, ngImport: i0, template: `
57247
57369
  @switch (true) {
57248
57370
  @case (element().type === 'connector') {
57249
57371
  <pptx-connector-renderer
@@ -57517,6 +57639,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
57517
57639
  selector: 'pptx-element-renderer',
57518
57640
  standalone: true,
57519
57641
  changeDetection: ChangeDetectionStrategy.OnPush,
57642
+ host: { class: 'contents' },
57520
57643
  imports: [
57521
57644
  NgStyle,
57522
57645
  ConnectorRendererComponent,
@@ -74119,7 +74242,7 @@ class FontEmbeddingListComponent {
74119
74242
  missingCount = input(0, /* @ts-ignore */
74120
74243
  ...(ngDevMode ? [{ debugName: "missingCount" }] : /* istanbul ignore next */ []));
74121
74244
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: FontEmbeddingListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
74122
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: FontEmbeddingListComponent, isStandalone: true, selector: "pptx-font-embedding-list", inputs: { usedFontFamilies: { classPropertyName: "usedFontFamilies", publicName: "usedFontFamilies", isSignal: true, isRequired: false, transformFunction: null }, availableFamilies: { classPropertyName: "availableFamilies", publicName: "availableFamilies", isSignal: true, isRequired: false, transformFunction: null }, embeddedSet: { classPropertyName: "embeddedSet", publicName: "embeddedSet", isSignal: true, isRequired: false, transformFunction: null }, scanning: { classPropertyName: "scanning", publicName: "scanning", isSignal: true, isRequired: false, transformFunction: null }, missingCount: { classPropertyName: "missingCount", publicName: "missingCount", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
74245
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: FontEmbeddingListComponent, isStandalone: true, selector: "pptx-font-embedding-list", inputs: { usedFontFamilies: { classPropertyName: "usedFontFamilies", publicName: "usedFontFamilies", isSignal: true, isRequired: false, transformFunction: null }, availableFamilies: { classPropertyName: "availableFamilies", publicName: "availableFamilies", isSignal: true, isRequired: false, transformFunction: null }, embeddedSet: { classPropertyName: "embeddedSet", publicName: "embeddedSet", isSignal: true, isRequired: false, transformFunction: null }, scanning: { classPropertyName: "scanning", publicName: "scanning", isSignal: true, isRequired: false, transformFunction: null }, missingCount: { classPropertyName: "missingCount", publicName: "missingCount", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "contents" }, ngImport: i0, template: `
74123
74246
  <div class="pptx-ng-fonts-section">
74124
74247
  <h3 class="pptx-ng-fonts-section-title">
74125
74248
  {{ 'pptx.fontEmbedding.usedFonts' | translate: { count: usedFontFamilies().length } }}
@@ -74168,7 +74291,7 @@ class FontEmbeddingListComponent {
74168
74291
  }
74169
74292
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: FontEmbeddingListComponent, decorators: [{
74170
74293
  type: Component,
74171
- args: [{ selector: 'pptx-font-embedding-list', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
74294
+ args: [{ selector: 'pptx-font-embedding-list', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'contents' }, template: `
74172
74295
  <div class="pptx-ng-fonts-section">
74173
74296
  <h3 class="pptx-ng-fonts-section-title">
74174
74297
  {{ 'pptx.fontEmbedding.usedFonts' | translate: { count: usedFontFamilies().length } }}
@@ -76039,7 +76162,7 @@ class ViewerExtraDialogsComponent {
76039
76162
  this.svc.showPassword.set(false);
76040
76163
  }
76041
76164
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: ViewerExtraDialogsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
76042
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.5", type: ViewerExtraDialogsComponent, isStandalone: true, selector: "pptx-viewer-extra-dialogs", inputs: { activeSlideIndex: { classPropertyName: "activeSlideIndex", publicName: "activeSlideIndex", isSignal: true, isRequired: false, transformFunction: null }, selectedElementId: { classPropertyName: "selectedElementId", publicName: "selectedElementId", isSignal: true, isRequired: false, transformFunction: null }, filePath: { classPropertyName: "filePath", publicName: "filePath", isSignal: true, isRequired: false, transformFunction: null }, customShows: { classPropertyName: "customShows", publicName: "customShows", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { restoreContent: "restoreContent" }, ngImport: i0, template: `
76165
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.5", type: ViewerExtraDialogsComponent, isStandalone: true, selector: "pptx-viewer-extra-dialogs", inputs: { activeSlideIndex: { classPropertyName: "activeSlideIndex", publicName: "activeSlideIndex", isSignal: true, isRequired: false, transformFunction: null }, selectedElementId: { classPropertyName: "selectedElementId", publicName: "selectedElementId", isSignal: true, isRequired: false, transformFunction: null }, filePath: { classPropertyName: "filePath", publicName: "filePath", isSignal: true, isRequired: false, transformFunction: null }, customShows: { classPropertyName: "customShows", publicName: "customShows", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { restoreContent: "restoreContent" }, host: { classAttribute: "contents" }, ngImport: i0, template: `
76043
76166
  <pptx-equation-editor-dialog
76044
76167
  [open]="svc.showEquation()"
76045
76168
  [existingOmml]="svc.editingEquationOmml()"
@@ -76120,6 +76243,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
76120
76243
  selector: 'pptx-viewer-extra-dialogs',
76121
76244
  standalone: true,
76122
76245
  changeDetection: ChangeDetectionStrategy.OnPush,
76246
+ host: { class: 'contents' },
76123
76247
  imports: [
76124
76248
  EquationEditorDialogComponent,
76125
76249
  SetUpSlideShowDialogComponent,