@netless/app-presentation 0.1.13-beta.1 → 0.1.13-beta.2

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/dist/index.d.ts CHANGED
@@ -143,6 +143,40 @@ interface DisposableStore extends DisposableDisposer {
143
143
  */
144
144
  size(): number;
145
145
  }
146
+ /**
147
+ * Create a {@link DisposableStore} that manages {@link Disposer}s and {@link IDisposable}s.
148
+ *
149
+ * A {@link DisposableStore} is also a {@link Disposer}, which means it can be the `dispose` method of an {@link IDisposable}.
150
+ *
151
+ * A {@link DisposableStore} is also an {@link IDisposable}, which means it can be managed by another {@link DisposableStore}.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * import { type IDisposable, disposableStore } from "@wopjs/disposable";
156
+ *
157
+ * class A implements IDisposable {
158
+ * dispose = disposableStore()
159
+ * constructor() {
160
+ * this.dispose.add(...);
161
+ * }
162
+ * }
163
+ *
164
+ * class B implements IDisposable {
165
+ * dispose = disposableStore()
166
+ * a = this.dispose.add(new A())
167
+ * constructor() {
168
+ * this.dispose.add(...);
169
+ * }
170
+ * }
171
+ *
172
+ * const b = new B();
173
+ * b.dispose(); // dispose both `b` and `b.a`.
174
+ * ```
175
+ *
176
+ * @param disposables Optional array of {@link DisposableType}s added to the store.
177
+ * @returns A disposable store.
178
+ */
179
+ declare function disposableStore(disposables?: DisposableType[]): DisposableStore;
146
180
 
147
181
  interface ILazyLoadInstance {
148
182
  /**
@@ -302,15 +336,27 @@ declare class Presentation implements IDisposable<void> {
302
336
  setReadonly(readonly: boolean): void;
303
337
  setPageIndex(pageIndex: number): void;
304
338
  togglePreview(showPreview?: boolean): void;
339
+ /** Release preview images while this presentation stays in the runtime cache. */
340
+ suspendPreviewResources(): void;
305
341
  onNewPageIndex(index: number, _origin: "navigation" | "keydown" | "input" | "preview"): void;
306
342
  page(): PresentationPage | undefined;
307
343
  updateImage(): void;
308
344
  /** Returns a modified URL that points to the thumbnail image. */
309
345
  thumbnail(src: string): string;
346
+ private destroyPreviewLazyload;
310
347
  private c;
311
348
  private isEditable;
312
349
  }
313
350
 
351
+ type Subscriber<T> = (value: T) => void;
352
+ type Unsubscriber = () => void;
353
+ interface Readable<T> {
354
+ readonly value: T;
355
+ subscribe(this: void, run: Subscriber<T>): Unsubscriber;
356
+ reaction(this: void, run: Subscriber<T>): Unsubscriber;
357
+ dispose(value?: T): void;
358
+ }
359
+
314
360
  interface ScrollbarOption {
315
361
  appId: string;
316
362
  readonly?: boolean;
@@ -483,6 +529,29 @@ interface PresentationController {
483
529
  /** screenshot the current page */
484
530
  screenshotCurrentPageAsync: (context: CanvasRenderingContext2D, width?: number, height?: number) => Promise<void>;
485
531
  }
532
+ /**
533
+ * Blur thumbnail degradation (see lazy-setup design doc §15): when the app
534
+ * loses focus but stays cached (multi-runtime cache mode), swap the scene
535
+ * background `<img>` src to the low-res `PptDescription.previewURL`
536
+ * (`page.thumbnail`) via a pure DOM change and pause full-image preloading;
537
+ * restore the full image when focus comes back. A page without a real
538
+ * thumbnail keeps its measurable view container, but its background layer is
539
+ * hidden and replaced with a static pixel after TeleBox commits the blur.
540
+ *
541
+ * While degraded, every load of a background `<img>` is re-degraded
542
+ * immediately via a capture-phase `load` listener on the view container
543
+ * (`load` does not bubble but capture works). This covers remote page turns,
544
+ * SDK React re-renders and the SDK's own background-image onError retries —
545
+ * any path that (re)renders the full-size image while the app stays blurred.
546
+ */
547
+ declare const setupBlurThumbnailDegradation: (config: {
548
+ context: AppContext;
549
+ view: View;
550
+ pages: PresentationPage[];
551
+ pageIndex$: Readable<number>;
552
+ app: Presentation;
553
+ dispose: ReturnType<typeof disposableStore>;
554
+ }) => void;
486
555
  declare const NetlessAppPresentation: NetlessApp<PresentationAttributes, {}, PresentationAppOptions, PresentationController> & {
487
556
  teardown(context: AppContext): void;
488
557
  };
@@ -510,5 +579,5 @@ declare const install: (register: RegisterFn, options?: InstallOptions) => Promi
510
579
 
511
580
  declare const version: string;
512
581
 
513
- export { NetlessAppPresentation, Presentation, Scrollbar, NetlessAppPresentation as default, install, version };
582
+ export { NetlessAppPresentation, Presentation, Scrollbar, NetlessAppPresentation as default, install, setupBlurThumbnailDegradation, version };
514
583
  export type { InstallOptions, Logger, PresentationAppOptions, PresentationAttributes, PresentationConfig, PresentationController, PresentationPage, RegisterFn, ScrollbarEventCallback, ScrollbarOption };
@@ -1316,10 +1316,7 @@ var NetlessAppPresentation = (function (exports) {
1316
1316
  if (this.initialized)
1317
1317
  return;
1318
1318
  this.initialized = true;
1319
- this.dispose.add(() => {
1320
- var _a2;
1321
- return (_a2 = this.previewLazyload) == null ? void 0 : _a2.destroy();
1322
- });
1319
+ this.dispose.add(() => this.destroyPreviewLazyload());
1323
1320
  this.contentDOM.classList.toggle(this.c("readonly"), this.readonly);
1324
1321
  this.dom.appendChild(this.contentDOM);
1325
1322
  const previewMask = this.contentDOM.appendChild(document.createElement("div"));
@@ -1472,6 +1469,17 @@ var NetlessAppPresentation = (function (exports) {
1472
1469
  }
1473
1470
  }
1474
1471
  }
1472
+ /** Release preview images while this presentation stays in the runtime cache. */
1473
+ suspendPreviewResources() {
1474
+ this.togglePreview(false);
1475
+ this.destroyPreviewLazyload();
1476
+ this.previewDOM.querySelectorAll("img").forEach((img) => {
1477
+ img.removeAttribute("src");
1478
+ img.removeAttribute("srcset");
1479
+ img.removeAttribute("sizes");
1480
+ LazyLoad.resetStatus(img);
1481
+ });
1482
+ }
1475
1483
  // [origin] means the cause:
1476
1484
  // - navigation: user clicked on the left / right button on footer
1477
1485
  // - input: user filled the bottom-right page number input box
@@ -1506,6 +1514,11 @@ var NetlessAppPresentation = (function (exports) {
1506
1514
  return src;
1507
1515
  }
1508
1516
  }
1517
+ destroyPreviewLazyload() {
1518
+ var _a;
1519
+ (_a = this.previewLazyload) == null ? void 0 : _a.destroy();
1520
+ this.previewLazyload = null;
1521
+ }
1509
1522
  c(className) {
1510
1523
  return `${this.namespace}-${className}`;
1511
1524
  }
@@ -1518,7 +1531,7 @@ var NetlessAppPresentation = (function (exports) {
1518
1531
  };
1519
1532
 
1520
1533
  // src/style.scss?inline
1521
- var style_default = ".netless-app-presentation{height:100%;display:flex;flex-flow:column nowrap}.netless-app-presentation-content{position:relative;height:100%;overflow:hidden}.netless-app-presentation-preview-mask{display:none;position:absolute;z-index:200;top:0;left:0;width:100%;height:100%}.netless-app-presentation-preview{display:flex;flex-direction:column;align-items:center;position:absolute;z-index:300;top:0;left:0;width:33%;max-width:200px;height:100%;padding-top:10px;transform:translate(-100%);background:#ededf0e6;box-shadow:inset -1px 0 #0000001c;transition:transform .4s;overflow:auto;overflow-y:scroll}.netless-app-presentation-preview-active .netless-app-presentation-preview-mask{display:block}.netless-app-presentation-preview-active .netless-app-presentation-preview{transform:translate(0)}.netless-app-presentation-preview-page{position:relative;display:block;width:55%;margin-bottom:10px;font-size:0;color:#0000;outline:none;border:7px solid rgba(0,0,0,0);border-radius:4px;transition:border-color .3s;user-select:none}.netless-app-presentation-preview-page:hover,.netless-app-presentation-preview-page.netless-app-presentation-preview-page-active{border-color:#444e601a}.netless-app-presentation-preview-page>img{width:100%;height:auto;box-sizing:border-box;border:1px solid rgba(0,0,0,.5);border-radius:1px;background-color:#fff;box-shadow:0 2px 8px #0000004d}.netless-app-presentation-preview-page-name{position:absolute;top:1px;left:-10px;transform:translate(-100%);text-align:right;font-size:12px;color:#5f5f5f;user-select:none}.netless-app-presentation-footer{box-sizing:border-box;height:26px;display:flex;align-items:center;padding:0 16px;border-top:1px solid #eeeef7;color:#191919}.netless-app-presentation-float-footer{width:100%;min-height:26px;position:absolute;left:0;bottom:0;z-index:2000;background:#f9f9fce6;transition:opacity .4s}.netless-app-presentation-footer-btn{box-sizing:border-box;width:26px;height:26px;font-size:0;margin:0;padding:3px;border:none;border-radius:1px;outline:none;color:currentColor;background:#0000;transition:background .4s;cursor:pointer;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}.netless-app-presentation-footer-btn:hover{background:#ededf0e6}@media (hover: none){.netless-app-presentation-footer-btn:hover{background:#0000!important}}.netless-app-presentation-footer-btn>svg{width:100%;height:100%}.netless-app-presentation-footer-btn>svg:nth-of-type(2){display:none}.netless-app-presentation-footer-btn.netless-app-presentation-footer-btn-playing>svg:nth-of-type(1){display:none}.netless-app-presentation-footer-btn.netless-app-presentation-footer-btn-playing>svg:nth-of-type(2){display:initial}.netless-app-presentation-footer-btn~.netless-app-presentation-footer-btn{margin-left:15px}.netless-app-presentation-page-jumps{flex:1;display:flex;justify-content:center;align-items:center}.netless-app-presentation-page-number{margin-left:auto;font-size:13px;user-select:none;white-space:nowrap;word-break:keep-all}.netless-app-presentation-page-number-input{border:none;outline:none;width:3em;margin:0;padding:0 2px;text-align:right;font-size:13px;line-height:1;font-weight:400;font-family:inherit;border-radius:2px;color:currentColor;background:#0000;transition:background .4s;user-select:text;-webkit-tap-highlight-color:rgba(0,0,0,0)}.netless-app-presentation-page-number-input:hover,.netless-app-presentation-page-number-input:focus,.netless-app-presentation-page-number-input:active{background:#fff;box-shadow:#63636333 0 2px 8px}.netless-app-presentation-readonly.netless-app-presentation-footer{display:none}.netless-app-presentation-wb-view{position:absolute;top:0;left:0;width:100%;height:100%;z-index:100;overflow:hidden;transition:opacity .2s}.netless-app-presentation-image{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.netless-app-presentation-image>img{max-width:100%;max-height:100%;object-fit:contain}.telebox-color-scheme-dark .netless-app-presentation-page-number-input{color:#a6a6a8}.telebox-color-scheme-dark .netless-app-presentation-page-number-input:active,.telebox-color-scheme-dark .netless-app-presentation-page-number-input:focus,.telebox-color-scheme-dark .netless-app-presentation-page-number-input:hover{color:#222}.telebox-color-scheme-dark .netless-app-presentation-footer{color:#a6a6a8;background:#2d2d33;border-top:none}.telebox-color-scheme-dark .netless-app-presentation-footer-btn:hover{background:#212126}.telebox-color-scheme-dark .netless-app-presentation-preview{background:#323232e6}.netless-app-presentation-scrollbar-container{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;z-index:101}.netless-app-presentation-scrollbar-container-x,.netless-app-presentation-scrollbar-container-y{position:absolute;pointer-events:none;display:flex;justify-content:center;box-sizing:border-box;padding:0}.netless-app-presentation-scrollbar-container-x{bottom:0;left:0;height:6px;width:100%;padding-right:6px}.netless-app-presentation-scrollbar-container-y{top:0;right:0;width:6px;height:100%;flex-direction:column;padding-bottom:6px}.netless-app-presentation-scrollbar-x,.netless-app-presentation-scrollbar-y{border-radius:2px;background-color:#1b1f4d4d;pointer-events:auto;cursor:default}.netless-app-presentation-scrollbar-x{height:6px}.netless-app-presentation-scrollbar-y{width:6px}";
1534
+ var style_default = ".netless-app-presentation{height:100%;display:flex;flex-flow:column nowrap}.netless-app-presentation-content{position:relative;height:100%;overflow:hidden}.netless-app-presentation-background-suspended .background{display:none!important}.netless-app-presentation-preview-mask{display:none;position:absolute;z-index:200;top:0;left:0;width:100%;height:100%}.netless-app-presentation-preview{display:flex;flex-direction:column;align-items:center;position:absolute;z-index:300;top:0;left:0;width:33%;max-width:200px;height:100%;padding-top:10px;transform:translate(-100%);background:#ededf0e6;box-shadow:inset -1px 0 #0000001c;transition:transform .4s;overflow:auto;overflow-y:scroll}.netless-app-presentation-preview-active .netless-app-presentation-preview-mask{display:block}.netless-app-presentation-preview-active .netless-app-presentation-preview{transform:translate(0)}.netless-app-presentation-preview-page{position:relative;display:block;width:55%;margin-bottom:10px;font-size:0;color:#0000;outline:none;border:7px solid rgba(0,0,0,0);border-radius:4px;transition:border-color .3s;user-select:none}.netless-app-presentation-preview-page:hover,.netless-app-presentation-preview-page.netless-app-presentation-preview-page-active{border-color:#444e601a}.netless-app-presentation-preview-page>img{width:100%;height:auto;box-sizing:border-box;border:1px solid rgba(0,0,0,.5);border-radius:1px;background-color:#fff;box-shadow:0 2px 8px #0000004d}.netless-app-presentation-preview-page-name{position:absolute;top:1px;left:-10px;transform:translate(-100%);text-align:right;font-size:12px;color:#5f5f5f;user-select:none}.netless-app-presentation-footer{box-sizing:border-box;height:26px;display:flex;align-items:center;padding:0 16px;border-top:1px solid #eeeef7;color:#191919}.netless-app-presentation-float-footer{width:100%;min-height:26px;position:absolute;left:0;bottom:0;z-index:2000;background:#f9f9fce6;transition:opacity .4s}.netless-app-presentation-footer-btn{box-sizing:border-box;width:26px;height:26px;font-size:0;margin:0;padding:3px;border:none;border-radius:1px;outline:none;color:currentColor;background:#0000;transition:background .4s;cursor:pointer;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}.netless-app-presentation-footer-btn:hover{background:#ededf0e6}@media (hover: none){.netless-app-presentation-footer-btn:hover{background:#0000!important}}.netless-app-presentation-footer-btn>svg{width:100%;height:100%}.netless-app-presentation-footer-btn>svg:nth-of-type(2){display:none}.netless-app-presentation-footer-btn.netless-app-presentation-footer-btn-playing>svg:nth-of-type(1){display:none}.netless-app-presentation-footer-btn.netless-app-presentation-footer-btn-playing>svg:nth-of-type(2){display:initial}.netless-app-presentation-footer-btn~.netless-app-presentation-footer-btn{margin-left:15px}.netless-app-presentation-page-jumps{flex:1;display:flex;justify-content:center;align-items:center}.netless-app-presentation-page-number{margin-left:auto;font-size:13px;user-select:none;white-space:nowrap;word-break:keep-all}.netless-app-presentation-page-number-input{border:none;outline:none;width:3em;margin:0;padding:0 2px;text-align:right;font-size:13px;line-height:1;font-weight:400;font-family:inherit;border-radius:2px;color:currentColor;background:#0000;transition:background .4s;user-select:text;-webkit-tap-highlight-color:rgba(0,0,0,0)}.netless-app-presentation-page-number-input:hover,.netless-app-presentation-page-number-input:focus,.netless-app-presentation-page-number-input:active{background:#fff;box-shadow:#63636333 0 2px 8px}.netless-app-presentation-readonly.netless-app-presentation-footer{display:none}.netless-app-presentation-wb-view{position:absolute;top:0;left:0;width:100%;height:100%;z-index:100;overflow:hidden;transition:opacity .2s}.netless-app-presentation-image{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.netless-app-presentation-image>img{max-width:100%;max-height:100%;object-fit:contain}.telebox-color-scheme-dark .netless-app-presentation-page-number-input{color:#a6a6a8}.telebox-color-scheme-dark .netless-app-presentation-page-number-input:active,.telebox-color-scheme-dark .netless-app-presentation-page-number-input:focus,.telebox-color-scheme-dark .netless-app-presentation-page-number-input:hover{color:#222}.telebox-color-scheme-dark .netless-app-presentation-footer{color:#a6a6a8;background:#2d2d33;border-top:none}.telebox-color-scheme-dark .netless-app-presentation-footer-btn:hover{background:#212126}.telebox-color-scheme-dark .netless-app-presentation-preview{background:#323232e6}.netless-app-presentation-scrollbar-container{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;z-index:101}.netless-app-presentation-scrollbar-container-x,.netless-app-presentation-scrollbar-container-y{position:absolute;pointer-events:none;display:flex;justify-content:center;box-sizing:border-box;padding:0}.netless-app-presentation-scrollbar-container-x{bottom:0;left:0;height:6px;width:100%;padding-right:6px}.netless-app-presentation-scrollbar-container-y{top:0;right:0;width:6px;height:100%;flex-direction:column;padding-bottom:6px}.netless-app-presentation-scrollbar-x,.netless-app-presentation-scrollbar-y{border-radius:2px;background-color:#1b1f4d4d;pointer-events:auto;cursor:default}.netless-app-presentation-scrollbar-x{height:6px}.netless-app-presentation-scrollbar-y{width:6px}";
1522
1535
 
1523
1536
  // src/store.ts
1524
1537
  function noop() {
@@ -1964,6 +1977,23 @@ var NetlessAppPresentation = (function (exports) {
1964
1977
  };
1965
1978
  }
1966
1979
 
1980
+ // src/runtime-lifecycle.ts
1981
+ var createPresentationRuntimeTeardown = (box, dispose2) => {
1982
+ let tornDown = false;
1983
+ return () => {
1984
+ if (tornDown)
1985
+ return;
1986
+ tornDown = true;
1987
+ try {
1988
+ dispose2();
1989
+ } finally {
1990
+ box.unmountContent();
1991
+ box.unmountFooter();
1992
+ box.unmountStyles();
1993
+ }
1994
+ };
1995
+ };
1996
+
1967
1997
  // src/app-presentation.ts
1968
1998
  var import_debounce2 = __toESM(require_debounce());
1969
1999
  var emptySceneName = "$$empty$$";
@@ -1980,6 +2010,10 @@ var NetlessAppPresentation = (function (exports) {
1980
2010
  return container.querySelector('img[alt="background"]');
1981
2011
  };
1982
2012
  var isBlurDegradationAllowed = (context) => {
2013
+ var _a;
2014
+ if (((_a = context.getWindowManager()) == null ? void 0 : _a.lazySetupInMaximizedMode) !== true) {
2015
+ return false;
2016
+ }
1983
2017
  const boxStatus = context.getBoxStatus();
1984
2018
  if (boxStatus) {
1985
2019
  return boxStatus !== "normal";
@@ -2025,17 +2059,55 @@ var NetlessAppPresentation = (function (exports) {
2025
2059
  return url;
2026
2060
  }
2027
2061
  };
2062
+ var TRANSPARENT_BACKGROUND = "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";
2063
+ var BACKGROUND_SUSPENDED_CLASS = "netless-app-presentation-background-suspended";
2064
+ var hasUsableThumbnail = (page) => {
2065
+ const thumbnailURL = page.thumbnail;
2066
+ if (!thumbnailURL)
2067
+ return false;
2068
+ try {
2069
+ const source = new URL(page.src, window.location.href);
2070
+ const thumbnail = new URL(thumbnailURL, window.location.href);
2071
+ const sameResourcePath = source.origin === thumbnail.origin && source.pathname === thumbnail.pathname;
2072
+ if (/\.gif$/i.test(source.pathname) && sameResourcePath)
2073
+ return false;
2074
+ } catch (e) {
2075
+ if (/\.gif(?:$|[?#])/i.test(page.src))
2076
+ return false;
2077
+ }
2078
+ return resolveImageUrl(thumbnailURL) !== resolveImageUrl(page.src);
2079
+ };
2028
2080
  var setupBlurThumbnailDegradation = (config) => {
2029
2081
  const { context, view, pages, pageIndex$, app, dispose: dispose2 } = config;
2082
+ const box = context.getBox();
2083
+ const boxBlurClass = box.wrapClassName("blur");
2030
2084
  let blurEpisode = false;
2031
2085
  let reDegradeEnabled = false;
2086
+ let boxBlurred = box.$box.classList.contains(boxBlurClass);
2087
+ const setBackgroundSuspended = (suspended) => {
2088
+ app.whiteboardDOM.classList.toggle(
2089
+ BACKGROUND_SUSPENDED_CLASS,
2090
+ suspended
2091
+ );
2092
+ };
2032
2093
  const degrade = () => {
2033
2094
  const page = pages[pageIndex$.value];
2034
- if (!(page == null ? void 0 : page.thumbnail))
2095
+ if (!page)
2035
2096
  return;
2097
+ const thumbnailURL = page.thumbnail;
2036
2098
  const img = findBackgroundImage(view);
2037
- if (img && resolveImageUrl(img.src) !== resolveImageUrl(page.thumbnail)) {
2038
- img.src = page.thumbnail;
2099
+ if (!thumbnailURL || !hasUsableThumbnail(page)) {
2100
+ if (boxBlurred) {
2101
+ setBackgroundSuspended(true);
2102
+ if (img && resolveImageUrl(img.src) !== resolveImageUrl(TRANSPARENT_BACKGROUND)) {
2103
+ img.src = TRANSPARENT_BACKGROUND;
2104
+ }
2105
+ }
2106
+ return;
2107
+ }
2108
+ setBackgroundSuspended(false);
2109
+ if (img && resolveImageUrl(img.src) !== resolveImageUrl(thumbnailURL)) {
2110
+ img.src = thumbnailURL;
2039
2111
  }
2040
2112
  };
2041
2113
  const restore2 = () => {
@@ -2044,6 +2116,24 @@ var NetlessAppPresentation = (function (exports) {
2044
2116
  if (page && img && resolveImageUrl(img.src) !== resolveImageUrl(page.src)) {
2045
2117
  img.src = page.src;
2046
2118
  }
2119
+ setBackgroundSuspended(false);
2120
+ };
2121
+ const suspendCachedResources = () => {
2122
+ if (!blurEpisode || !boxBlurred)
2123
+ return;
2124
+ app.suspendPreviewResources();
2125
+ const page = pages[pageIndex$.value];
2126
+ if (!page || !hasUsableThumbnail(page) || reDegradeEnabled) {
2127
+ degrade();
2128
+ }
2129
+ };
2130
+ const restoreFocusedResources = () => {
2131
+ if (!blurEpisode)
2132
+ return;
2133
+ blurEpisode = false;
2134
+ reDegradeEnabled = false;
2135
+ restore2();
2136
+ app.preload.resume(pageIndex$.value);
2047
2137
  };
2048
2138
  const isBackgroundImg = (target) => {
2049
2139
  var _a;
@@ -2052,28 +2142,29 @@ var NetlessAppPresentation = (function (exports) {
2052
2142
  el && el.tagName === "IMG" && ((_a = el.matches) == null ? void 0 : _a.call(el, 'img[alt="background"]'))
2053
2143
  );
2054
2144
  };
2145
+ const ownsTarget = (target) => {
2146
+ if (!isBackgroundImg(target))
2147
+ return false;
2148
+ const el = view.divElement;
2149
+ return !!(el && el.contains(target));
2150
+ };
2055
2151
  const onImgLoad = (ev) => {
2056
2152
  if (!blurEpisode || !reDegradeEnabled)
2057
2153
  return;
2058
- if (!isBackgroundImg(ev.target))
2154
+ if (!ownsTarget(ev.target))
2059
2155
  return;
2060
2156
  degrade();
2061
2157
  };
2062
2158
  const onImgError = (ev) => {
2063
2159
  if (!blurEpisode || !reDegradeEnabled)
2064
2160
  return;
2065
- if (!isBackgroundImg(ev.target))
2161
+ if (!ownsTarget(ev.target))
2066
2162
  return;
2067
2163
  reDegradeEnabled = false;
2068
2164
  };
2069
2165
  const offFocus = context.emitter.on("focus", (isFocused) => {
2070
2166
  if (isFocused) {
2071
- if (blurEpisode) {
2072
- blurEpisode = false;
2073
- reDegradeEnabled = false;
2074
- restore2();
2075
- app.preload.resume(pageIndex$.value);
2076
- }
2167
+ restoreFocusedResources();
2077
2168
  } else {
2078
2169
  if (!isBlurDegradationAllowed(context))
2079
2170
  return;
@@ -2081,29 +2172,41 @@ var NetlessAppPresentation = (function (exports) {
2081
2172
  reDegradeEnabled = true;
2082
2173
  degrade();
2083
2174
  app.preload.pause();
2175
+ suspendCachedResources();
2084
2176
  }
2085
2177
  });
2086
2178
  dispose2.add(offFocus);
2087
- const viewEl = view.divElement;
2088
- if (viewEl && typeof viewEl.addEventListener === "function") {
2089
- viewEl.addEventListener("load", onImgLoad, true);
2090
- viewEl.addEventListener("error", onImgError, true);
2091
- dispose2.add(() => {
2092
- viewEl.removeEventListener("load", onImgLoad, true);
2093
- viewEl.removeEventListener("error", onImgError, true);
2094
- });
2095
- } else {
2096
- warnOnceMissingViewContainer(view);
2097
- }
2098
- };
2099
- var degradedWarnedViews = /* @__PURE__ */ new WeakSet();
2100
- var warnOnceMissingViewContainer = (view) => {
2101
- if (degradedWarnedViews.has(view))
2102
- return;
2103
- degradedWarnedViews.add(view);
2104
- console.warn(
2105
- "[Presentation]: blur thumbnail degradation runs without re-degrade coverage (view container unavailable)"
2106
- );
2179
+ const updateBoxBlurred = (blurred) => {
2180
+ if (boxBlurred === blurred)
2181
+ return;
2182
+ boxBlurred = blurred;
2183
+ if (blurred)
2184
+ suspendCachedResources();
2185
+ else
2186
+ restoreFocusedResources();
2187
+ };
2188
+ const onBoxBlur = () => updateBoxBlurred(true);
2189
+ const onBoxFocus = () => updateBoxBlurred(false);
2190
+ box.events.on("blur", onBoxBlur);
2191
+ box.events.on("focus", onBoxFocus);
2192
+ dispose2.add(() => {
2193
+ box.events.off("blur", onBoxBlur);
2194
+ box.events.off("focus", onBoxFocus);
2195
+ });
2196
+ const boxClassObserver = new MutationObserver(() => {
2197
+ updateBoxBlurred(box.$box.classList.contains(boxBlurClass));
2198
+ });
2199
+ boxClassObserver.observe(box.$box, {
2200
+ attributes: true,
2201
+ attributeFilter: ["class"]
2202
+ });
2203
+ dispose2.add(() => boxClassObserver.disconnect());
2204
+ document.addEventListener("load", onImgLoad, true);
2205
+ document.addEventListener("error", onImgError, true);
2206
+ dispose2.add(() => {
2207
+ document.removeEventListener("load", onImgLoad, true);
2208
+ document.removeEventListener("error", onImgError, true);
2209
+ });
2107
2210
  };
2108
2211
  var createLogger = (room) => {
2109
2212
  if (room && room.logger) {
@@ -2526,7 +2629,7 @@ var NetlessAppPresentation = (function (exports) {
2526
2629
  options.useClipView
2527
2630
  )
2528
2631
  );
2529
- app.contentDOM.dataset.appPresentationVersion = "0.1.13-beta.1";
2632
+ app.contentDOM.dataset.appPresentationVersion = "0.1.13-beta.2";
2530
2633
  app.scaleDocsToFit = scaleDocsToFit;
2531
2634
  app.log = log;
2532
2635
  app.warn = warn;
@@ -2768,6 +2871,10 @@ var NetlessAppPresentation = (function (exports) {
2768
2871
  };
2769
2872
  let disposed = false;
2770
2873
  let offDestroy;
2874
+ const teardownRuntime = createPresentationRuntimeTeardown(
2875
+ box,
2876
+ dispose2
2877
+ );
2771
2878
  const teardown = () => {
2772
2879
  if (disposed)
2773
2880
  return;
@@ -2775,7 +2882,7 @@ var NetlessAppPresentation = (function (exports) {
2775
2882
  const removeDestroy = offDestroy;
2776
2883
  offDestroy = void 0;
2777
2884
  removeDestroy == null ? void 0 : removeDestroy();
2778
- dispose2();
2885
+ teardownRuntime();
2779
2886
  };
2780
2887
  offDestroy = context.emitter.on("destroy", teardown);
2781
2888
  teardownByContext.set(context, teardown);
@@ -2992,7 +3099,7 @@ var NetlessAppPresentation = (function (exports) {
2992
3099
  const setupReadyTimeout = (_d = options.setupReadyTimeout) != null ? _d : DEFAULT_SETUP_READY_TIMEOUT;
2993
3100
  return prepareScenesPromise.then(async () => {
2994
3101
  const loaded = await waitForCurrentPageImage(view, setupReadyTimeout, () => disposed);
2995
- if (!loaded) {
3102
+ if (!loaded && !disposed) {
2996
3103
  warn(`[Presentation] setup ready wait timed out after ${setupReadyTimeout}ms`);
2997
3104
  }
2998
3105
  return controller;
@@ -3082,13 +3189,14 @@ var NetlessAppPresentation = (function (exports) {
3082
3189
  };
3083
3190
 
3084
3191
  // src/index.ts
3085
- var version = "0.1.13-beta.1";
3192
+ var version = "0.1.13-beta.2";
3086
3193
 
3087
3194
  exports.NetlessAppPresentation = NetlessAppPresentation;
3088
3195
  exports.Presentation = Presentation;
3089
3196
  exports.Scrollbar = Scrollbar;
3090
3197
  exports.default = NetlessAppPresentation;
3091
3198
  exports.install = install;
3199
+ exports.setupBlurThumbnailDegradation = setupBlurThumbnailDegradation;
3092
3200
  exports.version = version;
3093
3201
 
3094
3202
  Object.defineProperty(exports, '__esModule', { value: true });