@embedpdf/plugin-thumbnail 1.5.0 → 2.0.0-next.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.
Files changed (36) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +316 -84
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/actions.d.ts +43 -0
  6. package/dist/lib/index.d.ts +5 -2
  7. package/dist/lib/reducer.d.ts +6 -0
  8. package/dist/lib/thumbnail-plugin.d.ts +19 -17
  9. package/dist/lib/types.d.ts +38 -2
  10. package/dist/preact/index.cjs +1 -1
  11. package/dist/preact/index.cjs.map +1 -1
  12. package/dist/preact/index.js +39 -18
  13. package/dist/preact/index.js.map +1 -1
  14. package/dist/react/index.cjs +1 -1
  15. package/dist/react/index.cjs.map +1 -1
  16. package/dist/react/index.js +39 -18
  17. package/dist/react/index.js.map +1 -1
  18. package/dist/shared/components/thumbnail-img.d.ts +5 -1
  19. package/dist/shared/components/thumbnails-pane.d.ts +6 -7
  20. package/dist/shared-preact/components/thumbnail-img.d.ts +5 -1
  21. package/dist/shared-preact/components/thumbnails-pane.d.ts +6 -7
  22. package/dist/shared-react/components/thumbnail-img.d.ts +5 -1
  23. package/dist/shared-react/components/thumbnails-pane.d.ts +6 -7
  24. package/dist/svelte/components/ThumbImg.svelte.d.ts +4 -0
  25. package/dist/svelte/components/ThumbnailsPane.svelte.d.ts +4 -0
  26. package/dist/svelte/index.cjs +1 -1
  27. package/dist/svelte/index.cjs.map +1 -1
  28. package/dist/svelte/index.js +44 -25
  29. package/dist/svelte/index.js.map +1 -1
  30. package/dist/vue/components/thumbnail-img.vue.d.ts +8 -3
  31. package/dist/vue/components/thumbnails-pane.vue.d.ts +9 -2
  32. package/dist/vue/index.cjs +1 -1
  33. package/dist/vue/index.cjs.map +1 -1
  34. package/dist/vue/index.js +135 -79
  35. package/dist/vue/index.js.map +1 -1
  36. package/package.json +7 -7
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("@embedpdf/core"),i=require("@embedpdf/models"),e="thumbnail",s={id:e,name:"Thumbnail Plugin",version:"1.0.0",provides:["thumbnail"],requires:["render"],optional:["scroll"],defaultConfig:{enabled:!0,width:150,gap:10,buffer:3,labelHeight:16,autoScroll:!0,scrollBehavior:"smooth",imagePadding:0,paddingY:0}},o=class extends t.BasePlugin{constructor(i,e,s){var o;super(i,e),this.cfg=s,this.scrollCapability=null,this.thumbs=[],this.window=null,this.viewportH=0,this.scrollY=0,this.emitWindow=t.createBehaviorEmitter(),this.refreshPages$=t.createEmitter(),this.taskCache=new Map,this.canAutoScroll=!0,this.scrollTo$=t.createBehaviorEmitter(),this.renderCapability=this.registry.getPlugin("render").provides(),this.scrollCapability=(null==(o=this.registry.getPlugin("scroll"))?void 0:o.provides())??null,this.coreStore.onAction(t.SET_DOCUMENT,((t,i)=>{this.taskCache.clear(),this.setWindowState(i)})),this.coreStore.onAction(t.REFRESH_PAGES,(t=>{this.refreshPages$.emit(t.payload);for(const i of t.payload)this.taskCache.delete(i)})),this.scrollCapability&&!1!==this.cfg.autoScroll&&(this.scrollCapability.onPageChangeState((({isChanging:t,targetPage:i})=>{this.canAutoScroll=!t,t||this.scrollToThumb(i-1)})),this.scrollCapability.onPageChange((({pageNumber:t})=>{this.canAutoScroll&&this.scrollToThumb(t-1)})))}async initialize(){}onRefreshPages(t){return this.refreshPages$.on(t)}onWindow(t){return this.emitWindow.on(t)}onScrollTo(t){return this.scrollTo$.on(t)}setWindowState(t){const i=t.core;if(!i.document)return;const e=this.cfg.width??120,s=this.cfg.labelHeight??16,o=this.cfg.gap??8,h=this.cfg.imagePadding??0,r=this.cfg.paddingY??0,a=Math.max(1,e-2*h);let n=r;this.thumbs=i.document.pages.map((t=>{const i=t.size.height/t.size.width,e=Math.round(a*i),r=h+e+h+s,l={pageIndex:t.index,width:a,height:e,wrapperHeight:r,top:n,labelHeight:s,padding:h};return n+=r+o,l})),this.window={start:-1,end:-1,items:[],totalHeight:n-o+r},this.viewportH>0?this.updateWindow(this.scrollY,this.viewportH):this.emitWindow.emit(this.window)}buildCapability(){return{renderThumb:(t,i)=>this.renderThumb(t,i),scrollToThumb:t=>this.scrollToThumb(t)}}updateWindow(t,i){const e=this.cfg.buffer??3;if(this.scrollY=t,this.viewportH=i,!this.window||0===this.thumbs.length)return;let s=0,o=this.thumbs.length-1,h=0;for(;s<=o;){const i=s+o>>1,e=this.thumbs[i];e.top+e.wrapperHeight<t?s=i+1:(h=i,o=i-1)}let r=h;const a=t+i;for(;r+1<this.thumbs.length&&this.thumbs[r].top<a;)r++;r=Math.min(this.thumbs.length-1,r+e);const n=Math.max(0,h-e);n===this.window.start&&r===this.window.end||(this.window={start:n,end:r,items:this.thumbs.slice(n,r+1),totalHeight:this.window.totalHeight},this.emitWindow.emit(this.window))}scrollToThumb(t){if(!this.window)return;const i=this.thumbs[t];if(!i)return;const e=this.cfg.scrollBehavior??"smooth",s=this.cfg.paddingY??0;if(this.viewportH<=0){const t=Math.max(s,i.top-i.wrapperHeight);return void this.scrollTo$.emit({top:t,behavior:e})}const o=i.top,h=i.top+i.wrapperHeight,r=o<this.scrollY+8+s,a=h>this.scrollY+this.viewportH-8;r?this.scrollTo$.emit({top:Math.max(0,o-s),behavior:e}):a&&this.scrollTo$.emit({top:Math.max(0,h-this.viewportH+s),behavior:e})}renderThumb(t,e){if(this.taskCache.has(t))return this.taskCache.get(t);const s=this.coreState.core.document.pages[t],o=this.cfg.width??120,h=this.cfg.imagePadding??0,r=Math.max(1,o-2*h)/s.size.width,a=this.renderCapability.renderPageRect({pageIndex:t,rect:{origin:{x:0,y:0},size:s.size},options:{scaleFactor:r,dpr:e}});return this.taskCache.set(t,a),a.wait(i.ignore,(()=>this.taskCache.delete(t))),a}};o.id="thumbnail";let h=o;const r={manifest:s,create:(t,i)=>new h(e,t,i),reducer:()=>{},initialState:{}};exports.THUMBNAIL_PLUGIN_ID=e,exports.ThumbnailPlugin=h,exports.ThumbnailPluginPackage=r,exports.manifest=s;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("@embedpdf/core"),e=require("@embedpdf/models"),o="thumbnail",i={id:o,name:"Thumbnail Plugin",version:"1.0.0",provides:["thumbnail"],requires:["render"],optional:["scroll"],defaultConfig:{enabled:!0,width:150,gap:10,buffer:3,labelHeight:16,autoScroll:!0,scrollBehavior:"smooth",imagePadding:0,paddingY:0}},n="THUMBNAIL/INIT_STATE",s="THUMBNAIL/CLEANUP_STATE",a="THUMBNAIL/SET_ACTIVE_DOCUMENT",r="THUMBNAIL/SET_WINDOW_STATE",c="THUMBNAIL/UPDATE_VIEWPORT_METRICS";function l(t,e){return{type:n,payload:{documentId:t,state:e}}}function d(t){return{type:s,payload:t}}function h(t,e){return{type:r,payload:{documentId:t,window:e}}}function u(t,e,o){return{type:c,payload:{documentId:t,scrollY:e,viewportH:o}}}const m={thumbs:[],window:null,viewportH:0,scrollY:0},p={documents:{},activeDocumentId:null},g=(t=p,e)=>{switch(e.type){case n:{const{documentId:o,state:i}=e.payload;return{...t,documents:{...t.documents,[o]:i},activeDocumentId:t.activeDocumentId??o}}case s:{const o=e.payload,{[o]:i,...n}=t.documents;return{...t,documents:n,activeDocumentId:t.activeDocumentId===o?null:t.activeDocumentId}}case a:return{...t,activeDocumentId:e.payload};case r:{const{documentId:o,window:i}=e.payload,n=t.documents[o];return n?{...t,documents:{...t.documents,[o]:{...n,window:i}}}:t}case c:{const{documentId:o,scrollY:i,viewportH:n}=e.payload,s=t.documents[o];return s?{...t,documents:{...t.documents,[o]:{...s,scrollY:i,viewportH:n}}}:t}default:return t}},w=class extends t.BasePlugin{constructor(e,o,i){var n;super(e,o),this.cfg=i,this.scrollCapability=null,this.taskCaches=new Map,this.canAutoScroll=new Map,this.window$=t.createScopedEmitter((t,e)=>({documentId:t,window:e})),this.scrollTo$=t.createScopedEmitter((t,e)=>({documentId:t,options:e})),this.refreshPages$=t.createScopedEmitter((t,e)=>({documentId:t,pages:e}),{cache:!1}),this.renderCapability=this.registry.getPlugin("render").provides(),this.scrollCapability=(null==(n=this.registry.getPlugin("scroll"))?void 0:n.provides())??null,this.coreStore.onAction(t.REFRESH_PAGES,t=>{const e=t.payload.documentId??this.getActiveDocumentId(),o=t.payload.pageIndexes;this.refreshPages$.emit(e,o);const i=this.taskCaches.get(e);if(i)for(const n of o)i.delete(n)}),this.scrollCapability&&!1!==this.cfg.autoScroll&&(this.scrollCapability.onPageChangeState(({documentId:t,state:e})=>{this.canAutoScroll.set(t,!e.isChanging),e.isChanging||this.scrollToThumb(e.targetPage-1,t)}),this.scrollCapability.onPageChange(({documentId:t,pageNumber:e})=>{!1!==this.canAutoScroll.get(t)&&this.scrollToThumb(e-1,t)}))}onDocumentLoadingStarted(t){this.dispatch(l(t,{...m})),this.taskCaches.set(t,new Map),this.canAutoScroll.set(t,!0),this.logger.debug("ThumbnailPlugin","DocumentOpened",`Initialized thumbnail state for document: ${t}`)}onDocumentLoaded(t){this.calculateWindowState(t)}onDocumentClosed(t){this.dispatch(d(t));const e=this.taskCaches.get(t);e&&(e.forEach(t=>{t.abort({code:"cancelled",message:"Document closed"})}),e.clear(),this.taskCaches.delete(t)),this.canAutoScroll.delete(t),this.window$.clearScope(t),this.scrollTo$.clearScope(t),this.refreshPages$.clearScope(t),this.logger.debug("ThumbnailPlugin","DocumentClosed",`Cleaned up thumbnail state for document: ${t}`)}onRotationChanged(t){this.calculateWindowState(t)}buildCapability(){return{scrollToThumb:t=>this.scrollToThumb(t),renderThumb:(t,e)=>this.renderThumb(t,e),updateWindow:(t,e)=>this.updateWindow(t,e),getWindow:()=>this.getWindow(),forDocument:t=>this.createThumbnailScope(t),onWindow:this.window$.onGlobal,onScrollTo:this.scrollTo$.onGlobal,onRefreshPages:this.refreshPages$.onGlobal}}createThumbnailScope(t){return{scrollToThumb:e=>this.scrollToThumb(e,t),renderThumb:(e,o)=>this.renderThumb(e,o,t),updateWindow:(e,o)=>this.updateWindow(e,o,t),getWindow:()=>this.getWindow(t),onWindow:this.window$.forScope(t),onScrollTo:this.scrollTo$.forScope(t),onRefreshPages:this.refreshPages$.forScope(t)}}getDocumentState(t){const e=t??this.getActiveDocumentId();return this.state.documents[e]??null}calculateWindowState(t){const e=this.coreState.core.documents[t];if(!(null==e?void 0:e.document))return;const o=this.cfg.width??120,i=this.cfg.labelHeight??16,n=this.cfg.gap??8,s=this.cfg.imagePadding??0,a=this.cfg.paddingY??0,r=Math.max(1,o-2*s);let c=a;const d=e.document.pages.map(t=>{const e=t.size.height/t.size.width,o=Math.round(r*e),a=s+o+s+i,l={pageIndex:t.index,width:r,height:o,wrapperHeight:a,top:c,labelHeight:i,padding:s};return c+=a+n,l}),h={start:-1,end:-1,items:[],totalHeight:c-n+a},u=this.getDocumentState(t);u&&(this.dispatch(l(t,{...u,thumbs:d,window:h})),u.viewportH>0?this.updateWindow(u.scrollY,u.viewportH,t):this.window$.emit(t,h))}updateWindow(t,e,o){const i=o??this.getActiveDocumentId(),n=this.getDocumentState(i);if(!n||!n.window||0===n.thumbs.length)return;const s=this.cfg.buffer??3;this.dispatch(u(i,t,e));let a=0,r=n.thumbs.length-1,c=0;for(;a<=r;){const e=a+r>>1,o=n.thumbs[e];o.top+o.wrapperHeight<t?a=e+1:(c=e,r=e-1)}let l=c;const d=t+e;for(;l+1<n.thumbs.length&&n.thumbs[l].top<d;)l++;l=Math.min(n.thumbs.length-1,l+s);const m=Math.max(0,c-s);if(m===n.window.start&&l===n.window.end)return;const p={start:m,end:l,items:n.thumbs.slice(m,l+1),totalHeight:n.window.totalHeight};this.dispatch(h(i,p)),this.window$.emit(i,p)}getWindow(t){const e=this.getDocumentState(t);return(null==e?void 0:e.window)??null}scrollToThumb(t,e){const o=e??this.getActiveDocumentId(),i=this.getDocumentState(o);if(!i||!i.window)return;const n=i.thumbs[t];if(!n)return;const s=this.cfg.scrollBehavior??"smooth",a=this.cfg.paddingY??0;if(i.viewportH<=0){const t=Math.max(a,n.top-n.wrapperHeight);return void this.scrollTo$.emit(o,{top:t,behavior:s})}const r=n.top,c=n.top+n.wrapperHeight,l=r<i.scrollY+8+a,d=c>i.scrollY+i.viewportH-8;l?this.scrollTo$.emit(o,{top:Math.max(0,r-a),behavior:s}):d&&this.scrollTo$.emit(o,{top:Math.max(0,c-i.viewportH+a),behavior:s})}renderThumb(t,o,i){const n=i??this.getActiveDocumentId(),s=this.taskCaches.get(n);if(!s)throw new Error(`Task cache not found for document: ${n}`);if(s.has(t))return s.get(t);const a=this.coreState.core.documents[n];if(!(null==a?void 0:a.document))throw new Error(`Document not found: ${n}`);const r=a.document.pages[t];if(!r)throw new Error(`Page ${t} not found in document: ${n}`);const c=this.cfg.width??120,l=this.cfg.imagePadding??0,d=Math.max(1,c-2*l)/r.size.width,h=this.renderCapability.forDocument(n).renderPageRect({pageIndex:t,rect:{origin:{x:0,y:0},size:r.size},options:{scaleFactor:d,dpr:o}});return s.set(t,h),h.wait(e.ignore,()=>s.delete(t)),h}async initialize(){this.logger.info("ThumbnailPlugin","Initialize","Thumbnail plugin initialized")}async destroy(){this.window$.clear(),this.refreshPages$.clear(),this.scrollTo$.clear(),this.taskCaches.forEach(t=>{t.forEach(t=>{t.abort({code:"cancelled",message:"Plugin destroyed"})}),t.clear()}),this.taskCaches.clear(),this.canAutoScroll.clear(),super.destroy()}};w.id="thumbnail";let T=w;const b={manifest:i,create:(t,e)=>new T(o,t,e),reducer:g,initialState:p};exports.CLEANUP_THUMBNAIL_STATE=s,exports.INIT_THUMBNAIL_STATE=n,exports.SET_ACTIVE_DOCUMENT=a,exports.SET_WINDOW_STATE=r,exports.THUMBNAIL_PLUGIN_ID=o,exports.ThumbnailPlugin=T,exports.ThumbnailPluginPackage=b,exports.UPDATE_VIEWPORT_METRICS=c,exports.cleanupThumbnailState=d,exports.initThumbnailState=l,exports.initialDocumentState=m,exports.initialState=p,exports.manifest=i,exports.setActiveDocument=function(t){return{type:a,payload:t}},exports.setWindowState=h,exports.thumbnailReducer=g,exports.updateViewportMetrics=u;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/lib/manifest.ts","../src/lib/thumbnail-plugin.ts","../src/lib/index.ts"],"sourcesContent":["import { PluginManifest } from '@embedpdf/core';\nimport { ThumbnailPluginConfig } from './types';\n\nexport const THUMBNAIL_PLUGIN_ID = 'thumbnail';\n\nexport const manifest: PluginManifest<ThumbnailPluginConfig> = {\n id: THUMBNAIL_PLUGIN_ID,\n name: 'Thumbnail Plugin',\n version: '1.0.0',\n provides: ['thumbnail'],\n requires: ['render'],\n optional: ['scroll'],\n defaultConfig: {\n enabled: true,\n width: 150,\n gap: 10,\n buffer: 3,\n labelHeight: 16,\n autoScroll: true,\n scrollBehavior: 'smooth',\n imagePadding: 0,\n paddingY: 0,\n },\n};\n","import {\n BasePlugin,\n CoreState,\n createBehaviorEmitter,\n createEmitter,\n PluginRegistry,\n REFRESH_PAGES,\n SET_DOCUMENT,\n StoreState,\n Unsubscribe,\n} from '@embedpdf/core';\nimport { ScrollToOptions, ThumbMeta, ThumbnailPluginConfig, WindowState } from './types';\nimport { ThumbnailCapability } from './types';\nimport { ignore, PdfErrorReason, Task } from '@embedpdf/models';\nimport { RenderCapability, RenderPlugin } from '@embedpdf/plugin-render';\nimport { ScrollCapability, ScrollPlugin } from '@embedpdf/plugin-scroll';\n\nexport class ThumbnailPlugin extends BasePlugin<ThumbnailPluginConfig, ThumbnailCapability> {\n static readonly id = 'thumbnail' as const;\n\n private renderCapability: RenderCapability;\n private scrollCapability: ScrollCapability | null = null;\n private thumbs: ThumbMeta[] = [];\n private window: WindowState | null = null;\n\n // track viewport metrics for scroll decisions\n private viewportH: number = 0;\n private scrollY: number = 0;\n\n private readonly emitWindow = createBehaviorEmitter<WindowState>();\n private readonly refreshPages$ = createEmitter<number[]>();\n private readonly taskCache = new Map<number, Task<Blob, PdfErrorReason>>();\n private canAutoScroll = true;\n // ask pane to scroll to a specific top\n private readonly scrollTo$ = createBehaviorEmitter<ScrollToOptions>();\n\n constructor(\n id: string,\n registry: PluginRegistry,\n public cfg: ThumbnailPluginConfig,\n ) {\n super(id, registry);\n\n this.renderCapability = this.registry.getPlugin<RenderPlugin>('render')!.provides();\n this.scrollCapability = this.registry.getPlugin<ScrollPlugin>('scroll')?.provides() ?? null;\n\n this.coreStore.onAction(SET_DOCUMENT, (_action, state) => {\n this.taskCache.clear();\n this.setWindowState(state);\n });\n\n this.coreStore.onAction(REFRESH_PAGES, (action) => {\n this.refreshPages$.emit(action.payload);\n for (const pageIdx of action.payload) {\n this.taskCache.delete(pageIdx);\n }\n });\n\n // auto-scroll thumbnails when the main scroller's current page changes\n if (this.scrollCapability && this.cfg.autoScroll !== false) {\n this.scrollCapability.onPageChangeState(({ isChanging, targetPage }) => {\n this.canAutoScroll = !isChanging;\n if (!isChanging) {\n this.scrollToThumb(targetPage - 1);\n }\n });\n this.scrollCapability.onPageChange(({ pageNumber }) => {\n if (this.canAutoScroll) {\n this.scrollToThumb(pageNumber - 1);\n }\n });\n }\n }\n\n /* ------------ init ------------------------------------------------ */\n async initialize(): Promise<void> {}\n\n public onRefreshPages(fn: (pages: number[]) => void): Unsubscribe {\n return this.refreshPages$.on(fn);\n }\n\n public onWindow(cb: (w: WindowState) => void): Unsubscribe {\n return this.emitWindow.on(cb);\n }\n\n public onScrollTo(cb: (o: ScrollToOptions) => void): Unsubscribe {\n return this.scrollTo$.on(cb);\n }\n\n private setWindowState(state: StoreState<CoreState>) {\n const core = state.core;\n if (!core.document) return;\n\n const OUTER_W = this.cfg.width ?? 120;\n const L = this.cfg.labelHeight ?? 16;\n const GAP = this.cfg.gap ?? 8;\n const P = this.cfg.imagePadding ?? 0;\n const PADDING_Y = this.cfg.paddingY ?? 0;\n\n // Inner bitmap width cannot go below 1px\n const INNER_W = Math.max(1, OUTER_W - 2 * P);\n\n let offset = PADDING_Y; // Start with top padding\n this.thumbs = core.document.pages.map((p) => {\n const ratio = p.size.height / p.size.width;\n const imgH = Math.round(INNER_W * ratio);\n const wrapH = P + imgH + P + L; // padding + image + padding + label\n\n const meta: ThumbMeta = {\n pageIndex: p.index,\n width: INNER_W, // bitmap width (for <img> size)\n height: imgH, // bitmap height (for <img> size)\n wrapperHeight: wrapH, // full row height used by virtualizer\n top: offset, // top of the row\n labelHeight: L,\n padding: P, // NEW\n };\n offset += wrapH + GAP;\n return meta;\n });\n\n this.window = {\n start: -1,\n end: -1,\n items: [],\n totalHeight: offset - GAP + PADDING_Y, // Add bottom padding to total height\n };\n\n if (this.viewportH > 0) {\n this.updateWindow(this.scrollY, this.viewportH);\n } else {\n this.emitWindow.emit(this.window);\n }\n }\n\n /* ------------ capability ----------------------------------------- */\n protected buildCapability(): ThumbnailCapability {\n return {\n renderThumb: (idx, dpr) => this.renderThumb(idx, dpr),\n scrollToThumb: (pageIdx) => this.scrollToThumb(pageIdx),\n };\n }\n\n /* ------------ windowing & viewport state ------------------------- */\n public updateWindow(scrollY: number, viewportH: number) {\n const BUF = this.cfg.buffer ?? 3;\n\n // remember latest viewport metrics for scroll decisions\n this.scrollY = scrollY;\n this.viewportH = viewportH;\n\n // Early return if window state hasn't been initialized yet\n if (!this.window || this.thumbs.length === 0) return;\n\n /* find first visible */\n let low = 0,\n high = this.thumbs.length - 1,\n first = 0;\n while (low <= high) {\n const mid = (low + high) >> 1;\n const m = this.thumbs[mid];\n if (m.top + m.wrapperHeight < scrollY) low = mid + 1;\n else {\n first = mid;\n high = mid - 1;\n }\n }\n\n /* find last visible + buffer */\n let last = first;\n const limit = scrollY + viewportH;\n while (last + 1 < this.thumbs.length && this.thumbs[last].top < limit) last++;\n last = Math.min(this.thumbs.length - 1, last + BUF);\n\n const start = Math.max(0, first - BUF);\n if (start === this.window.start && last === this.window.end) return;\n\n this.window = {\n start,\n end: last,\n items: this.thumbs.slice(start, last + 1),\n totalHeight: this.window.totalHeight,\n };\n this.emitWindow.emit(this.window);\n }\n\n /* ------------ scroll helper now in plugin ------------------------ */\n private scrollToThumb(pageIdx: number) {\n if (!this.window) return;\n const item = this.thumbs[pageIdx];\n if (!item) return;\n\n const behavior = this.cfg.scrollBehavior ?? 'smooth';\n const PADDING_Y = this.cfg.paddingY ?? 0; // Include padding in scroll calculations\n\n if (this.viewportH <= 0) {\n // Center the thumbnail in the viewport\n const top = Math.max(PADDING_Y, item.top - item.wrapperHeight);\n this.scrollTo$.emit({ top, behavior });\n return;\n }\n\n const margin = 8;\n const top = item.top;\n const bottom = item.top + item.wrapperHeight;\n\n const needsUp = top < this.scrollY + margin + PADDING_Y;\n const needsDown = bottom > this.scrollY + this.viewportH - margin;\n\n if (needsUp) {\n this.scrollTo$.emit({ top: Math.max(0, top - PADDING_Y), behavior });\n } else if (needsDown) {\n this.scrollTo$.emit({ top: Math.max(0, bottom - this.viewportH + PADDING_Y), behavior });\n }\n }\n\n /* ------------ thumbnail raster ----------------------------------- */\n private renderThumb(idx: number, dpr: number) {\n if (this.taskCache.has(idx)) return this.taskCache.get(idx)!;\n\n const core = this.coreState.core;\n const page = core.document!.pages[idx];\n\n const OUTER_W = this.cfg.width ?? 120;\n const P = this.cfg.imagePadding ?? 0;\n const INNER_W = Math.max(1, OUTER_W - 2 * P);\n\n const scale = INNER_W / page.size.width; // scale to inner width (excludes padding)\n\n const task = this.renderCapability.renderPageRect({\n pageIndex: idx,\n rect: { origin: { x: 0, y: 0 }, size: page.size },\n options: {\n scaleFactor: scale,\n dpr,\n },\n });\n\n this.taskCache.set(idx, task);\n task.wait(ignore, () => this.taskCache.delete(idx));\n return task;\n }\n}\n","import { PluginPackage } from '@embedpdf/core';\nimport { manifest, THUMBNAIL_PLUGIN_ID } from './manifest';\nimport { ThumbnailPluginConfig } from './types';\nimport { ThumbnailPlugin } from './thumbnail-plugin';\n\nexport const ThumbnailPluginPackage: PluginPackage<ThumbnailPlugin, ThumbnailPluginConfig> = {\n manifest,\n create: (registry, config) => new ThumbnailPlugin(THUMBNAIL_PLUGIN_ID, registry, config),\n reducer: () => {},\n initialState: {},\n};\n\nexport * from './thumbnail-plugin';\nexport * from './types';\nexport * from './manifest';\n"],"names":["THUMBNAIL_PLUGIN_ID","manifest","id","name","version","provides","requires","optional","defaultConfig","enabled","width","gap","buffer","labelHeight","autoScroll","scrollBehavior","imagePadding","paddingY","_ThumbnailPlugin","BasePlugin","constructor","registry","cfg","super","this","scrollCapability","thumbs","window","viewportH","scrollY","emitWindow","createBehaviorEmitter","refreshPages$","createEmitter","taskCache","Map","canAutoScroll","scrollTo$","renderCapability","getPlugin","_a","coreStore","onAction","SET_DOCUMENT","_action","state","clear","setWindowState","REFRESH_PAGES","action","emit","payload","pageIdx","delete","onPageChangeState","isChanging","targetPage","scrollToThumb","onPageChange","pageNumber","initialize","onRefreshPages","fn","on","onWindow","cb","onScrollTo","core","document","OUTER_W","L","GAP","P","PADDING_Y","INNER_W","Math","max","offset","pages","map","p","ratio","size","height","imgH","round","wrapH","meta","pageIndex","index","wrapperHeight","top","padding","start","end","items","totalHeight","updateWindow","buildCapability","renderThumb","idx","dpr","BUF","length","low","high","first","mid","m","last","limit","min","slice","item","behavior","bottom","needsUp","needsDown","has","get","page","coreState","scale","task","renderPageRect","rect","origin","x","y","options","scaleFactor","set","wait","ignore","ThumbnailPlugin","ThumbnailPluginPackage","create","config","reducer","initialState"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/lib/manifest.ts","../src/lib/actions.ts","../src/lib/reducer.ts","../src/lib/thumbnail-plugin.ts","../src/lib/index.ts"],"sourcesContent":["import { PluginManifest } from '@embedpdf/core';\nimport { ThumbnailPluginConfig } from './types';\n\nexport const THUMBNAIL_PLUGIN_ID = 'thumbnail';\n\nexport const manifest: PluginManifest<ThumbnailPluginConfig> = {\n id: THUMBNAIL_PLUGIN_ID,\n name: 'Thumbnail Plugin',\n version: '1.0.0',\n provides: ['thumbnail'],\n requires: ['render'],\n optional: ['scroll'],\n defaultConfig: {\n enabled: true,\n width: 150,\n gap: 10,\n buffer: 3,\n labelHeight: 16,\n autoScroll: true,\n scrollBehavior: 'smooth',\n imagePadding: 0,\n paddingY: 0,\n },\n};\n","import { Action } from '@embedpdf/core';\nimport { ThumbnailDocumentState, WindowState } from './types';\n\n// Document lifecycle\nexport const INIT_THUMBNAIL_STATE = 'THUMBNAIL/INIT_STATE';\nexport const CLEANUP_THUMBNAIL_STATE = 'THUMBNAIL/CLEANUP_STATE';\nexport const SET_ACTIVE_DOCUMENT = 'THUMBNAIL/SET_ACTIVE_DOCUMENT';\n\n// Per-document operations\nexport const SET_WINDOW_STATE = 'THUMBNAIL/SET_WINDOW_STATE';\nexport const UPDATE_VIEWPORT_METRICS = 'THUMBNAIL/UPDATE_VIEWPORT_METRICS';\n\n// Document lifecycle actions\nexport interface InitThumbnailStateAction extends Action {\n type: typeof INIT_THUMBNAIL_STATE;\n payload: {\n documentId: string;\n state: ThumbnailDocumentState;\n };\n}\n\nexport interface CleanupThumbnailStateAction extends Action {\n type: typeof CLEANUP_THUMBNAIL_STATE;\n payload: string; // documentId\n}\n\nexport interface SetActiveDocumentAction extends Action {\n type: typeof SET_ACTIVE_DOCUMENT;\n payload: string | null; // documentId\n}\n\nexport interface SetWindowStateAction extends Action {\n type: typeof SET_WINDOW_STATE;\n payload: {\n documentId: string;\n window: WindowState | null;\n };\n}\n\nexport interface UpdateViewportMetricsAction extends Action {\n type: typeof UPDATE_VIEWPORT_METRICS;\n payload: {\n documentId: string;\n scrollY: number;\n viewportH: number;\n };\n}\n\nexport type ThumbnailAction =\n | InitThumbnailStateAction\n | CleanupThumbnailStateAction\n | SetActiveDocumentAction\n | SetWindowStateAction\n | UpdateViewportMetricsAction;\n\n// Action Creators\nexport function initThumbnailState(\n documentId: string,\n state: ThumbnailDocumentState,\n): InitThumbnailStateAction {\n return { type: INIT_THUMBNAIL_STATE, payload: { documentId, state } };\n}\n\nexport function cleanupThumbnailState(documentId: string): CleanupThumbnailStateAction {\n return { type: CLEANUP_THUMBNAIL_STATE, payload: documentId };\n}\n\nexport function setActiveDocument(documentId: string | null): SetActiveDocumentAction {\n return { type: SET_ACTIVE_DOCUMENT, payload: documentId };\n}\n\nexport function setWindowState(\n documentId: string,\n window: WindowState | null,\n): SetWindowStateAction {\n return { type: SET_WINDOW_STATE, payload: { documentId, window } };\n}\n\nexport function updateViewportMetrics(\n documentId: string,\n scrollY: number,\n viewportH: number,\n): UpdateViewportMetricsAction {\n return { type: UPDATE_VIEWPORT_METRICS, payload: { documentId, scrollY, viewportH } };\n}\n","import { Reducer } from '@embedpdf/core';\nimport {\n ThumbnailAction,\n INIT_THUMBNAIL_STATE,\n CLEANUP_THUMBNAIL_STATE,\n SET_ACTIVE_DOCUMENT,\n SET_WINDOW_STATE,\n UPDATE_VIEWPORT_METRICS,\n} from './actions';\nimport { ThumbnailState, ThumbnailDocumentState } from './types';\n\nexport const initialDocumentState: ThumbnailDocumentState = {\n thumbs: [],\n window: null,\n viewportH: 0,\n scrollY: 0,\n};\n\nexport const initialState: ThumbnailState = {\n documents: {},\n activeDocumentId: null,\n};\n\nexport const thumbnailReducer: Reducer<ThumbnailState, ThumbnailAction> = (\n state = initialState,\n action,\n) => {\n switch (action.type) {\n case INIT_THUMBNAIL_STATE: {\n const { documentId, state: docState } = action.payload;\n return {\n ...state,\n documents: {\n ...state.documents,\n [documentId]: docState,\n },\n // Set as active if no active document\n activeDocumentId: state.activeDocumentId ?? documentId,\n };\n }\n\n case CLEANUP_THUMBNAIL_STATE: {\n const documentId = action.payload;\n const { [documentId]: removed, ...remainingDocs } = state.documents;\n return {\n ...state,\n documents: remainingDocs,\n activeDocumentId: state.activeDocumentId === documentId ? null : state.activeDocumentId,\n };\n }\n\n case SET_ACTIVE_DOCUMENT: {\n return {\n ...state,\n activeDocumentId: action.payload,\n };\n }\n\n case SET_WINDOW_STATE: {\n const { documentId, window } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n\n return {\n ...state,\n documents: {\n ...state.documents,\n [documentId]: {\n ...docState,\n window,\n },\n },\n };\n }\n\n case UPDATE_VIEWPORT_METRICS: {\n const { documentId, scrollY, viewportH } = action.payload;\n const docState = state.documents[documentId];\n if (!docState) return state;\n\n return {\n ...state,\n documents: {\n ...state.documents,\n [documentId]: {\n ...docState,\n scrollY,\n viewportH,\n },\n },\n };\n }\n\n default:\n return state;\n }\n};\n","import {\n BasePlugin,\n createBehaviorEmitter,\n createEmitter,\n createScopedEmitter,\n Listener,\n PluginRegistry,\n REFRESH_PAGES,\n} from '@embedpdf/core';\nimport {\n ScrollToOptions,\n ThumbMeta,\n ThumbnailPluginConfig,\n WindowState,\n ThumbnailCapability,\n ThumbnailScope,\n WindowChangeEvent,\n ScrollToEvent,\n RefreshPagesEvent,\n ThumbnailState,\n ThumbnailDocumentState,\n} from './types';\nimport { ignore, PdfErrorReason, Task } from '@embedpdf/models';\nimport { RenderCapability, RenderPlugin } from '@embedpdf/plugin-render';\nimport { ScrollCapability, ScrollPlugin } from '@embedpdf/plugin-scroll';\nimport {\n initThumbnailState,\n cleanupThumbnailState,\n setWindowState,\n updateViewportMetrics,\n ThumbnailAction,\n} from './actions';\nimport { initialDocumentState } from './reducer';\n\nexport class ThumbnailPlugin extends BasePlugin<\n ThumbnailPluginConfig,\n ThumbnailCapability,\n ThumbnailState,\n ThumbnailAction\n> {\n static readonly id = 'thumbnail' as const;\n\n private renderCapability: RenderCapability;\n private scrollCapability: ScrollCapability | null = null;\n\n // Per-document task caches\n private readonly taskCaches = new Map<string, Map<number, Task<Blob, PdfErrorReason>>>();\n\n // Per-document auto-scroll tracking\n private readonly canAutoScroll = new Map<string, boolean>();\n\n private readonly window$ = createScopedEmitter<WindowState, WindowChangeEvent, string>(\n (documentId, window) => ({ documentId, window }),\n );\n private readonly scrollTo$ = createScopedEmitter<ScrollToOptions, ScrollToEvent, string>(\n (documentId, options) => ({ documentId, options }),\n );\n private readonly refreshPages$ = createScopedEmitter<number[], RefreshPagesEvent, string>(\n (documentId, pages) => ({ documentId, pages }),\n { cache: false },\n );\n\n constructor(\n id: string,\n registry: PluginRegistry,\n public cfg: ThumbnailPluginConfig,\n ) {\n super(id, registry);\n\n this.renderCapability = this.registry.getPlugin<RenderPlugin>('render')!.provides();\n this.scrollCapability = this.registry.getPlugin<ScrollPlugin>('scroll')?.provides() ?? null;\n\n this.coreStore.onAction(REFRESH_PAGES, (action) => {\n const documentId = action.payload.documentId ?? this.getActiveDocumentId();\n const pages = action.payload.pageIndexes;\n\n this.refreshPages$.emit(documentId, pages);\n\n const taskCache = this.taskCaches.get(documentId);\n if (taskCache) {\n for (const pageIndex of pages) {\n taskCache.delete(pageIndex);\n }\n }\n });\n\n // Auto-scroll thumbnails when the main scroller's current page changes\n if (this.scrollCapability && this.cfg.autoScroll !== false) {\n this.scrollCapability.onPageChangeState(({ documentId, state }) => {\n this.canAutoScroll.set(documentId, !state.isChanging);\n if (!state.isChanging) {\n this.scrollToThumb(state.targetPage - 1, documentId);\n }\n });\n this.scrollCapability.onPageChange(({ documentId, pageNumber }) => {\n if (this.canAutoScroll.get(documentId) !== false) {\n this.scrollToThumb(pageNumber - 1, documentId);\n }\n });\n }\n }\n\n // ─────────────────────────────────────────────────────────\n // Document Lifecycle Hooks (from BasePlugin)\n // ─────────────────────────────────────────────────────────\n\n protected override onDocumentLoadingStarted(documentId: string): void {\n // Initialize state for this document\n this.dispatch(\n initThumbnailState(documentId, {\n ...initialDocumentState,\n }),\n );\n\n // Initialize task cache\n this.taskCaches.set(documentId, new Map());\n this.canAutoScroll.set(documentId, true);\n\n this.logger.debug(\n 'ThumbnailPlugin',\n 'DocumentOpened',\n `Initialized thumbnail state for document: ${documentId}`,\n );\n }\n\n protected override onDocumentLoaded(documentId: string): void {\n // Calculate initial window state after document is fully loaded\n this.calculateWindowState(documentId);\n }\n\n protected override onDocumentClosed(documentId: string): void {\n // Cleanup state\n this.dispatch(cleanupThumbnailState(documentId));\n\n // Cleanup task cache\n const taskCache = this.taskCaches.get(documentId);\n if (taskCache) {\n taskCache.forEach((task) => {\n task.abort({\n code: 'cancelled' as any,\n message: 'Document closed',\n });\n });\n taskCache.clear();\n this.taskCaches.delete(documentId);\n }\n\n this.canAutoScroll.delete(documentId);\n this.window$.clearScope(documentId);\n this.scrollTo$.clearScope(documentId);\n this.refreshPages$.clearScope(documentId);\n this.logger.debug(\n 'ThumbnailPlugin',\n 'DocumentClosed',\n `Cleaned up thumbnail state for document: ${documentId}`,\n );\n }\n\n protected override onRotationChanged(documentId: string): void {\n // Recalculate window state when rotation changes\n this.calculateWindowState(documentId);\n }\n\n // ─────────────────────────────────────────────────────────\n // Capability\n // ─────────────────────────────────────────────────────────\n\n protected buildCapability(): ThumbnailCapability {\n return {\n // Active document operations\n scrollToThumb: (pageIdx) => this.scrollToThumb(pageIdx),\n renderThumb: (idx, dpr) => this.renderThumb(idx, dpr),\n updateWindow: (scrollY, viewportH) => this.updateWindow(scrollY, viewportH),\n getWindow: () => this.getWindow(),\n\n // Document-scoped operations\n forDocument: (documentId: string) => this.createThumbnailScope(documentId),\n\n // Events\n onWindow: this.window$.onGlobal,\n onScrollTo: this.scrollTo$.onGlobal,\n onRefreshPages: this.refreshPages$.onGlobal,\n };\n }\n\n // ─────────────────────────────────────────────────────────\n // Document Scoping\n // ─────────────────────────────────────────────────────────\n\n private createThumbnailScope(documentId: string): ThumbnailScope {\n return {\n scrollToThumb: (pageIdx) => this.scrollToThumb(pageIdx, documentId),\n renderThumb: (idx, dpr) => this.renderThumb(idx, dpr, documentId),\n updateWindow: (scrollY, viewportH) => this.updateWindow(scrollY, viewportH, documentId),\n getWindow: () => this.getWindow(documentId),\n onWindow: this.window$.forScope(documentId),\n onScrollTo: this.scrollTo$.forScope(documentId),\n onRefreshPages: this.refreshPages$.forScope(documentId),\n };\n }\n\n // ─────────────────────────────────────────────────────────\n // State Helpers\n // ─────────────────────────────────────────────────────────\n\n private getDocumentState(documentId?: string): ThumbnailDocumentState | null {\n const id = documentId ?? this.getActiveDocumentId();\n return this.state.documents[id] ?? null;\n }\n\n // ─────────────────────────────────────────────────────────\n // Core Operations\n // ─────────────────────────────────────────────────────────\n\n private calculateWindowState(documentId: string) {\n const coreDoc = this.coreState.core.documents[documentId];\n if (!coreDoc?.document) return;\n\n const OUTER_W = this.cfg.width ?? 120;\n const L = this.cfg.labelHeight ?? 16;\n const GAP = this.cfg.gap ?? 8;\n const P = this.cfg.imagePadding ?? 0;\n const PADDING_Y = this.cfg.paddingY ?? 0;\n\n // Inner bitmap width cannot go below 1px\n const INNER_W = Math.max(1, OUTER_W - 2 * P);\n\n let offset = PADDING_Y; // Start with top padding\n const thumbs: ThumbMeta[] = coreDoc.document.pages.map((p) => {\n const ratio = p.size.height / p.size.width;\n const imgH = Math.round(INNER_W * ratio);\n const wrapH = P + imgH + P + L; // padding + image + padding + label\n\n const meta: ThumbMeta = {\n pageIndex: p.index,\n width: INNER_W, // bitmap width (for <img> size)\n height: imgH, // bitmap height (for <img> size)\n wrapperHeight: wrapH, // full row height used by virtualizer\n top: offset, // top of the row\n labelHeight: L,\n padding: P,\n };\n offset += wrapH + GAP;\n return meta;\n });\n\n const window: WindowState = {\n start: -1,\n end: -1,\n items: [],\n totalHeight: offset - GAP + PADDING_Y, // Add bottom padding to total height\n };\n\n const docState = this.getDocumentState(documentId);\n if (!docState) return;\n\n // Update state with new thumbs and window\n this.dispatch(\n initThumbnailState(documentId, {\n ...docState,\n thumbs,\n window,\n }),\n );\n\n // Update window based on current viewport metrics\n if (docState.viewportH > 0) {\n this.updateWindow(docState.scrollY, docState.viewportH, documentId);\n } else {\n this.window$.emit(documentId, window);\n }\n }\n\n public updateWindow(scrollY: number, viewportH: number, documentId?: string) {\n const id = documentId ?? this.getActiveDocumentId();\n const docState = this.getDocumentState(id);\n if (!docState || !docState.window || docState.thumbs.length === 0) return;\n\n const BUF = this.cfg.buffer ?? 3;\n\n // Update viewport metrics\n this.dispatch(updateViewportMetrics(id, scrollY, viewportH));\n\n /* find first visible */\n let low = 0,\n high = docState.thumbs.length - 1,\n first = 0;\n while (low <= high) {\n const mid = (low + high) >> 1;\n const m = docState.thumbs[mid];\n if (m.top + m.wrapperHeight < scrollY) low = mid + 1;\n else {\n first = mid;\n high = mid - 1;\n }\n }\n\n /* find last visible + buffer */\n let last = first;\n const limit = scrollY + viewportH;\n while (last + 1 < docState.thumbs.length && docState.thumbs[last].top < limit) last++;\n last = Math.min(docState.thumbs.length - 1, last + BUF);\n\n const start = Math.max(0, first - BUF);\n if (start === docState.window.start && last === docState.window.end) return;\n\n const newWindow: WindowState = {\n start,\n end: last,\n items: docState.thumbs.slice(start, last + 1),\n totalHeight: docState.window.totalHeight,\n };\n\n this.dispatch(setWindowState(id, newWindow));\n this.window$.emit(id, newWindow);\n }\n\n private getWindow(documentId?: string): WindowState | null {\n const docState = this.getDocumentState(documentId);\n return docState?.window ?? null;\n }\n\n private scrollToThumb(pageIdx: number, documentId?: string) {\n const id = documentId ?? this.getActiveDocumentId();\n const docState = this.getDocumentState(id);\n if (!docState || !docState.window) return;\n\n const item = docState.thumbs[pageIdx];\n if (!item) return;\n\n const behavior = this.cfg.scrollBehavior ?? 'smooth';\n const PADDING_Y = this.cfg.paddingY ?? 0;\n\n if (docState.viewportH <= 0) {\n // Center the thumbnail in the viewport\n const top = Math.max(PADDING_Y, item.top - item.wrapperHeight);\n this.scrollTo$.emit(id, { top, behavior });\n return;\n }\n\n const margin = 8;\n const top = item.top;\n const bottom = item.top + item.wrapperHeight;\n\n const needsUp = top < docState.scrollY + margin + PADDING_Y;\n const needsDown = bottom > docState.scrollY + docState.viewportH - margin;\n\n if (needsUp) {\n this.scrollTo$.emit(id, {\n top: Math.max(0, top - PADDING_Y),\n behavior,\n });\n } else if (needsDown) {\n this.scrollTo$.emit(id, {\n top: Math.max(0, bottom - docState.viewportH + PADDING_Y),\n behavior,\n });\n }\n }\n\n private renderThumb(idx: number, dpr: number, documentId?: string) {\n const id = documentId ?? this.getActiveDocumentId();\n const taskCache = this.taskCaches.get(id);\n if (!taskCache) {\n throw new Error(`Task cache not found for document: ${id}`);\n }\n\n if (taskCache.has(idx)) return taskCache.get(idx)!;\n\n const coreDoc = this.coreState.core.documents[id];\n if (!coreDoc?.document) {\n throw new Error(`Document not found: ${id}`);\n }\n\n const page = coreDoc.document.pages[idx];\n if (!page) {\n throw new Error(`Page ${idx} not found in document: ${id}`);\n }\n\n const OUTER_W = this.cfg.width ?? 120;\n const P = this.cfg.imagePadding ?? 0;\n const INNER_W = Math.max(1, OUTER_W - 2 * P);\n\n const scale = INNER_W / page.size.width;\n\n const task = this.renderCapability.forDocument(id).renderPageRect({\n pageIndex: idx,\n rect: { origin: { x: 0, y: 0 }, size: page.size },\n options: {\n scaleFactor: scale,\n dpr,\n },\n });\n\n taskCache.set(idx, task);\n task.wait(ignore, () => taskCache.delete(idx));\n return task;\n }\n\n // ─────────────────────────────────────────────────────────\n // Lifecycle\n // ─────────────────────────────────────────────────────────\n\n async initialize(): Promise<void> {\n this.logger.info('ThumbnailPlugin', 'Initialize', 'Thumbnail plugin initialized');\n }\n\n async destroy(): Promise<void> {\n this.window$.clear();\n this.refreshPages$.clear();\n this.scrollTo$.clear();\n\n // Cleanup all task caches\n this.taskCaches.forEach((cache) => {\n cache.forEach((task) => {\n task.abort({\n code: 'cancelled' as any,\n message: 'Plugin destroyed',\n });\n });\n cache.clear();\n });\n this.taskCaches.clear();\n this.canAutoScroll.clear();\n\n super.destroy();\n }\n}\n","import { PluginPackage } from '@embedpdf/core';\nimport { manifest, THUMBNAIL_PLUGIN_ID } from './manifest';\nimport { ThumbnailPluginConfig, ThumbnailState } from './types';\nimport { ThumbnailPlugin } from './thumbnail-plugin';\nimport { thumbnailReducer, initialState } from './reducer';\nimport { ThumbnailAction } from './actions';\n\nexport const ThumbnailPluginPackage: PluginPackage<\n ThumbnailPlugin,\n ThumbnailPluginConfig,\n ThumbnailState,\n ThumbnailAction\n> = {\n manifest,\n create: (registry, config) => new ThumbnailPlugin(THUMBNAIL_PLUGIN_ID, registry, config),\n reducer: thumbnailReducer,\n initialState,\n};\n\nexport * from './thumbnail-plugin';\nexport * from './types';\nexport * from './manifest';\nexport * from './actions';\nexport * from './reducer';\n"],"names":["THUMBNAIL_PLUGIN_ID","manifest","id","name","version","provides","requires","optional","defaultConfig","enabled","width","gap","buffer","labelHeight","autoScroll","scrollBehavior","imagePadding","paddingY","INIT_THUMBNAIL_STATE","CLEANUP_THUMBNAIL_STATE","SET_ACTIVE_DOCUMENT","SET_WINDOW_STATE","UPDATE_VIEWPORT_METRICS","initThumbnailState","documentId","state","type","payload","cleanupThumbnailState","setWindowState","window","updateViewportMetrics","scrollY","viewportH","initialDocumentState","thumbs","initialState","documents","activeDocumentId","thumbnailReducer","action","docState","removed","remainingDocs","_ThumbnailPlugin","BasePlugin","constructor","registry","cfg","super","this","scrollCapability","taskCaches","Map","canAutoScroll","window$","createScopedEmitter","scrollTo$","options","refreshPages$","pages","cache","renderCapability","getPlugin","_a","coreStore","onAction","REFRESH_PAGES","getActiveDocumentId","pageIndexes","emit","taskCache","get","pageIndex","delete","onPageChangeState","set","isChanging","scrollToThumb","targetPage","onPageChange","pageNumber","onDocumentLoadingStarted","dispatch","logger","debug","onDocumentLoaded","calculateWindowState","onDocumentClosed","forEach","task","abort","code","message","clear","clearScope","onRotationChanged","buildCapability","pageIdx","renderThumb","idx","dpr","updateWindow","getWindow","forDocument","createThumbnailScope","onWindow","onGlobal","onScrollTo","onRefreshPages","forScope","getDocumentState","coreDoc","coreState","core","document","OUTER_W","L","GAP","P","PADDING_Y","INNER_W","Math","max","offset","map","p","ratio","size","height","imgH","round","wrapH","meta","index","wrapperHeight","top","padding","start","end","items","totalHeight","length","BUF","low","high","first","mid","m","last","limit","min","newWindow","slice","item","behavior","bottom","needsUp","needsDown","Error","has","page","scale","renderPageRect","rect","origin","x","y","scaleFactor","wait","ignore","initialize","info","destroy","ThumbnailPlugin","ThumbnailPluginPackage","create","config","reducer"],"mappings":"gJAGaA,EAAsB,YAEtBC,EAAkD,CAC7DC,GAAIF,EACJG,KAAM,mBACNC,QAAS,QACTC,SAAU,CAAC,aACXC,SAAU,CAAC,UACXC,SAAU,CAAC,UACXC,cAAe,CACbC,SAAS,EACTC,MAAO,IACPC,IAAK,GACLC,OAAQ,EACRC,YAAa,GACbC,YAAY,EACZC,eAAgB,SAChBC,aAAc,EACdC,SAAU,ICjBDC,EAAuB,uBACvBC,EAA0B,0BAC1BC,EAAsB,gCAGtBC,EAAmB,6BACnBC,EAA0B,oCA8ChC,SAASC,EACdC,EACAC,GAEA,MAAO,CAAEC,KAAMR,EAAsBS,QAAS,CAAEH,aAAYC,SAC9D,CAEO,SAASG,EAAsBJ,GACpC,MAAO,CAAEE,KAAMP,EAAyBQ,QAASH,EACnD,CAMO,SAASK,EACdL,EACAM,GAEA,MAAO,CAAEJ,KAAML,EAAkBM,QAAS,CAAEH,aAAYM,UAC1D,CAEO,SAASC,EACdP,EACAQ,EACAC,GAEA,MAAO,CAAEP,KAAMJ,EAAyBK,QAAS,CAAEH,aAAYQ,UAASC,aAC1E,CCzEO,MAAMC,EAA+C,CAC1DC,OAAQ,GACRL,OAAQ,KACRG,UAAW,EACXD,QAAS,GAGEI,EAA+B,CAC1CC,UAAW,CAAA,EACXC,iBAAkB,MAGPC,EAA6D,CACxEd,EAAQW,EACRI,KAEA,OAAQA,EAAOd,MACb,KAAKR,EAAsB,CACzB,MAAMM,WAAEA,EAAYC,MAAOgB,GAAaD,EAAOb,QAC/C,MAAO,IACFF,EACHY,UAAW,IACNZ,EAAMY,UACTb,CAACA,GAAaiB,GAGhBH,iBAAkBb,EAAMa,kBAAoBd,EAEhD,CAEA,KAAKL,EAAyB,CAC5B,MAAMK,EAAagB,EAAOb,SAClBH,CAACA,GAAakB,KAAYC,GAAkBlB,EAAMY,UAC1D,MAAO,IACFZ,EACHY,UAAWM,EACXL,iBAAkBb,EAAMa,mBAAqBd,EAAa,KAAOC,EAAMa,iBAE3E,CAEA,KAAKlB,EACH,MAAO,IACFK,EACHa,iBAAkBE,EAAOb,SAI7B,KAAKN,EAAkB,CACrB,MAAMG,WAAEA,EAAAM,OAAYA,GAAWU,EAAOb,QAChCc,EAAWhB,EAAMY,UAAUb,GACjC,OAAKiB,EAEE,IACFhB,EACHY,UAAW,IACNZ,EAAMY,UACTb,CAACA,GAAa,IACTiB,EACHX,YARgBL,CAYxB,CAEA,KAAKH,EAAyB,CAC5B,MAAME,WAAEA,EAAAQ,QAAYA,EAAAC,UAASA,GAAcO,EAAOb,QAC5Cc,EAAWhB,EAAMY,UAAUb,GACjC,OAAKiB,EAEE,IACFhB,EACHY,UAAW,IACNZ,EAAMY,UACTb,CAACA,GAAa,IACTiB,EACHT,UACAC,eATgBR,CAaxB,CAEA,QACE,OAAOA,IC5DAmB,EAAN,cAA8BC,EAAAA,WA4BnC,WAAAC,CACE5C,EACA6C,EACOC,SAEPC,MAAM/C,EAAI6C,GAFHG,KAAAF,IAAAA,EAtBTE,KAAQC,iBAA4C,KAGpDD,KAAiBE,eAAiBC,IAGlCH,KAAiBI,kBAAoBD,IAErCH,KAAiBK,QAAUC,EAAAA,oBACzB,CAAChC,EAAYM,KAAA,CAAcN,aAAYM,YAEzCoB,KAAiBO,UAAYD,EAAAA,oBAC3B,CAAChC,EAAYkC,KAAA,CAAelC,aAAYkC,aAE1CR,KAAiBS,cAAgBH,EAAAA,oBAC/B,CAAChC,EAAYoC,KAAA,CAAapC,aAAYoC,UACtC,CAAEC,OAAO,IAUTX,KAAKY,iBAAmBZ,KAAKH,SAASgB,UAAwB,UAAW1D,WACzE6C,KAAKC,kBAAmB,OAAAa,EAAAd,KAAKH,SAASgB,UAAwB,oBAAW1D,aAAc,KAEvF6C,KAAKe,UAAUC,SAASC,EAAAA,cAAgB3B,IACtC,MAAMhB,EAAagB,EAAOb,QAAQH,YAAc0B,KAAKkB,sBAC/CR,EAAQpB,EAAOb,QAAQ0C,YAE7BnB,KAAKS,cAAcW,KAAK9C,EAAYoC,GAEpC,MAAMW,EAAYrB,KAAKE,WAAWoB,IAAIhD,GACtC,GAAI+C,EACF,IAAA,MAAWE,KAAab,EACtBW,EAAUG,OAAOD,KAMnBvB,KAAKC,mBAA4C,IAAxBD,KAAKF,IAAIlC,aACpCoC,KAAKC,iBAAiBwB,kBAAkB,EAAGnD,aAAYC,YACrDyB,KAAKI,cAAcsB,IAAIpD,GAAaC,EAAMoD,YACrCpD,EAAMoD,YACT3B,KAAK4B,cAAcrD,EAAMsD,WAAa,EAAGvD,KAG7C0B,KAAKC,iBAAiB6B,aAAa,EAAGxD,aAAYyD,kBACL,IAAvC/B,KAAKI,cAAckB,IAAIhD,IACzB0B,KAAK4B,cAAcG,EAAa,EAAGzD,KAI3C,CAMmB,wBAAA0D,CAAyB1D,GAE1C0B,KAAKiC,SACH5D,EAAmBC,EAAY,IAC1BU,KAKPgB,KAAKE,WAAWwB,IAAIpD,EAAY,IAAI6B,KACpCH,KAAKI,cAAcsB,IAAIpD,GAAY,GAEnC0B,KAAKkC,OAAOC,MACV,kBACA,iBACA,6CAA6C7D,IAEjD,CAEmB,gBAAA8D,CAAiB9D,GAElC0B,KAAKqC,qBAAqB/D,EAC5B,CAEmB,gBAAAgE,CAAiBhE,GAElC0B,KAAKiC,SAASvD,EAAsBJ,IAGpC,MAAM+C,EAAYrB,KAAKE,WAAWoB,IAAIhD,GAClC+C,IACFA,EAAUkB,QAASC,IACjBA,EAAKC,MAAM,CACTC,KAAM,YACNC,QAAS,sBAGbtB,EAAUuB,QACV5C,KAAKE,WAAWsB,OAAOlD,IAGzB0B,KAAKI,cAAcoB,OAAOlD,GAC1B0B,KAAKK,QAAQwC,WAAWvE,GACxB0B,KAAKO,UAAUsC,WAAWvE,GAC1B0B,KAAKS,cAAcoC,WAAWvE,GAC9B0B,KAAKkC,OAAOC,MACV,kBACA,iBACA,4CAA4C7D,IAEhD,CAEmB,iBAAAwE,CAAkBxE,GAEnC0B,KAAKqC,qBAAqB/D,EAC5B,CAMU,eAAAyE,GACR,MAAO,CAELnB,cAAgBoB,GAAYhD,KAAK4B,cAAcoB,GAC/CC,YAAa,CAACC,EAAKC,IAAQnD,KAAKiD,YAAYC,EAAKC,GACjDC,aAAc,CAACtE,EAASC,IAAciB,KAAKoD,aAAatE,EAASC,GACjEsE,UAAW,IAAMrD,KAAKqD,YAGtBC,YAAchF,GAAuB0B,KAAKuD,qBAAqBjF,GAG/DkF,SAAUxD,KAAKK,QAAQoD,SACvBC,WAAY1D,KAAKO,UAAUkD,SAC3BE,eAAgB3D,KAAKS,cAAcgD,SAEvC,CAMQ,oBAAAF,CAAqBjF,GAC3B,MAAO,CACLsD,cAAgBoB,GAAYhD,KAAK4B,cAAcoB,EAAS1E,GACxD2E,YAAa,CAACC,EAAKC,IAAQnD,KAAKiD,YAAYC,EAAKC,EAAK7E,GACtD8E,aAAc,CAACtE,EAASC,IAAciB,KAAKoD,aAAatE,EAASC,EAAWT,GAC5E+E,UAAW,IAAMrD,KAAKqD,UAAU/E,GAChCkF,SAAUxD,KAAKK,QAAQuD,SAAStF,GAChCoF,WAAY1D,KAAKO,UAAUqD,SAAStF,GACpCqF,eAAgB3D,KAAKS,cAAcmD,SAAStF,GAEhD,CAMQ,gBAAAuF,CAAiBvF,GACvB,MAAMtB,EAAKsB,GAAc0B,KAAKkB,sBAC9B,OAAOlB,KAAKzB,MAAMY,UAAUnC,IAAO,IACrC,CAMQ,oBAAAqF,CAAqB/D,GAC3B,MAAMwF,EAAU9D,KAAK+D,UAAUC,KAAK7E,UAAUb,GAC9C,WAAKwF,WAASG,UAAU,OAExB,MAAMC,EAAUlE,KAAKF,IAAItC,OAAS,IAC5B2G,EAAInE,KAAKF,IAAInC,aAAe,GAC5ByG,EAAMpE,KAAKF,IAAIrC,KAAO,EACtB4G,EAAIrE,KAAKF,IAAIhC,cAAgB,EAC7BwG,EAAYtE,KAAKF,IAAI/B,UAAY,EAGjCwG,EAAUC,KAAKC,IAAI,EAAGP,EAAU,EAAIG,GAE1C,IAAIK,EAASJ,EACb,MAAMrF,EAAsB6E,EAAQG,SAASvD,MAAMiE,IAAKC,IACtD,MAAMC,EAAQD,EAAEE,KAAKC,OAASH,EAAEE,KAAKtH,MAC/BwH,EAAOR,KAAKS,MAAMV,EAAUM,GAC5BK,EAAQb,EAAIW,EAAOX,EAAIF,EAEvBgB,EAAkB,CACtB5D,UAAWqD,EAAEQ,MACb5H,MAAO+G,EACPQ,OAAQC,EACRK,cAAeH,EACfI,IAAKZ,EACL/G,YAAawG,EACboB,QAASlB,GAGX,OADAK,GAAUQ,EAAQd,EACXe,IAGHvG,EAAsB,CAC1B4G,OAAO,EACPC,KAAK,EACLC,MAAO,GACPC,YAAajB,EAASN,EAAME,GAGxB/E,EAAWS,KAAK6D,iBAAiBvF,GAClCiB,IAGLS,KAAKiC,SACH5D,EAAmBC,EAAY,IAC1BiB,EACHN,SACAL,YAKAW,EAASR,UAAY,EACvBiB,KAAKoD,aAAa7D,EAAST,QAASS,EAASR,UAAWT,GAExD0B,KAAKK,QAAQe,KAAK9C,EAAYM,GAElC,CAEO,YAAAwE,CAAatE,EAAiBC,EAAmBT,GACtD,MAAMtB,EAAKsB,GAAc0B,KAAKkB,sBACxB3B,EAAWS,KAAK6D,iBAAiB7G,GACvC,IAAKuC,IAAaA,EAASX,QAAqC,IAA3BW,EAASN,OAAO2G,OAAc,OAEnE,MAAMC,EAAM7F,KAAKF,IAAIpC,QAAU,EAG/BsC,KAAKiC,SAASpD,EAAsB7B,EAAI8B,EAASC,IAGjD,IAAI+G,EAAM,EACRC,EAAOxG,EAASN,OAAO2G,OAAS,EAChCI,EAAQ,EACV,KAAOF,GAAOC,GAAM,CAClB,MAAME,EAAOH,EAAMC,GAAS,EACtBG,EAAI3G,EAASN,OAAOgH,GACtBC,EAAEZ,IAAMY,EAAEb,cAAgBvG,IAAemH,EAAM,GAEjDD,EAAQC,EACRF,EAAOE,EAAM,EAEjB,CAGA,IAAIE,EAAOH,EACX,MAAMI,EAAQtH,EAAUC,EACxB,KAAOoH,EAAO,EAAI5G,EAASN,OAAO2G,QAAUrG,EAASN,OAAOkH,GAAMb,IAAMc,GAAOD,IAC/EA,EAAO3B,KAAK6B,IAAI9G,EAASN,OAAO2G,OAAS,EAAGO,EAAON,GAEnD,MAAML,EAAQhB,KAAKC,IAAI,EAAGuB,EAAQH,GAClC,GAAIL,IAAUjG,EAASX,OAAO4G,OAASW,IAAS5G,EAASX,OAAO6G,IAAK,OAErE,MAAMa,EAAyB,CAC7Bd,QACAC,IAAKU,EACLT,MAAOnG,EAASN,OAAOsH,MAAMf,EAAOW,EAAO,GAC3CR,YAAapG,EAASX,OAAO+G,aAG/B3F,KAAKiC,SAAStD,EAAe3B,EAAIsJ,IACjCtG,KAAKK,QAAQe,KAAKpE,EAAIsJ,EACxB,CAEQ,SAAAjD,CAAU/E,GAChB,MAAMiB,EAAWS,KAAK6D,iBAAiBvF,GACvC,aAAOiB,WAAUX,SAAU,IAC7B,CAEQ,aAAAgD,CAAcoB,EAAiB1E,GACrC,MAAMtB,EAAKsB,GAAc0B,KAAKkB,sBACxB3B,EAAWS,KAAK6D,iBAAiB7G,GACvC,IAAKuC,IAAaA,EAASX,OAAQ,OAEnC,MAAM4H,EAAOjH,EAASN,OAAO+D,GAC7B,IAAKwD,EAAM,OAEX,MAAMC,EAAWzG,KAAKF,IAAIjC,gBAAkB,SACtCyG,EAAYtE,KAAKF,IAAI/B,UAAY,EAEvC,GAAIwB,EAASR,WAAa,EAAG,CAE3B,MAAMuG,EAAMd,KAAKC,IAAIH,EAAWkC,EAAKlB,IAAMkB,EAAKnB,eAEhD,YADArF,KAAKO,UAAUa,KAAKpE,EAAI,CAAEsI,IAAAA,EAAKmB,YAEjC,CAEA,MACMnB,EAAMkB,EAAKlB,IACXoB,EAASF,EAAKlB,IAAMkB,EAAKnB,cAEzBsB,EAAUrB,EAAM/F,EAAST,QAJhB,EAImCwF,EAC5CsC,EAAYF,EAASnH,EAAST,QAAUS,EAASR,UALxC,EAOX4H,EACF3G,KAAKO,UAAUa,KAAKpE,EAAI,CACtBsI,IAAKd,KAAKC,IAAI,EAAGa,EAAMhB,GACvBmC,aAEOG,GACT5G,KAAKO,UAAUa,KAAKpE,EAAI,CACtBsI,IAAKd,KAAKC,IAAI,EAAGiC,EAASnH,EAASR,UAAYuF,GAC/CmC,YAGN,CAEQ,WAAAxD,CAAYC,EAAaC,EAAa7E,GAC5C,MAAMtB,EAAKsB,GAAc0B,KAAKkB,sBACxBG,EAAYrB,KAAKE,WAAWoB,IAAItE,GACtC,IAAKqE,EACH,MAAM,IAAIwF,MAAM,sCAAsC7J,KAGxD,GAAIqE,EAAUyF,IAAI5D,GAAM,OAAO7B,EAAUC,IAAI4B,GAE7C,MAAMY,EAAU9D,KAAK+D,UAAUC,KAAK7E,UAAUnC,GAC9C,WAAK8G,WAASG,UACZ,MAAM,IAAI4C,MAAM,uBAAuB7J,KAGzC,MAAM+J,EAAOjD,EAAQG,SAASvD,MAAMwC,GACpC,IAAK6D,EACH,MAAM,IAAIF,MAAM,QAAQ3D,4BAA8BlG,KAGxD,MAAMkH,EAAUlE,KAAKF,IAAItC,OAAS,IAC5B6G,EAAIrE,KAAKF,IAAIhC,cAAgB,EAG7BkJ,EAFUxC,KAAKC,IAAI,EAAGP,EAAU,EAAIG,GAElB0C,EAAKjC,KAAKtH,MAE5BgF,EAAOxC,KAAKY,iBAAiB0C,YAAYtG,GAAIiK,eAAe,CAChE1F,UAAW2B,EACXgE,KAAM,CAAEC,OAAQ,CAAEC,EAAG,EAAGC,EAAG,GAAKvC,KAAMiC,EAAKjC,MAC3CtE,QAAS,CACP8G,YAAaN,EACb7D,SAMJ,OAFA9B,EAAUK,IAAIwB,EAAKV,GACnBA,EAAK+E,KAAKC,EAAAA,OAAQ,IAAMnG,EAAUG,OAAO0B,IAClCV,CACT,CAMA,gBAAMiF,GACJzH,KAAKkC,OAAOwF,KAAK,kBAAmB,aAAc,+BACpD,CAEA,aAAMC,GACJ3H,KAAKK,QAAQuC,QACb5C,KAAKS,cAAcmC,QACnB5C,KAAKO,UAAUqC,QAGf5C,KAAKE,WAAWqC,QAAS5B,IACvBA,EAAM4B,QAASC,IACbA,EAAKC,MAAM,CACTC,KAAM,YACNC,QAAS,uBAGbhC,EAAMiC,UAER5C,KAAKE,WAAW0C,QAChB5C,KAAKI,cAAcwC,QAEnB7C,MAAM4H,SACR,GAlYAjI,EAAgB1C,GAAK,YANhB,IAAM4K,EAANlI,EC3BA,MAAMmI,EAKT,CACF9K,WACA+K,OAAQ,CAACjI,EAAUkI,IAAW,IAAIH,EAAgB9K,EAAqB+C,EAAUkI,GACjFC,QAAS3I,EACTH,qaHmDK,SAA2BZ,GAChC,MAAO,CAAEE,KAAMN,EAAqBO,QAASH,EAC/C"}