@embedpdf/plugin-thumbnail 1.3.5 → 1.3.7

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.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}},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,r=this.cfg.imagePadding??0,h=Math.max(1,e-2*r);let a=0;this.thumbs=i.document.pages.map((t=>{const i=t.size.height/t.size.width,e=Math.round(h*i),n=r+e+r+s,l={pageIndex:t.index,width:h,height:e,wrapperHeight:n,top:a,labelHeight:s,padding:r};return a+=n+o,l})),this.window={start:-1,end:-1,items:[],totalHeight:a-o},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;this.scrollY=t,this.viewportH=i;let s=0,o=this.thumbs.length-1,r=0;for(;s<=o;){const i=s+o>>1,e=this.thumbs[i];e.top+e.wrapperHeight<t?s=i+1:(r=i,o=i-1)}let h=r;const a=t+i;for(;h+1<this.thumbs.length&&this.thumbs[h].top<a;)h++;h=Math.min(this.thumbs.length-1,h+e);const n=Math.max(0,r-e);this.window&&n===this.window.start&&h===this.window.end||(this.window={start:n,end:h,items:this.thumbs.slice(n,h+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";if(this.viewportH<=0){const t=Math.max(0,i.top-i.wrapperHeight);return void this.scrollTo$.emit({top:t,behavior:e})}const s=i.top,o=i.top+i.wrapperHeight,r=s<this.scrollY+8,h=o>this.scrollY+this.viewportH-8;r?this.scrollTo$.emit({top:s,behavior:e}):h&&this.scrollTo$.emit({top:Math.max(0,o-this.viewportH),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,r=this.cfg.imagePadding??0,h=Math.max(1,o-2*r)/s.size.width,a=this.renderCapability.renderPageRect({pageIndex:t,rect:{origin:{x:0,y:0},size:s.size},options:{scaleFactor:h,dpr:e}});return this.taskCache.set(t,a),a.wait(i.ignore,(()=>this.taskCache.delete(t))),a}};o.id="thumbnail";let r=o;const h={manifest:s,create:(t,i)=>new r(e,t,i),reducer:()=>{},initialState:{}};exports.THUMBNAIL_PLUGIN_ID=e,exports.ThumbnailPlugin=r,exports.ThumbnailPluginPackage=h,exports.manifest=s;
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;
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 },\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 private 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\n // Inner bitmap width cannot go below 1px\n const INNER_W = Math.max(1, OUTER_W - 2 * P);\n\n let offset = 0;\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,\n };\n this.emitWindow.emit(this.window);\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 /* 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 (this.window && 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\n if (this.viewportH <= 0) {\n // Center the thumbnail in the viewport\n const top = Math.max(0, 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;\n const needsDown = bottom > this.scrollY + this.viewportH - margin;\n\n if (needsUp) {\n this.scrollTo$.emit({ top, behavior });\n } else if (needsDown) {\n this.scrollTo$.emit({ top: Math.max(0, bottom - this.viewportH), 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","_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","INNER_W","Math","max","offset","pages","map","p","ratio","size","height","imgH","round","wrapH","meta","pageIndex","index","wrapperHeight","top","padding","start","end","items","totalHeight","buildCapability","renderThumb","idx","dpr","updateWindow","BUF","low","high","length","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":"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,ICHLC,EAAN,cAA8BC,EAAAA,WAmBnC,WAAAC,CACEjB,EACAkB,EACQC,SAERC,MAAMpB,EAAIkB,GAFFG,KAAAF,IAAAA,EAlBVE,KAAQC,iBAA4C,KACpDD,KAAQE,OAAsB,GAC9BF,KAAQG,OAA6B,KAGrCH,KAAQI,UAAoB,EAC5BJ,KAAQK,QAAkB,EAETL,KAAAM,WAAaC,0BACbP,KAAAQ,cAAgBC,kBAChBT,KAAAU,cAAgBC,IACjCX,KAAQY,eAAgB,EAEPZ,KAAAa,UAAYN,0BAS3BP,KAAKc,iBAAmBd,KAAKH,SAASkB,UAAwB,UAAWjC,WACzEkB,KAAKC,kBAAmB,OAAAe,EAAKhB,KAAAH,SAASkB,UAAwB,oBAAWjC,aAAc,KAEvFkB,KAAKiB,UAAUC,SAASC,EAAcA,cAAA,CAACC,EAASC,KAC9CrB,KAAKU,UAAUY,QACftB,KAAKuB,eAAeF,EAAK,IAG3BrB,KAAKiB,UAAUC,SAASM,EAAeA,eAACC,IACjCzB,KAAAQ,cAAckB,KAAKD,EAAOE,SACpB,IAAA,MAAAC,KAAWH,EAAOE,QACtB3B,KAAAU,UAAUmB,OAAOD,EAAO,IAK7B5B,KAAKC,mBAA4C,IAAxBD,KAAKF,IAAIP,aACpCS,KAAKC,iBAAiB6B,mBAAkB,EAAGC,aAAYC,iBACrDhC,KAAKY,eAAiBmB,EACjBA,GACE/B,KAAAiC,cAAcD,EAAa,EAAC,IAGrChC,KAAKC,iBAAiBiC,cAAa,EAAGC,iBAChCnC,KAAKY,eACFZ,KAAAiC,cAAcE,EAAa,EAAC,IAGvC,CAIF,gBAAMC,GAA4B,CAE3B,cAAAC,CAAeC,GACb,OAAAtC,KAAKQ,cAAc+B,GAAGD,EAAE,CAG1B,QAAAE,CAASC,GACP,OAAAzC,KAAKM,WAAWiC,GAAGE,EAAE,CAGvB,UAAAC,CAAWD,GACT,OAAAzC,KAAKa,UAAU0B,GAAGE,EAAE,CAGrB,cAAAlB,CAAeF,GACrB,MAAMsB,EAAOtB,EAAMsB,KACf,IAACA,EAAKC,SAAU,OAEd,MAAAC,EAAU7C,KAAKF,IAAIX,OAAS,IAC5B2D,EAAI9C,KAAKF,IAAIR,aAAe,GAC5ByD,EAAM/C,KAAKF,IAAIV,KAAO,EACtB4D,EAAIhD,KAAKF,IAAIL,cAAgB,EAG7BwD,EAAUC,KAAKC,IAAI,EAAGN,EAAU,EAAIG,GAE1C,IAAII,EAAS,EACbpD,KAAKE,OAASyC,EAAKC,SAASS,MAAMC,KAAKC,IACrC,MAAMC,EAAQD,EAAEE,KAAKC,OAASH,EAAEE,KAAKtE,MAC/BwE,EAAOT,KAAKU,MAAMX,EAAUO,GAC5BK,EAAQb,EAAIW,EAAOX,EAAIF,EAEvBgB,EAAkB,CACtBC,UAAWR,EAAES,MACb7E,MAAO8D,EACPS,OAAQC,EACRM,cAAeJ,EACfK,IAAKd,EACL9D,YAAawD,EACbqB,QAASnB,GAGJ,OADPI,GAAUS,EAAQd,EACXe,CAAA,IAGT9D,KAAKG,OAAS,CACZiE,OAAO,EACPC,KAAK,EACLC,MAAO,GACPC,YAAanB,EAASL,GAEnB/C,KAAAM,WAAWoB,KAAK1B,KAAKG,OAAM,CAIxB,eAAAqE,GACD,MAAA,CACLC,YAAa,CAACC,EAAKC,IAAQ3E,KAAKyE,YAAYC,EAAKC,GACjD1C,cAAgBL,GAAY5B,KAAKiC,cAAcL,GACjD,CAIK,YAAAgD,CAAavE,EAAiBD,GAC7B,MAAAyE,EAAM7E,KAAKF,IAAIT,QAAU,EAG/BW,KAAKK,QAAUA,EACfL,KAAKI,UAAYA,EAGjB,IAAI0E,EAAM,EACRC,EAAO/E,KAAKE,OAAO8E,OAAS,EAC5BC,EAAQ,EACV,KAAOH,GAAOC,GAAM,CACZ,MAAAG,EAAOJ,EAAMC,GAAS,EACtBI,EAAInF,KAAKE,OAAOgF,GAClBC,EAAEjB,IAAMiB,EAAElB,cAAgB5D,IAAe6E,EAAM,GAEzCD,EAAAC,EACRH,EAAOG,EAAM,EACf,CAIF,IAAIE,EAAOH,EACX,MAAMI,EAAQhF,EAAUD,EACjB,KAAAgF,EAAO,EAAIpF,KAAKE,OAAO8E,QAAUhF,KAAKE,OAAOkF,GAAMlB,IAAMmB,GAAOD,IACvEA,EAAOlC,KAAKoC,IAAItF,KAAKE,OAAO8E,OAAS,EAAGI,EAAOP,GAE/C,MAAMT,EAAQlB,KAAKC,IAAI,EAAG8B,EAAQJ,GAC9B7E,KAAKG,QAAUiE,IAAUpE,KAAKG,OAAOiE,OAASgB,IAASpF,KAAKG,OAAOkE,MAEvErE,KAAKG,OAAS,CACZiE,QACAC,IAAKe,EACLd,MAAOtE,KAAKE,OAAOqF,MAAMnB,EAAOgB,EAAO,GACvCb,YAAavE,KAAKG,OAAQoE,aAEvBvE,KAAAM,WAAWoB,KAAK1B,KAAKG,QAAM,CAI1B,aAAA8B,CAAcL,GAChB,IAAC5B,KAAKG,OAAQ,OACZ,MAAAqF,EAAOxF,KAAKE,OAAO0B,GACzB,IAAK4D,EAAM,OAEL,MAAAC,EAAWzF,KAAKF,IAAIN,gBAAkB,SAExC,GAAAQ,KAAKI,WAAa,EAAG,CAEvB,MAAM8D,EAAMhB,KAAKC,IAAI,EAAGqC,EAAKtB,IAAMsB,EAAKvB,eAExC,YADAjE,KAAKa,UAAUa,KAAK,CAAEwC,IAAAA,EAAKuB,YAC3B,CAGF,MACMvB,EAAMsB,EAAKtB,IACXwB,EAASF,EAAKtB,IAAMsB,EAAKvB,cAEzB0B,EAAUzB,EAAMlE,KAAKK,QAJZ,EAKTuF,EAAYF,EAAS1F,KAAKK,QAAUL,KAAKI,UALhC,EAOXuF,EACF3F,KAAKa,UAAUa,KAAK,CAAEwC,MAAKuB,aAClBG,GACT5F,KAAKa,UAAUa,KAAK,CAAEwC,IAAKhB,KAAKC,IAAI,EAAGuC,EAAS1F,KAAKI,WAAYqF,YACnE,CAIM,WAAAhB,CAAYC,EAAaC,GAC3B,GAAA3E,KAAKU,UAAUmF,IAAInB,GAAa,OAAA1E,KAAKU,UAAUoF,IAAIpB,GAEjD/B,MACAoD,EADO/F,KAAKgG,UAAUrD,KACVC,SAAUS,MAAMqB,GAE5B7B,EAAU7C,KAAKF,IAAIX,OAAS,IAC5B6D,EAAIhD,KAAKF,IAAIL,cAAgB,EAG7BwG,EAFU/C,KAAKC,IAAI,EAAGN,EAAU,EAAIG,GAElB+C,EAAKtC,KAAKtE,MAE5B+G,EAAOlG,KAAKc,iBAAiBqF,eAAe,CAChDpC,UAAWW,EACX0B,KAAM,CAAEC,OAAQ,CAAEC,EAAG,EAAGC,EAAG,GAAK9C,KAAMsC,EAAKtC,MAC3C+C,QAAS,CACPC,YAAaR,EACbtB,SAMG,OAFF3E,KAAAU,UAAUgG,IAAIhC,EAAKwB,GACnBA,EAAAS,KAAKC,UAAQ,IAAM5G,KAAKU,UAAUmB,OAAO6C,KACvCwB,CAAA,GApNTxG,EAAgBf,GAAK,YADhB,IAAMkI,EAANnH,ECZA,MAAMoH,EAAgF,CAC3FpI,WACAqI,OAAQ,CAAClH,EAAUmH,IAAW,IAAIH,EAAgBpI,EAAqBoB,EAAUmH,GACjFC,QAAS,OACTC,aAAc,CAAA"}
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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"}
package/dist/index.js CHANGED
@@ -16,7 +16,8 @@ const manifest = {
16
16
  labelHeight: 16,
17
17
  autoScroll: true,
18
18
  scrollBehavior: "smooth",
19
- imagePadding: 0
19
+ imagePadding: 0,
20
+ paddingY: 0
20
21
  }
21
22
  };
22
23
  const _ThumbnailPlugin = class _ThumbnailPlugin extends BasePlugin {
@@ -79,8 +80,9 @@ const _ThumbnailPlugin = class _ThumbnailPlugin extends BasePlugin {
79
80
  const L = this.cfg.labelHeight ?? 16;
80
81
  const GAP = this.cfg.gap ?? 8;
81
82
  const P = this.cfg.imagePadding ?? 0;
83
+ const PADDING_Y = this.cfg.paddingY ?? 0;
82
84
  const INNER_W = Math.max(1, OUTER_W - 2 * P);
83
- let offset = 0;
85
+ let offset = PADDING_Y;
84
86
  this.thumbs = core.document.pages.map((p) => {
85
87
  const ratio = p.size.height / p.size.width;
86
88
  const imgH = Math.round(INNER_W * ratio);
@@ -106,9 +108,14 @@ const _ThumbnailPlugin = class _ThumbnailPlugin extends BasePlugin {
106
108
  start: -1,
107
109
  end: -1,
108
110
  items: [],
109
- totalHeight: offset - GAP
111
+ totalHeight: offset - GAP + PADDING_Y
112
+ // Add bottom padding to total height
110
113
  };
111
- this.emitWindow.emit(this.window);
114
+ if (this.viewportH > 0) {
115
+ this.updateWindow(this.scrollY, this.viewportH);
116
+ } else {
117
+ this.emitWindow.emit(this.window);
118
+ }
112
119
  }
113
120
  /* ------------ capability ----------------------------------------- */
114
121
  buildCapability() {
@@ -122,6 +129,7 @@ const _ThumbnailPlugin = class _ThumbnailPlugin extends BasePlugin {
122
129
  const BUF = this.cfg.buffer ?? 3;
123
130
  this.scrollY = scrollY;
124
131
  this.viewportH = viewportH;
132
+ if (!this.window || this.thumbs.length === 0) return;
125
133
  let low = 0, high = this.thumbs.length - 1, first = 0;
126
134
  while (low <= high) {
127
135
  const mid = low + high >> 1;
@@ -137,7 +145,7 @@ const _ThumbnailPlugin = class _ThumbnailPlugin extends BasePlugin {
137
145
  while (last + 1 < this.thumbs.length && this.thumbs[last].top < limit) last++;
138
146
  last = Math.min(this.thumbs.length - 1, last + BUF);
139
147
  const start = Math.max(0, first - BUF);
140
- if (this.window && start === this.window.start && last === this.window.end) return;
148
+ if (start === this.window.start && last === this.window.end) return;
141
149
  this.window = {
142
150
  start,
143
151
  end: last,
@@ -152,20 +160,21 @@ const _ThumbnailPlugin = class _ThumbnailPlugin extends BasePlugin {
152
160
  const item = this.thumbs[pageIdx];
153
161
  if (!item) return;
154
162
  const behavior = this.cfg.scrollBehavior ?? "smooth";
163
+ const PADDING_Y = this.cfg.paddingY ?? 0;
155
164
  if (this.viewportH <= 0) {
156
- const top2 = Math.max(0, item.top - item.wrapperHeight);
165
+ const top2 = Math.max(PADDING_Y, item.top - item.wrapperHeight);
157
166
  this.scrollTo$.emit({ top: top2, behavior });
158
167
  return;
159
168
  }
160
169
  const margin = 8;
161
170
  const top = item.top;
162
171
  const bottom = item.top + item.wrapperHeight;
163
- const needsUp = top < this.scrollY + margin;
172
+ const needsUp = top < this.scrollY + margin + PADDING_Y;
164
173
  const needsDown = bottom > this.scrollY + this.viewportH - margin;
165
174
  if (needsUp) {
166
- this.scrollTo$.emit({ top, behavior });
175
+ this.scrollTo$.emit({ top: Math.max(0, top - PADDING_Y), behavior });
167
176
  } else if (needsDown) {
168
- this.scrollTo$.emit({ top: Math.max(0, bottom - this.viewportH), behavior });
177
+ this.scrollTo$.emit({ top: Math.max(0, bottom - this.viewportH + PADDING_Y), behavior });
169
178
  }
170
179
  }
171
180
  /* ------------ thumbnail raster ----------------------------------- */
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","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 },\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 private 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\n // Inner bitmap width cannot go below 1px\n const INNER_W = Math.max(1, OUTER_W - 2 * P);\n\n let offset = 0;\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,\n };\n this.emitWindow.emit(this.window);\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 /* 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 (this.window && 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\n if (this.viewportH <= 0) {\n // Center the thumbnail in the viewport\n const top = Math.max(0, 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;\n const needsDown = bottom > this.scrollY + this.viewportH - margin;\n\n if (needsUp) {\n this.scrollTo$.emit({ top, behavior });\n } else if (needsDown) {\n this.scrollTo$.emit({ top: Math.max(0, bottom - this.viewportH), 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":["top"],"mappings":";;AAGO,MAAM,sBAAsB;AAE5B,MAAM,WAAkD;AAAA,EAC7D,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,SAAS;AAAA,EACT,UAAU,CAAC,WAAW;AAAA,EACtB,UAAU,CAAC,QAAQ;AAAA,EACnB,UAAU,CAAC,QAAQ;AAAA,EACnB,eAAe;AAAA,IACb,SAAS;AAAA,IACT,OAAO;AAAA,IACP,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,cAAc;AAAA,EAAA;AAElB;ACLO,MAAM,mBAAN,MAAM,yBAAwB,WAAuD;AAAA,EAmB1F,YACE,IACA,UACQ,KACR;;AACA,UAAM,IAAI,QAAQ;AAFV,SAAA,MAAA;AAlBV,SAAQ,mBAA4C;AACpD,SAAQ,SAAsB,CAAC;AAC/B,SAAQ,SAA6B;AAGrC,SAAQ,YAAoB;AAC5B,SAAQ,UAAkB;AAE1B,SAAiB,aAAa,sBAAmC;AACjE,SAAiB,gBAAgB,cAAwB;AACxC,SAAA,gCAAgB,IAAwC;AACzE,SAAQ,gBAAgB;AAExB,SAAiB,YAAY,sBAAuC;AASlE,SAAK,mBAAmB,KAAK,SAAS,UAAwB,QAAQ,EAAG,SAAS;AAClF,SAAK,qBAAmB,UAAK,SAAS,UAAwB,QAAQ,MAA9C,mBAAiD,eAAc;AAEvF,SAAK,UAAU,SAAS,cAAc,CAAC,SAAS,UAAU;AACxD,WAAK,UAAU,MAAM;AACrB,WAAK,eAAe,KAAK;AAAA,IAAA,CAC1B;AAED,SAAK,UAAU,SAAS,eAAe,CAAC,WAAW;AAC5C,WAAA,cAAc,KAAK,OAAO,OAAO;AAC3B,iBAAA,WAAW,OAAO,SAAS;AAC/B,aAAA,UAAU,OAAO,OAAO;AAAA,MAAA;AAAA,IAC/B,CACD;AAGD,QAAI,KAAK,oBAAoB,KAAK,IAAI,eAAe,OAAO;AAC1D,WAAK,iBAAiB,kBAAkB,CAAC,EAAE,YAAY,iBAAiB;AACtE,aAAK,gBAAgB,CAAC;AACtB,YAAI,CAAC,YAAY;AACV,eAAA,cAAc,aAAa,CAAC;AAAA,QAAA;AAAA,MACnC,CACD;AACD,WAAK,iBAAiB,aAAa,CAAC,EAAE,iBAAiB;AACrD,YAAI,KAAK,eAAe;AACjB,eAAA,cAAc,aAAa,CAAC;AAAA,QAAA;AAAA,MACnC,CACD;AAAA,IAAA;AAAA,EACH;AAAA;AAAA,EAIF,MAAM,aAA4B;AAAA,EAAA;AAAA,EAE3B,eAAe,IAA4C;AACzD,WAAA,KAAK,cAAc,GAAG,EAAE;AAAA,EAAA;AAAA,EAG1B,SAAS,IAA2C;AAClD,WAAA,KAAK,WAAW,GAAG,EAAE;AAAA,EAAA;AAAA,EAGvB,WAAW,IAA+C;AACxD,WAAA,KAAK,UAAU,GAAG,EAAE;AAAA,EAAA;AAAA,EAGrB,eAAe,OAA8B;AACnD,UAAM,OAAO,MAAM;AACf,QAAA,CAAC,KAAK,SAAU;AAEd,UAAA,UAAU,KAAK,IAAI,SAAS;AAC5B,UAAA,IAAI,KAAK,IAAI,eAAe;AAC5B,UAAA,MAAM,KAAK,IAAI,OAAO;AACtB,UAAA,IAAI,KAAK,IAAI,gBAAgB;AAGnC,UAAM,UAAU,KAAK,IAAI,GAAG,UAAU,IAAI,CAAC;AAE3C,QAAI,SAAS;AACb,SAAK,SAAS,KAAK,SAAS,MAAM,IAAI,CAAC,MAAM;AAC3C,YAAM,QAAQ,EAAE,KAAK,SAAS,EAAE,KAAK;AACrC,YAAM,OAAO,KAAK,MAAM,UAAU,KAAK;AACjC,YAAA,QAAQ,IAAI,OAAO,IAAI;AAE7B,YAAM,OAAkB;AAAA,QACtB,WAAW,EAAE;AAAA,QACb,OAAO;AAAA;AAAA,QACP,QAAQ;AAAA;AAAA,QACR,eAAe;AAAA;AAAA,QACf,KAAK;AAAA;AAAA,QACL,aAAa;AAAA,QACb,SAAS;AAAA;AAAA,MACX;AACA,gBAAU,QAAQ;AACX,aAAA;AAAA,IAAA,CACR;AAED,SAAK,SAAS;AAAA,MACZ,OAAO;AAAA,MACP,KAAK;AAAA,MACL,OAAO,CAAC;AAAA,MACR,aAAa,SAAS;AAAA,IACxB;AACK,SAAA,WAAW,KAAK,KAAK,MAAM;AAAA,EAAA;AAAA;AAAA,EAIxB,kBAAuC;AACxC,WAAA;AAAA,MACL,aAAa,CAAC,KAAK,QAAQ,KAAK,YAAY,KAAK,GAAG;AAAA,MACpD,eAAe,CAAC,YAAY,KAAK,cAAc,OAAO;AAAA,IACxD;AAAA,EAAA;AAAA;AAAA,EAIK,aAAa,SAAiB,WAAmB;AAChD,UAAA,MAAM,KAAK,IAAI,UAAU;AAG/B,SAAK,UAAU;AACf,SAAK,YAAY;AAGjB,QAAI,MAAM,GACR,OAAO,KAAK,OAAO,SAAS,GAC5B,QAAQ;AACV,WAAO,OAAO,MAAM;AACZ,YAAA,MAAO,MAAM,QAAS;AACtB,YAAA,IAAI,KAAK,OAAO,GAAG;AACzB,UAAI,EAAE,MAAM,EAAE,gBAAgB,eAAe,MAAM;AAAA,WAC9C;AACK,gBAAA;AACR,eAAO,MAAM;AAAA,MAAA;AAAA,IACf;AAIF,QAAI,OAAO;AACX,UAAM,QAAQ,UAAU;AACjB,WAAA,OAAO,IAAI,KAAK,OAAO,UAAU,KAAK,OAAO,IAAI,EAAE,MAAM,MAAO;AACvE,WAAO,KAAK,IAAI,KAAK,OAAO,SAAS,GAAG,OAAO,GAAG;AAElD,UAAM,QAAQ,KAAK,IAAI,GAAG,QAAQ,GAAG;AACjC,QAAA,KAAK,UAAU,UAAU,KAAK,OAAO,SAAS,SAAS,KAAK,OAAO,IAAK;AAE5E,SAAK,SAAS;AAAA,MACZ;AAAA,MACA,KAAK;AAAA,MACL,OAAO,KAAK,OAAO,MAAM,OAAO,OAAO,CAAC;AAAA,MACxC,aAAa,KAAK,OAAQ;AAAA,IAC5B;AACK,SAAA,WAAW,KAAK,KAAK,MAAM;AAAA,EAAA;AAAA;AAAA,EAI1B,cAAc,SAAiB;AACjC,QAAA,CAAC,KAAK,OAAQ;AACZ,UAAA,OAAO,KAAK,OAAO,OAAO;AAChC,QAAI,CAAC,KAAM;AAEL,UAAA,WAAW,KAAK,IAAI,kBAAkB;AAExC,QAAA,KAAK,aAAa,GAAG;AAEvB,YAAMA,OAAM,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,aAAa;AACrD,WAAK,UAAU,KAAK,EAAE,KAAAA,MAAK,UAAU;AACrC;AAAA,IAAA;AAGF,UAAM,SAAS;AACf,UAAM,MAAM,KAAK;AACX,UAAA,SAAS,KAAK,MAAM,KAAK;AAEzB,UAAA,UAAU,MAAM,KAAK,UAAU;AACrC,UAAM,YAAY,SAAS,KAAK,UAAU,KAAK,YAAY;AAE3D,QAAI,SAAS;AACX,WAAK,UAAU,KAAK,EAAE,KAAK,UAAU;AAAA,eAC5B,WAAW;AACpB,WAAK,UAAU,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,SAAS,KAAK,SAAS,GAAG,SAAA,CAAU;AAAA,IAAA;AAAA,EAC7E;AAAA;AAAA,EAIM,YAAY,KAAa,KAAa;AACxC,QAAA,KAAK,UAAU,IAAI,GAAG,EAAU,QAAA,KAAK,UAAU,IAAI,GAAG;AAEpD,UAAA,OAAO,KAAK,UAAU;AAC5B,UAAM,OAAO,KAAK,SAAU,MAAM,GAAG;AAE/B,UAAA,UAAU,KAAK,IAAI,SAAS;AAC5B,UAAA,IAAI,KAAK,IAAI,gBAAgB;AACnC,UAAM,UAAU,KAAK,IAAI,GAAG,UAAU,IAAI,CAAC;AAErC,UAAA,QAAQ,UAAU,KAAK,KAAK;AAE5B,UAAA,OAAO,KAAK,iBAAiB,eAAe;AAAA,MAChD,WAAW;AAAA,MACX,MAAM,EAAE,QAAQ,EAAE,GAAG,GAAG,GAAG,KAAK,MAAM,KAAK,KAAK;AAAA,MAChD,SAAS;AAAA,QACP,aAAa;AAAA,QACb;AAAA,MAAA;AAAA,IACF,CACD;AAEI,SAAA,UAAU,IAAI,KAAK,IAAI;AAC5B,SAAK,KAAK,QAAQ,MAAM,KAAK,UAAU,OAAO,GAAG,CAAC;AAC3C,WAAA;AAAA,EAAA;AAEX;AAtNE,iBAAgB,KAAK;AADhB,IAAM,kBAAN;ACZA,MAAM,yBAAgF;AAAA,EAC3F;AAAA,EACA,QAAQ,CAAC,UAAU,WAAW,IAAI,gBAAgB,qBAAqB,UAAU,MAAM;AAAA,EACvF,SAAS,MAAM;AAAA,EAAC;AAAA,EAChB,cAAc,CAAA;AAChB;"}
1
+ {"version":3,"file":"index.js","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":["top"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { BasePlugin, PluginRegistry, Unsubscribe } from '@embedpdf/core';
2
2
  import { ScrollToOptions, ThumbnailPluginConfig, WindowState, ThumbnailCapability } from './types';
3
3
  export declare class ThumbnailPlugin extends BasePlugin<ThumbnailPluginConfig, ThumbnailCapability> {
4
- private cfg;
4
+ cfg: ThumbnailPluginConfig;
5
5
  static readonly id: "thumbnail";
6
6
  private renderCapability;
7
7
  private scrollCapability;
@@ -2,13 +2,22 @@ import { BasePluginConfig } from '@embedpdf/core';
2
2
  import { PdfErrorReason, Task } from '@embedpdf/models';
3
3
  import { ScrollBehavior } from '@embedpdf/plugin-scroll';
4
4
  export interface ThumbnailPluginConfig extends BasePluginConfig {
5
+ /** Thumbnail width in CSS pixels. Controls the display width of each thumbnail image. Default: 120 */
5
6
  width?: number;
7
+ /** Vertical gap between thumbnails in CSS pixels. Controls spacing between each thumbnail row. Default: 8 */
6
8
  gap?: number;
9
+ /** Number of extra thumbnail rows to render above and below the visible viewport. Higher values improve scrolling smoothness but use more memory. Default: 3 */
7
10
  buffer?: number;
11
+ /** Height reserved for the page number label below each thumbnail in CSS pixels. Set to 0 to hide labels. Default: 16 */
8
12
  labelHeight?: number;
13
+ /** Whether to automatically scroll the thumbnail pane when the main document's current page changes. Default: true */
9
14
  autoScroll?: boolean;
15
+ /** Scroll animation behavior when auto-scrolling or programmatically scrolling to thumbnails. Default: 'smooth' */
10
16
  scrollBehavior?: ScrollBehavior;
17
+ /** Internal padding around each thumbnail image in CSS pixels. Creates visual spacing between the image and its border. Default: 0 */
11
18
  imagePadding?: number;
19
+ /** Vertical padding for the entire thumbnail pane in CSS pixels. Adds space at the top and bottom of the scrollable area. Default: 0 */
20
+ paddingY?: number;
12
21
  }
13
22
  export interface ScrollToOptions {
14
23
  top: number;
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@embedpdf/core/preact"),t=require("@embedpdf/plugin-thumbnail"),r=require("preact/jsx-runtime");require("preact");const n=require("preact/hooks"),l=require("@embedpdf/models"),o=()=>e.usePlugin(t.ThumbnailPlugin.id),u=()=>e.useCapability(t.ThumbnailPlugin.id);exports.ThumbImg=function({meta:e,style:t,...i}){const{provides:s}=u(),{plugin:c}=o(),[a,d]=n.useState(),p=n.useRef(null),[f,b]=n.useState(0);return n.useEffect((()=>{if(c)return c.onRefreshPages((t=>{t.includes(e.pageIndex)&&b((e=>e+1))}))}),[c]),n.useEffect((()=>{const t=null==s?void 0:s.renderThumb(e.pageIndex,window.devicePixelRatio);return null==t||t.wait((e=>{const t=URL.createObjectURL(e);p.current=t,d(t)}),l.ignore),()=>{p.current?(URL.revokeObjectURL(p.current),p.current=null):null==t||t.abort({code:l.PdfErrorCode.Cancelled,message:"canceled render task"})}}),[s,e.pageIndex,f]),a?r.jsx("img",{src:a,onLoad:()=>{p.current&&(URL.revokeObjectURL(p.current),p.current=null)},style:t,...i}):null},exports.ThumbnailsPane=function({style:e,scrollOptions:t,selectedPage:l,...u}){const{plugin:i}=o(),s=n.useRef(null),[c,a]=n.useState(null);return n.useEffect((()=>null==i?void 0:i.onWindow(a)),[i]),n.useEffect((()=>{const e=s.current;if(!e)return;const t=()=>null==i?void 0:i.updateWindow(e.scrollTop,e.clientHeight);return e.addEventListener("scroll",t),()=>e.removeEventListener("scroll",t)}),[i]),n.useEffect((()=>{const e=s.current;e&&i&&i.updateWindow(e.scrollTop,e.clientHeight)}),[c,i]),n.useEffect((()=>{const e=s.current;if(e&&i&&c)return i.onScrollTo((({top:t,behavior:r})=>{e.scrollTo({top:t,behavior:r})}))}),[i,!!c]),r.jsx("div",{ref:s,style:{overflowY:"auto",position:"relative",...e},...u,children:r.jsx("div",{style:{height:(null==c?void 0:c.totalHeight)??0,position:"relative"},children:null==c?void 0:c.items.map((e=>u.children(e)))})})},exports.useThumbnailCapability=u,exports.useThumbnailPlugin=o,Object.keys(t).forEach((e=>{"default"===e||Object.prototype.hasOwnProperty.call(exports,e)||Object.defineProperty(exports,e,{enumerable:!0,get:()=>t[e]})}));
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@embedpdf/core/preact"),t=require("@embedpdf/plugin-thumbnail"),r=require("preact/jsx-runtime");require("preact");const n=require("preact/hooks"),o=require("@embedpdf/models"),l=()=>e.usePlugin(t.ThumbnailPlugin.id),i=()=>e.useCapability(t.ThumbnailPlugin.id);exports.ThumbImg=function({meta:e,style:t,...u}){const{provides:s}=i(),{plugin:c}=l(),[d,a]=n.useState(),p=n.useRef(null),[f,g]=n.useState(0);return n.useEffect((()=>{if(c)return c.onRefreshPages((t=>{t.includes(e.pageIndex)&&g((e=>e+1))}))}),[c]),n.useEffect((()=>{const t=null==s?void 0:s.renderThumb(e.pageIndex,window.devicePixelRatio);return null==t||t.wait((e=>{const t=URL.createObjectURL(e);p.current=t,a(t)}),o.ignore),()=>{p.current?(URL.revokeObjectURL(p.current),p.current=null):null==t||t.abort({code:o.PdfErrorCode.Cancelled,message:"canceled render task"})}}),[s,e.pageIndex,f]),d?r.jsx("img",{src:d,onLoad:()=>{p.current&&(URL.revokeObjectURL(p.current),p.current=null)},style:t,...u}):null},exports.ThumbnailsPane=function({style:e,scrollOptions:t,selectedPage:o,...i}){const{plugin:u}=l(),s=n.useRef(null),[c,d]=n.useState(null);n.useEffect((()=>null==u?void 0:u.onWindow(d)),[u]),n.useEffect((()=>{const e=s.current;if(!e)return;const t=()=>null==u?void 0:u.updateWindow(e.scrollTop,e.clientHeight);return e.addEventListener("scroll",t),()=>e.removeEventListener("scroll",t)}),[u]),n.useEffect((()=>{const e=s.current;if(!e||!u)return;const t=new ResizeObserver((()=>{u.updateWindow(e.scrollTop,e.clientHeight)}));return t.observe(e),()=>t.disconnect()}),[u]),n.useEffect((()=>{const e=s.current;e&&u&&u.updateWindow(e.scrollTop,e.clientHeight)}),[c,u]),n.useEffect((()=>{const e=s.current;if(e&&u&&c)return u.onScrollTo((({top:t,behavior:r})=>{e.scrollTo({top:t,behavior:r})}))}),[u,!!c]);const a=(null==u?void 0:u.cfg.paddingY)??0;return r.jsx("div",{ref:s,style:{overflowY:"auto",position:"relative",paddingTop:a,paddingBottom:a,height:"100%",...e},...i,children:r.jsx("div",{style:{height:(null==c?void 0:c.totalHeight)??0,position:"relative"},children:null==c?void 0:c.items.map((e=>i.children(e)))})})},exports.useThumbnailCapability=i,exports.useThumbnailPlugin=l,Object.keys(t).forEach((e=>{"default"===e||Object.prototype.hasOwnProperty.call(exports,e)||Object.defineProperty(exports,e,{enumerable:!0,get:()=>t[e]})}));
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../src/shared/hooks/use-thumbnail.ts","../../src/shared/components/thumbnail-img.tsx","../../src/shared/components/thumbnails-pane.tsx"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/@framework';\nimport { ThumbnailPlugin } from '@embedpdf/plugin-thumbnail';\n\nexport const useThumbnailPlugin = () => usePlugin<ThumbnailPlugin>(ThumbnailPlugin.id);\nexport const useThumbnailCapability = () => useCapability<ThumbnailPlugin>(ThumbnailPlugin.id);\n","import { useEffect, useState, useRef, HTMLAttributes, CSSProperties, Fragment } from '@framework';\nimport { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\nimport { useThumbnailCapability, useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailImgProps = Omit<HTMLAttributes<HTMLImageElement>, 'style'> & {\n style?: CSSProperties;\n meta: ThumbMeta;\n};\n\nexport function ThumbImg({ meta, style, ...props }: ThumbnailImgProps) {\n const { provides: thumbs } = useThumbnailCapability();\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const [url, setUrl] = useState<string>();\n const urlRef = useRef<string | null>(null);\n const [refreshTick, setRefreshTick] = useState(0);\n\n useEffect(() => {\n if (!thumbnailPlugin) return;\n return thumbnailPlugin.onRefreshPages((pages) => {\n if (pages.includes(meta.pageIndex)) {\n setRefreshTick((tick) => tick + 1);\n }\n });\n }, [thumbnailPlugin]);\n\n useEffect(() => {\n const task = thumbs?.renderThumb(meta.pageIndex, window.devicePixelRatio);\n task?.wait((blob) => {\n const objectUrl = URL.createObjectURL(blob);\n urlRef.current = objectUrl;\n setUrl(objectUrl);\n }, ignore);\n\n return () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n } else {\n task?.abort({\n code: PdfErrorCode.Cancelled,\n message: 'canceled render task',\n });\n }\n };\n }, [thumbs, meta.pageIndex, refreshTick]);\n\n const handleImageLoad = () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n };\n\n return url ? <img src={url} onLoad={handleImageLoad} style={style} {...props} /> : null;\n}\n","import { useEffect, useRef, useState, HTMLAttributes, CSSProperties, ReactNode } from '@framework';\nimport { ThumbMeta, WindowState } from '@embedpdf/plugin-thumbnail';\nimport { useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n /** @deprecated use scrollToThumb via capability or rely on autoScroll */\n selectedPage?: number;\n /** @deprecated behavior is now controlled by ThumbnailPluginConfig.scrollBehavior */\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({ style, scrollOptions, selectedPage, ...props }: ThumbnailsProps) {\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const viewportRef = useRef<HTMLDivElement>(null);\n\n const [window, setWindow] = useState<WindowState | null>(null);\n\n // 1) subscribe once to window updates\n useEffect(() => thumbnailPlugin?.onWindow(setWindow), [thumbnailPlugin]);\n\n // 2) keep plugin in sync while the user scrolls\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp) return;\n const onScroll = () => thumbnailPlugin?.updateWindow(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbnailPlugin]);\n\n // 3) kick-start after document change\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin) return;\n\n // push initial metrics\n thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);\n }, [window, thumbnailPlugin]);\n\n // 4) let plugin drive scroll – only after window is set, and only once\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin || !window) return;\n\n return thumbnailPlugin.onScrollTo(({ top, behavior }) => {\n vp.scrollTo({ top, behavior });\n });\n }, [thumbnailPlugin, !!window]); // Note: !!window to prevent re-subscription on window updates\n\n return (\n <div ref={viewportRef} style={{ overflowY: 'auto', position: 'relative', ...style }} {...props}>\n <div style={{ height: window?.totalHeight ?? 0, position: 'relative' }}>\n {window?.items.map((m) => props.children(m))}\n </div>\n </div>\n );\n}\n"],"names":["useThumbnailPlugin","usePlugin","ThumbnailPlugin","id","useThumbnailCapability","useCapability","meta","style","props","provides","thumbs","plugin","thumbnailPlugin","url","setUrl","useState","urlRef","useRef","refreshTick","setRefreshTick","useEffect","onRefreshPages","pages","includes","pageIndex","tick","task","renderThumb","window","devicePixelRatio","wait","blob","objectUrl","URL","createObjectURL","current","ignore","revokeObjectURL","abort","code","PdfErrorCode","Cancelled","message","jsx","src","onLoad","scrollOptions","selectedPage","viewportRef","setWindow","onWindow","vp","onScroll","updateWindow","scrollTop","clientHeight","addEventListener","removeEventListener","onScrollTo","top","behavior","scrollTo","ref","overflowY","position","children","height","totalHeight","items","map","m"],"mappings":"iRAGaA,EAAqB,IAAMC,YAA2BC,EAAAA,gBAAgBC,IACtEC,EAAyB,IAAMC,gBAA+BH,EAAAA,gBAAgBC,qBCMpF,UAAkBG,KAAEA,EAAAC,MAAMA,KAAUC,IACzC,MAAQC,SAAUC,GAAWN,KACrBO,OAAQC,GAAoBZ,KAC7Ba,EAAKC,GAAUC,aAChBC,EAASC,SAAsB,OAC9BC,EAAaC,GAAkBJ,EAAAA,SAAS,GAuCxC,OArCPK,EAAAA,WAAU,KACR,GAAKR,EACE,OAAAA,EAAgBS,gBAAgBC,IACjCA,EAAMC,SAASjB,EAAKkB,YACPL,GAACM,GAASA,EAAO,GAAC,GAEpC,GACA,CAACb,IAEJQ,EAAAA,WAAU,KACR,MAAMM,EAAO,MAAAhB,OAAA,EAAAA,EAAQiB,YAAYrB,EAAKkB,UAAWI,OAAOC,kBAOxD,OANM,MAAAH,GAAAA,EAAAI,MAAMC,IACJ,MAAAC,EAAYC,IAAIC,gBAAgBH,GACtCf,EAAOmB,QAAUH,EACjBlB,EAAOkB,EAAS,GACfI,UAEI,KACDpB,EAAOmB,SACLF,IAAAI,gBAAgBrB,EAAOmB,SAC3BnB,EAAOmB,QAAU,MAEjB,MAAAT,GAAAA,EAAMY,MAAM,CACVC,KAAMC,EAAaA,aAAAC,UACnBC,QAAS,wBACV,CAEL,GACC,CAAChC,EAAQJ,EAAKkB,UAAWN,IASrBL,EAAO8B,EAAAA,IAAA,MAAA,CAAIC,IAAK/B,EAAKgC,OAPJ,KAClB7B,EAAOmB,UACLF,IAAAI,gBAAgBrB,EAAOmB,SAC3BnB,EAAOmB,QAAU,KAAA,EAIgC5B,WAAkBC,IAAY,IACrF,yBC1CO,UAAwBD,MAAEA,EAAAuC,cAAOA,eAAeC,KAAiBvC,IACtE,MAAQG,OAAQC,GAAoBZ,IAC9BgD,EAAc/B,SAAuB,OAEpCW,EAAQqB,GAAalC,EAAAA,SAA6B,aAGzDK,EAAAA,WAAU,IAAM,MAAAR,OAAA,EAAAA,EAAiBsC,SAASD,IAAY,CAACrC,IAGvDQ,EAAAA,WAAU,KACR,MAAM+B,EAAKH,EAAYb,QACvB,IAAKgB,EAAI,OACT,MAAMC,EAAW,IAAM,MAAAxC,OAAA,EAAAA,EAAiByC,aAAaF,EAAGG,UAAWH,EAAGI,cAEtE,OADGJ,EAAAK,iBAAiB,SAAUJ,GACvB,IAAMD,EAAGM,oBAAoB,SAAUL,EAAQ,GACrD,CAACxC,IAGJQ,EAAAA,WAAU,KACR,MAAM+B,EAAKH,EAAYb,QAClBgB,GAAOvC,GAGZA,EAAgByC,aAAaF,EAAGG,UAAWH,EAAGI,aAAY,GACzD,CAAC3B,EAAQhB,IAGZQ,EAAAA,WAAU,KACR,MAAM+B,EAAKH,EAAYb,QACvB,GAAKgB,GAAOvC,GAAoBgB,EAEhC,OAAOhB,EAAgB8C,YAAW,EAAGC,MAAKC,eACxCT,EAAGU,SAAS,CAAEF,MAAKC,YAAU,GAC9B,GACA,CAAChD,IAAmBgB,UAGpB,MAAI,CAAAkC,IAAKd,EAAazC,MAAO,CAAEwD,UAAW,OAAQC,SAAU,cAAezD,MAAaC,EACvFyD,eAAC,MAAI,CAAA1D,MAAO,CAAE2D,QAAQ,MAAAtC,SAAAA,EAAQuC,cAAe,EAAGH,SAAU,YACvDC,SAAQ,MAAArC,OAAA,EAAAA,EAAAwC,MAAMC,KAAKC,GAAM9D,EAAMyD,SAASK,QAIjD"}
1
+ {"version":3,"file":"index.cjs","sources":["../../src/shared/hooks/use-thumbnail.ts","../../src/shared/components/thumbnail-img.tsx","../../src/shared/components/thumbnails-pane.tsx"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/@framework';\nimport { ThumbnailPlugin } from '@embedpdf/plugin-thumbnail';\n\nexport const useThumbnailPlugin = () => usePlugin<ThumbnailPlugin>(ThumbnailPlugin.id);\nexport const useThumbnailCapability = () => useCapability<ThumbnailPlugin>(ThumbnailPlugin.id);\n","import { useEffect, useState, useRef, HTMLAttributes, CSSProperties, Fragment } from '@framework';\nimport { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\nimport { useThumbnailCapability, useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailImgProps = Omit<HTMLAttributes<HTMLImageElement>, 'style'> & {\n style?: CSSProperties;\n meta: ThumbMeta;\n};\n\nexport function ThumbImg({ meta, style, ...props }: ThumbnailImgProps) {\n const { provides: thumbs } = useThumbnailCapability();\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const [url, setUrl] = useState<string>();\n const urlRef = useRef<string | null>(null);\n const [refreshTick, setRefreshTick] = useState(0);\n\n useEffect(() => {\n if (!thumbnailPlugin) return;\n return thumbnailPlugin.onRefreshPages((pages) => {\n if (pages.includes(meta.pageIndex)) {\n setRefreshTick((tick) => tick + 1);\n }\n });\n }, [thumbnailPlugin]);\n\n useEffect(() => {\n const task = thumbs?.renderThumb(meta.pageIndex, window.devicePixelRatio);\n task?.wait((blob) => {\n const objectUrl = URL.createObjectURL(blob);\n urlRef.current = objectUrl;\n setUrl(objectUrl);\n }, ignore);\n\n return () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n } else {\n task?.abort({\n code: PdfErrorCode.Cancelled,\n message: 'canceled render task',\n });\n }\n };\n }, [thumbs, meta.pageIndex, refreshTick]);\n\n const handleImageLoad = () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n };\n\n return url ? <img src={url} onLoad={handleImageLoad} style={style} {...props} /> : null;\n}\n","import { useEffect, useRef, useState, HTMLAttributes, CSSProperties, ReactNode } from '@framework';\nimport { ThumbMeta, WindowState } from '@embedpdf/plugin-thumbnail';\nimport { useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n /** @deprecated use scrollToThumb via capability or rely on autoScroll */\n selectedPage?: number;\n /** @deprecated behavior is now controlled by ThumbnailPluginConfig.scrollBehavior */\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({ style, scrollOptions, selectedPage, ...props }: ThumbnailsProps) {\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const viewportRef = useRef<HTMLDivElement>(null);\n\n const [window, setWindow] = useState<WindowState | null>(null);\n\n // 1) subscribe once to window updates\n useEffect(() => thumbnailPlugin?.onWindow(setWindow), [thumbnailPlugin]);\n\n // 2) keep plugin in sync while the user scrolls\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp) return;\n const onScroll = () => thumbnailPlugin?.updateWindow(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbnailPlugin]);\n\n // 2.5) keep plugin in sync when viewport resizes (e.g., menu opens/closes)\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin) return;\n\n const resizeObserver = new ResizeObserver(() => {\n thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);\n });\n resizeObserver.observe(vp);\n\n return () => resizeObserver.disconnect();\n }, [thumbnailPlugin]);\n\n // 3) kick-start after document change\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin) return;\n\n // push initial metrics\n thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);\n }, [window, thumbnailPlugin]);\n\n // 4) let plugin drive scroll – only after window is set, and only once\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin || !window) return;\n\n return thumbnailPlugin.onScrollTo(({ top, behavior }) => {\n vp.scrollTo({ top, behavior });\n });\n }, [thumbnailPlugin, !!window]); // Note: !!window to prevent re-subscription on window updates\n\n const paddingY = thumbnailPlugin?.cfg.paddingY ?? 0;\n\n return (\n <div\n ref={viewportRef}\n style={{\n overflowY: 'auto',\n position: 'relative',\n paddingTop: paddingY,\n paddingBottom: paddingY,\n height: '100%',\n ...style,\n }}\n {...props}\n >\n <div style={{ height: window?.totalHeight ?? 0, position: 'relative' }}>\n {window?.items.map((m) => props.children(m))}\n </div>\n </div>\n );\n}\n"],"names":["useThumbnailPlugin","usePlugin","ThumbnailPlugin","id","useThumbnailCapability","useCapability","meta","style","props","provides","thumbs","plugin","thumbnailPlugin","url","setUrl","useState","urlRef","useRef","refreshTick","setRefreshTick","useEffect","onRefreshPages","pages","includes","pageIndex","tick","task","renderThumb","window","devicePixelRatio","wait","blob","objectUrl","URL","createObjectURL","current","ignore","revokeObjectURL","abort","code","PdfErrorCode","Cancelled","message","jsx","src","onLoad","scrollOptions","selectedPage","viewportRef","setWindow","onWindow","vp","onScroll","updateWindow","scrollTop","clientHeight","addEventListener","removeEventListener","resizeObserver","ResizeObserver","observe","disconnect","onScrollTo","top","behavior","scrollTo","paddingY","cfg","jsxRuntime","ref","overflowY","position","paddingTop","paddingBottom","height","children","totalHeight","items","map","m"],"mappings":"iRAGaA,EAAqB,IAAMC,YAA2BC,EAAAA,gBAAgBC,IACtEC,EAAyB,IAAMC,gBAA+BH,EAAAA,gBAAgBC,qBCMpF,UAAkBG,KAAEA,EAAAC,MAAMA,KAAUC,IACzC,MAAQC,SAAUC,GAAWN,KACrBO,OAAQC,GAAoBZ,KAC7Ba,EAAKC,GAAUC,aAChBC,EAASC,SAAsB,OAC9BC,EAAaC,GAAkBJ,EAAAA,SAAS,GAuCxC,OArCPK,EAAAA,WAAU,KACR,GAAKR,EACE,OAAAA,EAAgBS,gBAAgBC,IACjCA,EAAMC,SAASjB,EAAKkB,YACPL,GAACM,GAASA,EAAO,GAAC,GAEpC,GACA,CAACb,IAEJQ,EAAAA,WAAU,KACR,MAAMM,EAAO,MAAAhB,OAAA,EAAAA,EAAQiB,YAAYrB,EAAKkB,UAAWI,OAAOC,kBAOxD,OANM,MAAAH,GAAAA,EAAAI,MAAMC,IACJ,MAAAC,EAAYC,IAAIC,gBAAgBH,GACtCf,EAAOmB,QAAUH,EACjBlB,EAAOkB,EAAS,GACfI,UAEI,KACDpB,EAAOmB,SACLF,IAAAI,gBAAgBrB,EAAOmB,SAC3BnB,EAAOmB,QAAU,MAEjB,MAAAT,GAAAA,EAAMY,MAAM,CACVC,KAAMC,EAAaA,aAAAC,UACnBC,QAAS,wBACV,CAEL,GACC,CAAChC,EAAQJ,EAAKkB,UAAWN,IASrBL,EAAO8B,EAAAA,IAAA,MAAA,CAAIC,IAAK/B,EAAKgC,OAPJ,KAClB7B,EAAOmB,UACLF,IAAAI,gBAAgBrB,EAAOmB,SAC3BnB,EAAOmB,QAAU,KAAA,EAIgC5B,WAAkBC,IAAY,IACrF,yBC1CO,UAAwBD,MAAEA,EAAAuC,cAAOA,eAAeC,KAAiBvC,IACtE,MAAQG,OAAQC,GAAoBZ,IAC9BgD,EAAc/B,SAAuB,OAEpCW,EAAQqB,GAAalC,EAAAA,SAA6B,MAGzDK,EAAAA,WAAU,IAAM,MAAAR,OAAA,EAAAA,EAAiBsC,SAASD,IAAY,CAACrC,IAGvDQ,EAAAA,WAAU,KACR,MAAM+B,EAAKH,EAAYb,QACvB,IAAKgB,EAAI,OACT,MAAMC,EAAW,IAAM,MAAAxC,OAAA,EAAAA,EAAiByC,aAAaF,EAAGG,UAAWH,EAAGI,cAEtE,OADGJ,EAAAK,iBAAiB,SAAUJ,GACvB,IAAMD,EAAGM,oBAAoB,SAAUL,EAAQ,GACrD,CAACxC,IAGJQ,EAAAA,WAAU,KACR,MAAM+B,EAAKH,EAAYb,QACnB,IAACgB,IAAOvC,EAAiB,OAEvB,MAAA8C,EAAiB,IAAIC,gBAAe,KACxC/C,EAAgByC,aAAaF,EAAGG,UAAWH,EAAGI,aAAY,IAIrD,OAFPG,EAAeE,QAAQT,GAEhB,IAAMO,EAAeG,YAAW,GACtC,CAACjD,IAGJQ,EAAAA,WAAU,KACR,MAAM+B,EAAKH,EAAYb,QAClBgB,GAAOvC,GAGZA,EAAgByC,aAAaF,EAAGG,UAAWH,EAAGI,aAAY,GACzD,CAAC3B,EAAQhB,IAGZQ,EAAAA,WAAU,KACR,MAAM+B,EAAKH,EAAYb,QACvB,GAAKgB,GAAOvC,GAAoBgB,EAEhC,OAAOhB,EAAgBkD,YAAW,EAAGC,MAAKC,eACxCb,EAAGc,SAAS,CAAEF,MAAKC,YAAU,GAC9B,GACA,CAACpD,IAAmBgB,IAEjB,MAAAsC,GAA4B,MAAjBtD,OAAiB,EAAAA,EAAAuD,IAAID,WAAY,EAGhD,OAAAE,EAAAzB,IAAC,MAAA,CACC0B,IAAKrB,EACLzC,MAAO,CACL+D,UAAW,OACXC,SAAU,WACVC,WAAYN,EACZO,cAAeP,EACfQ,OAAQ,UACLnE,MAEDC,EAEJmE,SAAAhC,EAAAA,IAAC,OAAIpC,MAAO,CAAEmE,QAAQ,MAAA9C,OAAA,EAAAA,EAAQgD,cAAe,EAAGL,SAAU,YACvDI,SAAQ,MAAA/C,OAAAA,EAAAA,EAAAiD,MAAMC,KAAKC,GAAMvE,EAAMmE,SAASI,QAIjD"}
@@ -19,6 +19,15 @@ function ThumbnailsPane({ style, scrollOptions, selectedPage, ...props }) {
19
19
  vp.addEventListener("scroll", onScroll);
20
20
  return () => vp.removeEventListener("scroll", onScroll);
21
21
  }, [thumbnailPlugin]);
22
+ useEffect(() => {
23
+ const vp = viewportRef.current;
24
+ if (!vp || !thumbnailPlugin) return;
25
+ const resizeObserver = new ResizeObserver(() => {
26
+ thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);
27
+ });
28
+ resizeObserver.observe(vp);
29
+ return () => resizeObserver.disconnect();
30
+ }, [thumbnailPlugin]);
22
31
  useEffect(() => {
23
32
  const vp = viewportRef.current;
24
33
  if (!vp || !thumbnailPlugin) return;
@@ -31,7 +40,23 @@ function ThumbnailsPane({ style, scrollOptions, selectedPage, ...props }) {
31
40
  vp.scrollTo({ top, behavior });
32
41
  });
33
42
  }, [thumbnailPlugin, !!window2]);
34
- return /* @__PURE__ */ jsx("div", { ref: viewportRef, style: { overflowY: "auto", position: "relative", ...style }, ...props, children: /* @__PURE__ */ jsx("div", { style: { height: (window2 == null ? void 0 : window2.totalHeight) ?? 0, position: "relative" }, children: window2 == null ? void 0 : window2.items.map((m) => props.children(m)) }) });
43
+ const paddingY = (thumbnailPlugin == null ? void 0 : thumbnailPlugin.cfg.paddingY) ?? 0;
44
+ return /* @__PURE__ */ jsx(
45
+ "div",
46
+ {
47
+ ref: viewportRef,
48
+ style: {
49
+ overflowY: "auto",
50
+ position: "relative",
51
+ paddingTop: paddingY,
52
+ paddingBottom: paddingY,
53
+ height: "100%",
54
+ ...style
55
+ },
56
+ ...props,
57
+ children: /* @__PURE__ */ jsx("div", { style: { height: (window2 == null ? void 0 : window2.totalHeight) ?? 0, position: "relative" }, children: window2 == null ? void 0 : window2.items.map((m) => props.children(m)) })
58
+ }
59
+ );
35
60
  }
36
61
  function ThumbImg({ meta, style, ...props }) {
37
62
  const { provides: thumbs } = useThumbnailCapability();
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/shared/hooks/use-thumbnail.ts","../../src/shared/components/thumbnails-pane.tsx","../../src/shared/components/thumbnail-img.tsx"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/@framework';\nimport { ThumbnailPlugin } from '@embedpdf/plugin-thumbnail';\n\nexport const useThumbnailPlugin = () => usePlugin<ThumbnailPlugin>(ThumbnailPlugin.id);\nexport const useThumbnailCapability = () => useCapability<ThumbnailPlugin>(ThumbnailPlugin.id);\n","import { useEffect, useRef, useState, HTMLAttributes, CSSProperties, ReactNode } from '@framework';\nimport { ThumbMeta, WindowState } from '@embedpdf/plugin-thumbnail';\nimport { useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n /** @deprecated use scrollToThumb via capability or rely on autoScroll */\n selectedPage?: number;\n /** @deprecated behavior is now controlled by ThumbnailPluginConfig.scrollBehavior */\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({ style, scrollOptions, selectedPage, ...props }: ThumbnailsProps) {\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const viewportRef = useRef<HTMLDivElement>(null);\n\n const [window, setWindow] = useState<WindowState | null>(null);\n\n // 1) subscribe once to window updates\n useEffect(() => thumbnailPlugin?.onWindow(setWindow), [thumbnailPlugin]);\n\n // 2) keep plugin in sync while the user scrolls\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp) return;\n const onScroll = () => thumbnailPlugin?.updateWindow(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbnailPlugin]);\n\n // 3) kick-start after document change\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin) return;\n\n // push initial metrics\n thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);\n }, [window, thumbnailPlugin]);\n\n // 4) let plugin drive scroll – only after window is set, and only once\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin || !window) return;\n\n return thumbnailPlugin.onScrollTo(({ top, behavior }) => {\n vp.scrollTo({ top, behavior });\n });\n }, [thumbnailPlugin, !!window]); // Note: !!window to prevent re-subscription on window updates\n\n return (\n <div ref={viewportRef} style={{ overflowY: 'auto', position: 'relative', ...style }} {...props}>\n <div style={{ height: window?.totalHeight ?? 0, position: 'relative' }}>\n {window?.items.map((m) => props.children(m))}\n </div>\n </div>\n );\n}\n","import { useEffect, useState, useRef, HTMLAttributes, CSSProperties, Fragment } from '@framework';\nimport { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\nimport { useThumbnailCapability, useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailImgProps = Omit<HTMLAttributes<HTMLImageElement>, 'style'> & {\n style?: CSSProperties;\n meta: ThumbMeta;\n};\n\nexport function ThumbImg({ meta, style, ...props }: ThumbnailImgProps) {\n const { provides: thumbs } = useThumbnailCapability();\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const [url, setUrl] = useState<string>();\n const urlRef = useRef<string | null>(null);\n const [refreshTick, setRefreshTick] = useState(0);\n\n useEffect(() => {\n if (!thumbnailPlugin) return;\n return thumbnailPlugin.onRefreshPages((pages) => {\n if (pages.includes(meta.pageIndex)) {\n setRefreshTick((tick) => tick + 1);\n }\n });\n }, [thumbnailPlugin]);\n\n useEffect(() => {\n const task = thumbs?.renderThumb(meta.pageIndex, window.devicePixelRatio);\n task?.wait((blob) => {\n const objectUrl = URL.createObjectURL(blob);\n urlRef.current = objectUrl;\n setUrl(objectUrl);\n }, ignore);\n\n return () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n } else {\n task?.abort({\n code: PdfErrorCode.Cancelled,\n message: 'canceled render task',\n });\n }\n };\n }, [thumbs, meta.pageIndex, refreshTick]);\n\n const handleImageLoad = () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n };\n\n return url ? <img src={url} onLoad={handleImageLoad} style={style} {...props} /> : null;\n}\n"],"names":["window"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../src/shared/hooks/use-thumbnail.ts","../../src/shared/components/thumbnails-pane.tsx","../../src/shared/components/thumbnail-img.tsx"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/@framework';\nimport { ThumbnailPlugin } from '@embedpdf/plugin-thumbnail';\n\nexport const useThumbnailPlugin = () => usePlugin<ThumbnailPlugin>(ThumbnailPlugin.id);\nexport const useThumbnailCapability = () => useCapability<ThumbnailPlugin>(ThumbnailPlugin.id);\n","import { useEffect, useRef, useState, HTMLAttributes, CSSProperties, ReactNode } from '@framework';\nimport { ThumbMeta, WindowState } from '@embedpdf/plugin-thumbnail';\nimport { useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n /** @deprecated use scrollToThumb via capability or rely on autoScroll */\n selectedPage?: number;\n /** @deprecated behavior is now controlled by ThumbnailPluginConfig.scrollBehavior */\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({ style, scrollOptions, selectedPage, ...props }: ThumbnailsProps) {\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const viewportRef = useRef<HTMLDivElement>(null);\n\n const [window, setWindow] = useState<WindowState | null>(null);\n\n // 1) subscribe once to window updates\n useEffect(() => thumbnailPlugin?.onWindow(setWindow), [thumbnailPlugin]);\n\n // 2) keep plugin in sync while the user scrolls\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp) return;\n const onScroll = () => thumbnailPlugin?.updateWindow(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbnailPlugin]);\n\n // 2.5) keep plugin in sync when viewport resizes (e.g., menu opens/closes)\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin) return;\n\n const resizeObserver = new ResizeObserver(() => {\n thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);\n });\n resizeObserver.observe(vp);\n\n return () => resizeObserver.disconnect();\n }, [thumbnailPlugin]);\n\n // 3) kick-start after document change\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin) return;\n\n // push initial metrics\n thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);\n }, [window, thumbnailPlugin]);\n\n // 4) let plugin drive scroll – only after window is set, and only once\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin || !window) return;\n\n return thumbnailPlugin.onScrollTo(({ top, behavior }) => {\n vp.scrollTo({ top, behavior });\n });\n }, [thumbnailPlugin, !!window]); // Note: !!window to prevent re-subscription on window updates\n\n const paddingY = thumbnailPlugin?.cfg.paddingY ?? 0;\n\n return (\n <div\n ref={viewportRef}\n style={{\n overflowY: 'auto',\n position: 'relative',\n paddingTop: paddingY,\n paddingBottom: paddingY,\n height: '100%',\n ...style,\n }}\n {...props}\n >\n <div style={{ height: window?.totalHeight ?? 0, position: 'relative' }}>\n {window?.items.map((m) => props.children(m))}\n </div>\n </div>\n );\n}\n","import { useEffect, useState, useRef, HTMLAttributes, CSSProperties, Fragment } from '@framework';\nimport { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\nimport { useThumbnailCapability, useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailImgProps = Omit<HTMLAttributes<HTMLImageElement>, 'style'> & {\n style?: CSSProperties;\n meta: ThumbMeta;\n};\n\nexport function ThumbImg({ meta, style, ...props }: ThumbnailImgProps) {\n const { provides: thumbs } = useThumbnailCapability();\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const [url, setUrl] = useState<string>();\n const urlRef = useRef<string | null>(null);\n const [refreshTick, setRefreshTick] = useState(0);\n\n useEffect(() => {\n if (!thumbnailPlugin) return;\n return thumbnailPlugin.onRefreshPages((pages) => {\n if (pages.includes(meta.pageIndex)) {\n setRefreshTick((tick) => tick + 1);\n }\n });\n }, [thumbnailPlugin]);\n\n useEffect(() => {\n const task = thumbs?.renderThumb(meta.pageIndex, window.devicePixelRatio);\n task?.wait((blob) => {\n const objectUrl = URL.createObjectURL(blob);\n urlRef.current = objectUrl;\n setUrl(objectUrl);\n }, ignore);\n\n return () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n } else {\n task?.abort({\n code: PdfErrorCode.Cancelled,\n message: 'canceled render task',\n });\n }\n };\n }, [thumbs, meta.pageIndex, refreshTick]);\n\n const handleImageLoad = () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n };\n\n return url ? <img src={url} onLoad={handleImageLoad} style={style} {...props} /> : null;\n}\n"],"names":["window"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@embedpdf/core/react"),t=require("@embedpdf/plugin-thumbnail"),r=require("react/jsx-runtime"),n=require("react"),l=require("@embedpdf/models"),o=()=>e.usePlugin(t.ThumbnailPlugin.id),u=()=>e.useCapability(t.ThumbnailPlugin.id);exports.ThumbImg=function({meta:e,style:t,...i}){const{provides:s}=u(),{plugin:c}=o(),[a,d]=n.useState(),p=n.useRef(null),[f,b]=n.useState(0);return n.useEffect((()=>{if(c)return c.onRefreshPages((t=>{t.includes(e.pageIndex)&&b((e=>e+1))}))}),[c]),n.useEffect((()=>{const t=null==s?void 0:s.renderThumb(e.pageIndex,window.devicePixelRatio);return null==t||t.wait((e=>{const t=URL.createObjectURL(e);p.current=t,d(t)}),l.ignore),()=>{p.current?(URL.revokeObjectURL(p.current),p.current=null):null==t||t.abort({code:l.PdfErrorCode.Cancelled,message:"canceled render task"})}}),[s,e.pageIndex,f]),a?r.jsx("img",{src:a,onLoad:()=>{p.current&&(URL.revokeObjectURL(p.current),p.current=null)},style:t,...i}):null},exports.ThumbnailsPane=function({style:e,scrollOptions:t,selectedPage:l,...u}){const{plugin:i}=o(),s=n.useRef(null),[c,a]=n.useState(null);return n.useEffect((()=>null==i?void 0:i.onWindow(a)),[i]),n.useEffect((()=>{const e=s.current;if(!e)return;const t=()=>null==i?void 0:i.updateWindow(e.scrollTop,e.clientHeight);return e.addEventListener("scroll",t),()=>e.removeEventListener("scroll",t)}),[i]),n.useEffect((()=>{const e=s.current;e&&i&&i.updateWindow(e.scrollTop,e.clientHeight)}),[c,i]),n.useEffect((()=>{const e=s.current;if(e&&i&&c)return i.onScrollTo((({top:t,behavior:r})=>{e.scrollTo({top:t,behavior:r})}))}),[i,!!c]),r.jsx("div",{ref:s,style:{overflowY:"auto",position:"relative",...e},...u,children:r.jsx("div",{style:{height:(null==c?void 0:c.totalHeight)??0,position:"relative"},children:null==c?void 0:c.items.map((e=>u.children(e)))})})},exports.useThumbnailCapability=u,exports.useThumbnailPlugin=o,Object.keys(t).forEach((e=>{"default"===e||Object.prototype.hasOwnProperty.call(exports,e)||Object.defineProperty(exports,e,{enumerable:!0,get:()=>t[e]})}));
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@embedpdf/core/react"),t=require("@embedpdf/plugin-thumbnail"),r=require("react/jsx-runtime"),n=require("react"),o=require("@embedpdf/models"),l=()=>e.usePlugin(t.ThumbnailPlugin.id),i=()=>e.useCapability(t.ThumbnailPlugin.id);exports.ThumbImg=function({meta:e,style:t,...u}){const{provides:s}=i(),{plugin:c}=l(),[d,a]=n.useState(),p=n.useRef(null),[f,g]=n.useState(0);return n.useEffect((()=>{if(c)return c.onRefreshPages((t=>{t.includes(e.pageIndex)&&g((e=>e+1))}))}),[c]),n.useEffect((()=>{const t=null==s?void 0:s.renderThumb(e.pageIndex,window.devicePixelRatio);return null==t||t.wait((e=>{const t=URL.createObjectURL(e);p.current=t,a(t)}),o.ignore),()=>{p.current?(URL.revokeObjectURL(p.current),p.current=null):null==t||t.abort({code:o.PdfErrorCode.Cancelled,message:"canceled render task"})}}),[s,e.pageIndex,f]),d?r.jsx("img",{src:d,onLoad:()=>{p.current&&(URL.revokeObjectURL(p.current),p.current=null)},style:t,...u}):null},exports.ThumbnailsPane=function({style:e,scrollOptions:t,selectedPage:o,...i}){const{plugin:u}=l(),s=n.useRef(null),[c,d]=n.useState(null);n.useEffect((()=>null==u?void 0:u.onWindow(d)),[u]),n.useEffect((()=>{const e=s.current;if(!e)return;const t=()=>null==u?void 0:u.updateWindow(e.scrollTop,e.clientHeight);return e.addEventListener("scroll",t),()=>e.removeEventListener("scroll",t)}),[u]),n.useEffect((()=>{const e=s.current;if(!e||!u)return;const t=new ResizeObserver((()=>{u.updateWindow(e.scrollTop,e.clientHeight)}));return t.observe(e),()=>t.disconnect()}),[u]),n.useEffect((()=>{const e=s.current;e&&u&&u.updateWindow(e.scrollTop,e.clientHeight)}),[c,u]),n.useEffect((()=>{const e=s.current;if(e&&u&&c)return u.onScrollTo((({top:t,behavior:r})=>{e.scrollTo({top:t,behavior:r})}))}),[u,!!c]);const a=(null==u?void 0:u.cfg.paddingY)??0;return r.jsx("div",{ref:s,style:{overflowY:"auto",position:"relative",paddingTop:a,paddingBottom:a,height:"100%",...e},...i,children:r.jsx("div",{style:{height:(null==c?void 0:c.totalHeight)??0,position:"relative"},children:null==c?void 0:c.items.map((e=>i.children(e)))})})},exports.useThumbnailCapability=i,exports.useThumbnailPlugin=l,Object.keys(t).forEach((e=>{"default"===e||Object.prototype.hasOwnProperty.call(exports,e)||Object.defineProperty(exports,e,{enumerable:!0,get:()=>t[e]})}));
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../src/shared/hooks/use-thumbnail.ts","../../src/shared/components/thumbnail-img.tsx","../../src/shared/components/thumbnails-pane.tsx"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/@framework';\nimport { ThumbnailPlugin } from '@embedpdf/plugin-thumbnail';\n\nexport const useThumbnailPlugin = () => usePlugin<ThumbnailPlugin>(ThumbnailPlugin.id);\nexport const useThumbnailCapability = () => useCapability<ThumbnailPlugin>(ThumbnailPlugin.id);\n","import { useEffect, useState, useRef, HTMLAttributes, CSSProperties, Fragment } from '@framework';\nimport { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\nimport { useThumbnailCapability, useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailImgProps = Omit<HTMLAttributes<HTMLImageElement>, 'style'> & {\n style?: CSSProperties;\n meta: ThumbMeta;\n};\n\nexport function ThumbImg({ meta, style, ...props }: ThumbnailImgProps) {\n const { provides: thumbs } = useThumbnailCapability();\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const [url, setUrl] = useState<string>();\n const urlRef = useRef<string | null>(null);\n const [refreshTick, setRefreshTick] = useState(0);\n\n useEffect(() => {\n if (!thumbnailPlugin) return;\n return thumbnailPlugin.onRefreshPages((pages) => {\n if (pages.includes(meta.pageIndex)) {\n setRefreshTick((tick) => tick + 1);\n }\n });\n }, [thumbnailPlugin]);\n\n useEffect(() => {\n const task = thumbs?.renderThumb(meta.pageIndex, window.devicePixelRatio);\n task?.wait((blob) => {\n const objectUrl = URL.createObjectURL(blob);\n urlRef.current = objectUrl;\n setUrl(objectUrl);\n }, ignore);\n\n return () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n } else {\n task?.abort({\n code: PdfErrorCode.Cancelled,\n message: 'canceled render task',\n });\n }\n };\n }, [thumbs, meta.pageIndex, refreshTick]);\n\n const handleImageLoad = () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n };\n\n return url ? <img src={url} onLoad={handleImageLoad} style={style} {...props} /> : null;\n}\n","import { useEffect, useRef, useState, HTMLAttributes, CSSProperties, ReactNode } from '@framework';\nimport { ThumbMeta, WindowState } from '@embedpdf/plugin-thumbnail';\nimport { useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n /** @deprecated use scrollToThumb via capability or rely on autoScroll */\n selectedPage?: number;\n /** @deprecated behavior is now controlled by ThumbnailPluginConfig.scrollBehavior */\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({ style, scrollOptions, selectedPage, ...props }: ThumbnailsProps) {\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const viewportRef = useRef<HTMLDivElement>(null);\n\n const [window, setWindow] = useState<WindowState | null>(null);\n\n // 1) subscribe once to window updates\n useEffect(() => thumbnailPlugin?.onWindow(setWindow), [thumbnailPlugin]);\n\n // 2) keep plugin in sync while the user scrolls\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp) return;\n const onScroll = () => thumbnailPlugin?.updateWindow(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbnailPlugin]);\n\n // 3) kick-start after document change\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin) return;\n\n // push initial metrics\n thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);\n }, [window, thumbnailPlugin]);\n\n // 4) let plugin drive scroll – only after window is set, and only once\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin || !window) return;\n\n return thumbnailPlugin.onScrollTo(({ top, behavior }) => {\n vp.scrollTo({ top, behavior });\n });\n }, [thumbnailPlugin, !!window]); // Note: !!window to prevent re-subscription on window updates\n\n return (\n <div ref={viewportRef} style={{ overflowY: 'auto', position: 'relative', ...style }} {...props}>\n <div style={{ height: window?.totalHeight ?? 0, position: 'relative' }}>\n {window?.items.map((m) => props.children(m))}\n </div>\n </div>\n );\n}\n"],"names":["useThumbnailPlugin","usePlugin","ThumbnailPlugin","id","useThumbnailCapability","useCapability","meta","style","props","provides","thumbs","plugin","thumbnailPlugin","url","setUrl","useState","urlRef","useRef","refreshTick","setRefreshTick","useEffect","onRefreshPages","pages","includes","pageIndex","tick","task","renderThumb","window","devicePixelRatio","wait","blob","objectUrl","URL","createObjectURL","current","ignore","revokeObjectURL","abort","code","PdfErrorCode","Cancelled","message","jsx","src","onLoad","scrollOptions","selectedPage","viewportRef","setWindow","onWindow","vp","onScroll","updateWindow","scrollTop","clientHeight","addEventListener","removeEventListener","onScrollTo","top","behavior","scrollTo","ref","overflowY","position","children","height","totalHeight","items","map","m"],"mappings":"gPAGaA,EAAqB,IAAMC,YAA2BC,EAAAA,gBAAgBC,IACtEC,EAAyB,IAAMC,gBAA+BH,EAAAA,gBAAgBC,qBCMpF,UAAkBG,KAAEA,EAAAC,MAAMA,KAAUC,IACzC,MAAQC,SAAUC,GAAWN,KACrBO,OAAQC,GAAoBZ,KAC7Ba,EAAKC,GAAUC,aAChBC,EAASC,SAAsB,OAC9BC,EAAaC,GAAkBJ,EAAAA,SAAS,GAuCxC,OArCPK,EAAAA,WAAU,KACR,GAAKR,EACE,OAAAA,EAAgBS,gBAAgBC,IACjCA,EAAMC,SAASjB,EAAKkB,YACPL,GAACM,GAASA,EAAO,GAAC,GAEpC,GACA,CAACb,IAEJQ,EAAAA,WAAU,KACR,MAAMM,EAAO,MAAAhB,OAAA,EAAAA,EAAQiB,YAAYrB,EAAKkB,UAAWI,OAAOC,kBAOxD,OANM,MAAAH,GAAAA,EAAAI,MAAMC,IACJ,MAAAC,EAAYC,IAAIC,gBAAgBH,GACtCf,EAAOmB,QAAUH,EACjBlB,EAAOkB,EAAS,GACfI,UAEI,KACDpB,EAAOmB,SACLF,IAAAI,gBAAgBrB,EAAOmB,SAC3BnB,EAAOmB,QAAU,MAEjB,MAAAT,GAAAA,EAAMY,MAAM,CACVC,KAAMC,EAAaA,aAAAC,UACnBC,QAAS,wBACV,CAEL,GACC,CAAChC,EAAQJ,EAAKkB,UAAWN,IASrBL,EAAO8B,EAAAA,IAAA,MAAA,CAAIC,IAAK/B,EAAKgC,OAPJ,KAClB7B,EAAOmB,UACLF,IAAAI,gBAAgBrB,EAAOmB,SAC3BnB,EAAOmB,QAAU,KAAA,EAIgC5B,WAAkBC,IAAY,IACrF,yBC1CO,UAAwBD,MAAEA,EAAAuC,cAAOA,eAAeC,KAAiBvC,IACtE,MAAQG,OAAQC,GAAoBZ,IAC9BgD,EAAc/B,SAAuB,OAEpCW,EAAQqB,GAAalC,EAAAA,SAA6B,aAGzDK,EAAAA,WAAU,IAAM,MAAAR,OAAA,EAAAA,EAAiBsC,SAASD,IAAY,CAACrC,IAGvDQ,EAAAA,WAAU,KACR,MAAM+B,EAAKH,EAAYb,QACvB,IAAKgB,EAAI,OACT,MAAMC,EAAW,IAAM,MAAAxC,OAAA,EAAAA,EAAiByC,aAAaF,EAAGG,UAAWH,EAAGI,cAEtE,OADGJ,EAAAK,iBAAiB,SAAUJ,GACvB,IAAMD,EAAGM,oBAAoB,SAAUL,EAAQ,GACrD,CAACxC,IAGJQ,EAAAA,WAAU,KACR,MAAM+B,EAAKH,EAAYb,QAClBgB,GAAOvC,GAGZA,EAAgByC,aAAaF,EAAGG,UAAWH,EAAGI,aAAY,GACzD,CAAC3B,EAAQhB,IAGZQ,EAAAA,WAAU,KACR,MAAM+B,EAAKH,EAAYb,QACvB,GAAKgB,GAAOvC,GAAoBgB,EAEhC,OAAOhB,EAAgB8C,YAAW,EAAGC,MAAKC,eACxCT,EAAGU,SAAS,CAAEF,MAAKC,YAAU,GAC9B,GACA,CAAChD,IAAmBgB,UAGpB,MAAI,CAAAkC,IAAKd,EAAazC,MAAO,CAAEwD,UAAW,OAAQC,SAAU,cAAezD,MAAaC,EACvFyD,eAAC,MAAI,CAAA1D,MAAO,CAAE2D,QAAQ,MAAAtC,SAAAA,EAAQuC,cAAe,EAAGH,SAAU,YACvDC,SAAQ,MAAArC,OAAA,EAAAA,EAAAwC,MAAMC,KAAKC,GAAM9D,EAAMyD,SAASK,QAIjD"}
1
+ {"version":3,"file":"index.cjs","sources":["../../src/shared/hooks/use-thumbnail.ts","../../src/shared/components/thumbnail-img.tsx","../../src/shared/components/thumbnails-pane.tsx"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/@framework';\nimport { ThumbnailPlugin } from '@embedpdf/plugin-thumbnail';\n\nexport const useThumbnailPlugin = () => usePlugin<ThumbnailPlugin>(ThumbnailPlugin.id);\nexport const useThumbnailCapability = () => useCapability<ThumbnailPlugin>(ThumbnailPlugin.id);\n","import { useEffect, useState, useRef, HTMLAttributes, CSSProperties, Fragment } from '@framework';\nimport { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\nimport { useThumbnailCapability, useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailImgProps = Omit<HTMLAttributes<HTMLImageElement>, 'style'> & {\n style?: CSSProperties;\n meta: ThumbMeta;\n};\n\nexport function ThumbImg({ meta, style, ...props }: ThumbnailImgProps) {\n const { provides: thumbs } = useThumbnailCapability();\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const [url, setUrl] = useState<string>();\n const urlRef = useRef<string | null>(null);\n const [refreshTick, setRefreshTick] = useState(0);\n\n useEffect(() => {\n if (!thumbnailPlugin) return;\n return thumbnailPlugin.onRefreshPages((pages) => {\n if (pages.includes(meta.pageIndex)) {\n setRefreshTick((tick) => tick + 1);\n }\n });\n }, [thumbnailPlugin]);\n\n useEffect(() => {\n const task = thumbs?.renderThumb(meta.pageIndex, window.devicePixelRatio);\n task?.wait((blob) => {\n const objectUrl = URL.createObjectURL(blob);\n urlRef.current = objectUrl;\n setUrl(objectUrl);\n }, ignore);\n\n return () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n } else {\n task?.abort({\n code: PdfErrorCode.Cancelled,\n message: 'canceled render task',\n });\n }\n };\n }, [thumbs, meta.pageIndex, refreshTick]);\n\n const handleImageLoad = () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n };\n\n return url ? <img src={url} onLoad={handleImageLoad} style={style} {...props} /> : null;\n}\n","import { useEffect, useRef, useState, HTMLAttributes, CSSProperties, ReactNode } from '@framework';\nimport { ThumbMeta, WindowState } from '@embedpdf/plugin-thumbnail';\nimport { useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n /** @deprecated use scrollToThumb via capability or rely on autoScroll */\n selectedPage?: number;\n /** @deprecated behavior is now controlled by ThumbnailPluginConfig.scrollBehavior */\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({ style, scrollOptions, selectedPage, ...props }: ThumbnailsProps) {\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const viewportRef = useRef<HTMLDivElement>(null);\n\n const [window, setWindow] = useState<WindowState | null>(null);\n\n // 1) subscribe once to window updates\n useEffect(() => thumbnailPlugin?.onWindow(setWindow), [thumbnailPlugin]);\n\n // 2) keep plugin in sync while the user scrolls\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp) return;\n const onScroll = () => thumbnailPlugin?.updateWindow(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbnailPlugin]);\n\n // 2.5) keep plugin in sync when viewport resizes (e.g., menu opens/closes)\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin) return;\n\n const resizeObserver = new ResizeObserver(() => {\n thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);\n });\n resizeObserver.observe(vp);\n\n return () => resizeObserver.disconnect();\n }, [thumbnailPlugin]);\n\n // 3) kick-start after document change\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin) return;\n\n // push initial metrics\n thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);\n }, [window, thumbnailPlugin]);\n\n // 4) let plugin drive scroll – only after window is set, and only once\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin || !window) return;\n\n return thumbnailPlugin.onScrollTo(({ top, behavior }) => {\n vp.scrollTo({ top, behavior });\n });\n }, [thumbnailPlugin, !!window]); // Note: !!window to prevent re-subscription on window updates\n\n const paddingY = thumbnailPlugin?.cfg.paddingY ?? 0;\n\n return (\n <div\n ref={viewportRef}\n style={{\n overflowY: 'auto',\n position: 'relative',\n paddingTop: paddingY,\n paddingBottom: paddingY,\n height: '100%',\n ...style,\n }}\n {...props}\n >\n <div style={{ height: window?.totalHeight ?? 0, position: 'relative' }}>\n {window?.items.map((m) => props.children(m))}\n </div>\n </div>\n );\n}\n"],"names":["useThumbnailPlugin","usePlugin","ThumbnailPlugin","id","useThumbnailCapability","useCapability","meta","style","props","provides","thumbs","plugin","thumbnailPlugin","url","setUrl","useState","urlRef","useRef","refreshTick","setRefreshTick","useEffect","onRefreshPages","pages","includes","pageIndex","tick","task","renderThumb","window","devicePixelRatio","wait","blob","objectUrl","URL","createObjectURL","current","ignore","revokeObjectURL","abort","code","PdfErrorCode","Cancelled","message","jsx","src","onLoad","scrollOptions","selectedPage","viewportRef","setWindow","onWindow","vp","onScroll","updateWindow","scrollTop","clientHeight","addEventListener","removeEventListener","resizeObserver","ResizeObserver","observe","disconnect","onScrollTo","top","behavior","scrollTo","paddingY","cfg","jsxRuntime","ref","overflowY","position","paddingTop","paddingBottom","height","children","totalHeight","items","map","m"],"mappings":"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"}
@@ -18,6 +18,15 @@ function ThumbnailsPane({ style, scrollOptions, selectedPage, ...props }) {
18
18
  vp.addEventListener("scroll", onScroll);
19
19
  return () => vp.removeEventListener("scroll", onScroll);
20
20
  }, [thumbnailPlugin]);
21
+ useEffect(() => {
22
+ const vp = viewportRef.current;
23
+ if (!vp || !thumbnailPlugin) return;
24
+ const resizeObserver = new ResizeObserver(() => {
25
+ thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);
26
+ });
27
+ resizeObserver.observe(vp);
28
+ return () => resizeObserver.disconnect();
29
+ }, [thumbnailPlugin]);
21
30
  useEffect(() => {
22
31
  const vp = viewportRef.current;
23
32
  if (!vp || !thumbnailPlugin) return;
@@ -30,7 +39,23 @@ function ThumbnailsPane({ style, scrollOptions, selectedPage, ...props }) {
30
39
  vp.scrollTo({ top, behavior });
31
40
  });
32
41
  }, [thumbnailPlugin, !!window2]);
33
- return /* @__PURE__ */ jsx("div", { ref: viewportRef, style: { overflowY: "auto", position: "relative", ...style }, ...props, children: /* @__PURE__ */ jsx("div", { style: { height: (window2 == null ? void 0 : window2.totalHeight) ?? 0, position: "relative" }, children: window2 == null ? void 0 : window2.items.map((m) => props.children(m)) }) });
42
+ const paddingY = (thumbnailPlugin == null ? void 0 : thumbnailPlugin.cfg.paddingY) ?? 0;
43
+ return /* @__PURE__ */ jsx(
44
+ "div",
45
+ {
46
+ ref: viewportRef,
47
+ style: {
48
+ overflowY: "auto",
49
+ position: "relative",
50
+ paddingTop: paddingY,
51
+ paddingBottom: paddingY,
52
+ height: "100%",
53
+ ...style
54
+ },
55
+ ...props,
56
+ children: /* @__PURE__ */ jsx("div", { style: { height: (window2 == null ? void 0 : window2.totalHeight) ?? 0, position: "relative" }, children: window2 == null ? void 0 : window2.items.map((m) => props.children(m)) })
57
+ }
58
+ );
34
59
  }
35
60
  function ThumbImg({ meta, style, ...props }) {
36
61
  const { provides: thumbs } = useThumbnailCapability();
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/shared/hooks/use-thumbnail.ts","../../src/shared/components/thumbnails-pane.tsx","../../src/shared/components/thumbnail-img.tsx"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/@framework';\nimport { ThumbnailPlugin } from '@embedpdf/plugin-thumbnail';\n\nexport const useThumbnailPlugin = () => usePlugin<ThumbnailPlugin>(ThumbnailPlugin.id);\nexport const useThumbnailCapability = () => useCapability<ThumbnailPlugin>(ThumbnailPlugin.id);\n","import { useEffect, useRef, useState, HTMLAttributes, CSSProperties, ReactNode } from '@framework';\nimport { ThumbMeta, WindowState } from '@embedpdf/plugin-thumbnail';\nimport { useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n /** @deprecated use scrollToThumb via capability or rely on autoScroll */\n selectedPage?: number;\n /** @deprecated behavior is now controlled by ThumbnailPluginConfig.scrollBehavior */\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({ style, scrollOptions, selectedPage, ...props }: ThumbnailsProps) {\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const viewportRef = useRef<HTMLDivElement>(null);\n\n const [window, setWindow] = useState<WindowState | null>(null);\n\n // 1) subscribe once to window updates\n useEffect(() => thumbnailPlugin?.onWindow(setWindow), [thumbnailPlugin]);\n\n // 2) keep plugin in sync while the user scrolls\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp) return;\n const onScroll = () => thumbnailPlugin?.updateWindow(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbnailPlugin]);\n\n // 3) kick-start after document change\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin) return;\n\n // push initial metrics\n thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);\n }, [window, thumbnailPlugin]);\n\n // 4) let plugin drive scroll – only after window is set, and only once\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin || !window) return;\n\n return thumbnailPlugin.onScrollTo(({ top, behavior }) => {\n vp.scrollTo({ top, behavior });\n });\n }, [thumbnailPlugin, !!window]); // Note: !!window to prevent re-subscription on window updates\n\n return (\n <div ref={viewportRef} style={{ overflowY: 'auto', position: 'relative', ...style }} {...props}>\n <div style={{ height: window?.totalHeight ?? 0, position: 'relative' }}>\n {window?.items.map((m) => props.children(m))}\n </div>\n </div>\n );\n}\n","import { useEffect, useState, useRef, HTMLAttributes, CSSProperties, Fragment } from '@framework';\nimport { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\nimport { useThumbnailCapability, useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailImgProps = Omit<HTMLAttributes<HTMLImageElement>, 'style'> & {\n style?: CSSProperties;\n meta: ThumbMeta;\n};\n\nexport function ThumbImg({ meta, style, ...props }: ThumbnailImgProps) {\n const { provides: thumbs } = useThumbnailCapability();\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const [url, setUrl] = useState<string>();\n const urlRef = useRef<string | null>(null);\n const [refreshTick, setRefreshTick] = useState(0);\n\n useEffect(() => {\n if (!thumbnailPlugin) return;\n return thumbnailPlugin.onRefreshPages((pages) => {\n if (pages.includes(meta.pageIndex)) {\n setRefreshTick((tick) => tick + 1);\n }\n });\n }, [thumbnailPlugin]);\n\n useEffect(() => {\n const task = thumbs?.renderThumb(meta.pageIndex, window.devicePixelRatio);\n task?.wait((blob) => {\n const objectUrl = URL.createObjectURL(blob);\n urlRef.current = objectUrl;\n setUrl(objectUrl);\n }, ignore);\n\n return () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n } else {\n task?.abort({\n code: PdfErrorCode.Cancelled,\n message: 'canceled render task',\n });\n }\n };\n }, [thumbs, meta.pageIndex, refreshTick]);\n\n const handleImageLoad = () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n };\n\n return url ? <img src={url} onLoad={handleImageLoad} style={style} {...props} /> : null;\n}\n"],"names":["window"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../src/shared/hooks/use-thumbnail.ts","../../src/shared/components/thumbnails-pane.tsx","../../src/shared/components/thumbnail-img.tsx"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/@framework';\nimport { ThumbnailPlugin } from '@embedpdf/plugin-thumbnail';\n\nexport const useThumbnailPlugin = () => usePlugin<ThumbnailPlugin>(ThumbnailPlugin.id);\nexport const useThumbnailCapability = () => useCapability<ThumbnailPlugin>(ThumbnailPlugin.id);\n","import { useEffect, useRef, useState, HTMLAttributes, CSSProperties, ReactNode } from '@framework';\nimport { ThumbMeta, WindowState } from '@embedpdf/plugin-thumbnail';\nimport { useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n /** @deprecated use scrollToThumb via capability or rely on autoScroll */\n selectedPage?: number;\n /** @deprecated behavior is now controlled by ThumbnailPluginConfig.scrollBehavior */\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({ style, scrollOptions, selectedPage, ...props }: ThumbnailsProps) {\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const viewportRef = useRef<HTMLDivElement>(null);\n\n const [window, setWindow] = useState<WindowState | null>(null);\n\n // 1) subscribe once to window updates\n useEffect(() => thumbnailPlugin?.onWindow(setWindow), [thumbnailPlugin]);\n\n // 2) keep plugin in sync while the user scrolls\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp) return;\n const onScroll = () => thumbnailPlugin?.updateWindow(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbnailPlugin]);\n\n // 2.5) keep plugin in sync when viewport resizes (e.g., menu opens/closes)\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin) return;\n\n const resizeObserver = new ResizeObserver(() => {\n thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);\n });\n resizeObserver.observe(vp);\n\n return () => resizeObserver.disconnect();\n }, [thumbnailPlugin]);\n\n // 3) kick-start after document change\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin) return;\n\n // push initial metrics\n thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);\n }, [window, thumbnailPlugin]);\n\n // 4) let plugin drive scroll – only after window is set, and only once\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin || !window) return;\n\n return thumbnailPlugin.onScrollTo(({ top, behavior }) => {\n vp.scrollTo({ top, behavior });\n });\n }, [thumbnailPlugin, !!window]); // Note: !!window to prevent re-subscription on window updates\n\n const paddingY = thumbnailPlugin?.cfg.paddingY ?? 0;\n\n return (\n <div\n ref={viewportRef}\n style={{\n overflowY: 'auto',\n position: 'relative',\n paddingTop: paddingY,\n paddingBottom: paddingY,\n height: '100%',\n ...style,\n }}\n {...props}\n >\n <div style={{ height: window?.totalHeight ?? 0, position: 'relative' }}>\n {window?.items.map((m) => props.children(m))}\n </div>\n </div>\n );\n}\n","import { useEffect, useState, useRef, HTMLAttributes, CSSProperties, Fragment } from '@framework';\nimport { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\nimport { useThumbnailCapability, useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailImgProps = Omit<HTMLAttributes<HTMLImageElement>, 'style'> & {\n style?: CSSProperties;\n meta: ThumbMeta;\n};\n\nexport function ThumbImg({ meta, style, ...props }: ThumbnailImgProps) {\n const { provides: thumbs } = useThumbnailCapability();\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const [url, setUrl] = useState<string>();\n const urlRef = useRef<string | null>(null);\n const [refreshTick, setRefreshTick] = useState(0);\n\n useEffect(() => {\n if (!thumbnailPlugin) return;\n return thumbnailPlugin.onRefreshPages((pages) => {\n if (pages.includes(meta.pageIndex)) {\n setRefreshTick((tick) => tick + 1);\n }\n });\n }, [thumbnailPlugin]);\n\n useEffect(() => {\n const task = thumbs?.renderThumb(meta.pageIndex, window.devicePixelRatio);\n task?.wait((blob) => {\n const objectUrl = URL.createObjectURL(blob);\n urlRef.current = objectUrl;\n setUrl(objectUrl);\n }, ignore);\n\n return () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n } else {\n task?.abort({\n code: PdfErrorCode.Cancelled,\n message: 'canceled render task',\n });\n }\n };\n }, [thumbs, meta.pageIndex, refreshTick]);\n\n const handleImageLoad = () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n };\n\n return url ? <img src={url} onLoad={handleImageLoad} style={style} {...props} /> : null;\n}\n"],"names":["window"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@embedpdf/core/vue"),l=require("@embedpdf/plugin-thumbnail"),n=require("vue"),t=require("@embedpdf/models"),o=()=>e.usePlugin(l.ThumbnailPlugin.id),u=()=>e.useCapability(l.ThumbnailPlugin.id),r=n.defineComponent({__name:"thumbnails-pane",setup(e){const l=n.useAttrs(),{plugin:t}=o(),u=n.ref(null),r=n.ref(null);let a=null,i=null;return n.watchEffect((e=>{t.value&&(null==a||a(),a=t.value.onWindow((e=>r.value=e)),e((()=>null==a?void 0:a())))})),n.onMounted((()=>{const e=u.value;if(!e||!t.value)return;const l=()=>t.value.updateWindow(e.scrollTop,e.clientHeight);e.addEventListener("scroll",l),t.value.updateWindow(e.scrollTop,e.clientHeight),n.onBeforeUnmount((()=>{e.removeEventListener("scroll",l)}))})),n.watchEffect((e=>{const l=u.value;l&&t.value&&r.value&&(i=t.value.onScrollTo((({top:e,behavior:t})=>{n.nextTick((()=>{l.scrollTo({top:e,behavior:t})}))})),e((()=>null==i?void 0:i())))})),n.onBeforeUnmount((()=>{null==a||a(),null==i||i()})),(e,t)=>{var o,a;return n.openBlock(),n.createElementBlock("div",n.mergeProps({ref_key:"viewportRef",ref:u,style:{overflowY:"auto",position:"relative"}},n.unref(l)),[n.createElementVNode("div",{style:n.normalizeStyle({height:((null==(o=r.value)?void 0:o.totalHeight)??0)+"px",position:"relative"})},[(n.openBlock(!0),n.createElementBlock(n.Fragment,null,n.renderList((null==(a=r.value)?void 0:a.items)??[],(l=>n.renderSlot(e.$slots,"default",{key:l.pageIndex,meta:l}))),128))],4)],16)}}}),a=["src"],i=n.defineComponent({__name:"thumbnail-img",props:{meta:{}},setup(e){const l=e,r=n.useAttrs(),{provides:i}=u(),{plugin:s}=o(),c=n.ref(null);let d=null;const p=n.ref(0);let m=null;function v(){d&&(URL.revokeObjectURL(d),d=null)}n.watchEffect((e=>{s.value&&(null==m||m(),m=s.value.onRefreshPages((e=>{e.includes(l.meta.pageIndex)&&p.value++})),e((()=>null==m?void 0:m())))}));let f=null;return n.watch((()=>[l.meta.pageIndex,p.value,!!i.value]),(()=>{null==f||f(),function(){if(!i.value)return;const e=i.value.renderThumb(l.meta.pageIndex,window.devicePixelRatio);f=()=>e.abort({code:t.PdfErrorCode.Cancelled,message:"canceled render task"}),e.wait((e=>{v();const l=URL.createObjectURL(e);d=l,c.value=l}),t.ignore)}()}),{immediate:!0}),n.onBeforeUnmount((()=>{null==f||f(),v()})),(e,l)=>c.value?(n.openBlock(),n.createElementBlock("img",n.mergeProps({key:0,src:c.value},n.unref(r),{onLoad:v}),null,16,a)):n.createCommentVNode("",!0)}});exports.ThumbImg=i,exports.ThumbnailsPane=r,exports.useThumbnailCapability=u,exports.useThumbnailPlugin=o,Object.keys(l).forEach((e=>{"default"===e||Object.prototype.hasOwnProperty.call(exports,e)||Object.defineProperty(exports,e,{enumerable:!0,get:()=>l[e]})}));
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@embedpdf/core/vue"),l=require("@embedpdf/plugin-thumbnail"),n=require("vue"),t=require("@embedpdf/models"),o=()=>e.usePlugin(l.ThumbnailPlugin.id),u=()=>e.useCapability(l.ThumbnailPlugin.id),r=n.defineComponent({__name:"thumbnails-pane",setup(e){const l=n.useAttrs(),{plugin:t}=o(),u=n.ref(null),r=n.ref(null);let a=null,i=null;return n.watchEffect((e=>{t.value&&(null==a||a(),a=t.value.onWindow((e=>r.value=e)),e((()=>null==a?void 0:a())))})),n.onMounted((()=>{const e=u.value;if(!e||!t.value)return;const l=()=>t.value.updateWindow(e.scrollTop,e.clientHeight);e.addEventListener("scroll",l);const o=new ResizeObserver((()=>{t.value.updateWindow(e.scrollTop,e.clientHeight)}));o.observe(e),t.value.updateWindow(e.scrollTop,e.clientHeight),n.onBeforeUnmount((()=>{e.removeEventListener("scroll",l),o.disconnect()}))})),n.watchEffect((e=>{const l=u.value;l&&t.value&&r.value&&(i=t.value.onScrollTo((({top:e,behavior:t})=>{n.nextTick((()=>{l.scrollTo({top:e,behavior:t})}))})),e((()=>null==i?void 0:i())))})),n.onBeforeUnmount((()=>{null==a||a(),null==i||i()})),(e,o)=>{var a,i,d,s,c,p;return n.openBlock(),n.createElementBlock("div",n.mergeProps({ref_key:"viewportRef",ref:u,style:{overflowY:"auto",position:"relative",paddingTop:((null==(i=null==(a=n.unref(t))?void 0:a.cfg)?void 0:i.paddingY)??0)+"px",paddingBottom:((null==(s=null==(d=n.unref(t))?void 0:d.cfg)?void 0:s.paddingY)??0)+"px",height:"100%"}},n.unref(l)),[n.createElementVNode("div",{style:n.normalizeStyle({height:((null==(c=r.value)?void 0:c.totalHeight)??0)+"px",position:"relative"})},[(n.openBlock(!0),n.createElementBlock(n.Fragment,null,n.renderList((null==(p=r.value)?void 0:p.items)??[],(l=>n.renderSlot(e.$slots,"default",{key:l.pageIndex,meta:l}))),128))],4)],16)}}}),a=["src"],i=n.defineComponent({__name:"thumbnail-img",props:{meta:{}},setup(e){const l=e,r=n.useAttrs(),{provides:i}=u(),{plugin:d}=o(),s=n.ref(null);let c=null;const p=n.ref(0);let v=null;function m(){c&&(URL.revokeObjectURL(c),c=null)}n.watchEffect((e=>{d.value&&(null==v||v(),v=d.value.onRefreshPages((e=>{e.includes(l.meta.pageIndex)&&p.value++})),e((()=>null==v?void 0:v())))}));let f=null;return n.watch((()=>[l.meta.pageIndex,p.value,!!i.value]),(()=>{null==f||f(),function(){if(!i.value)return;const e=i.value.renderThumb(l.meta.pageIndex,window.devicePixelRatio);f=()=>e.abort({code:t.PdfErrorCode.Cancelled,message:"canceled render task"}),e.wait((e=>{m();const l=URL.createObjectURL(e);c=l,s.value=l}),t.ignore)}()}),{immediate:!0}),n.onBeforeUnmount((()=>{null==f||f(),m()})),(e,l)=>s.value?(n.openBlock(),n.createElementBlock("img",n.mergeProps({key:0,src:s.value},n.unref(r),{onLoad:m}),null,16,a)):n.createCommentVNode("",!0)}});exports.ThumbImg=i,exports.ThumbnailsPane=r,exports.useThumbnailCapability=u,exports.useThumbnailPlugin=o,Object.keys(l).forEach((e=>{"default"===e||Object.prototype.hasOwnProperty.call(exports,e)||Object.defineProperty(exports,e,{enumerable:!0,get:()=>l[e]})}));
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../src/vue/hooks/use-thumbnail.ts","../../src/vue/components/thumbnails-pane.vue","../../src/vue/components/thumbnail-img.vue"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/vue';\nimport { ThumbnailPlugin } from '@embedpdf/plugin-thumbnail';\n\nexport const useThumbnailPlugin = () => usePlugin<ThumbnailPlugin>(ThumbnailPlugin.id);\nexport const useThumbnailCapability = () => useCapability<ThumbnailPlugin>(ThumbnailPlugin.id);\n","<script setup lang=\"ts\">\nimport { onMounted, onBeforeUnmount, ref, watchEffect, nextTick, useAttrs } from 'vue';\nimport { useThumbnailPlugin } from '../hooks';\nimport type { WindowState } from '@embedpdf/plugin-thumbnail';\n\nconst attrs = useAttrs();\n\nconst { plugin: thumbnailPlugin } = useThumbnailPlugin();\nconst viewportRef = ref<HTMLDivElement | null>(null);\nconst windowState = ref<WindowState | null>(null);\n\nlet offWindow: (() => void) | null = null;\nlet offScrollTo: (() => void) | null = null;\n\nwatchEffect((onCleanup) => {\n if (!thumbnailPlugin.value) return;\n offWindow?.();\n offWindow = thumbnailPlugin.value.onWindow((w) => (windowState.value = w));\n onCleanup(() => offWindow?.());\n});\n\n// Setup scroll listener on mount\nonMounted(() => {\n const vp = viewportRef.value;\n if (!vp || !thumbnailPlugin.value) return;\n\n const onScroll = () => thumbnailPlugin.value!.updateWindow(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n\n // initial push\n thumbnailPlugin.value.updateWindow(vp.scrollTop, vp.clientHeight);\n\n onBeforeUnmount(() => {\n vp.removeEventListener('scroll', onScroll);\n });\n});\n\n// Setup scrollTo subscription only after window is ready\nwatchEffect((onCleanup) => {\n const vp = viewportRef.value;\n if (!vp || !thumbnailPlugin.value || !windowState.value) return;\n\n offScrollTo = thumbnailPlugin.value.onScrollTo(({ top, behavior }) => {\n // Wait for Vue to finish rendering the content before scrolling\n nextTick(() => {\n vp.scrollTo({ top, behavior });\n });\n });\n\n onCleanup(() => offScrollTo?.());\n});\n\nonBeforeUnmount(() => {\n offWindow?.();\n offScrollTo?.();\n});\n</script>\n\n<template>\n <div ref=\"viewportRef\" :style=\"{ overflowY: 'auto', position: 'relative' }\" v-bind=\"attrs\">\n <div :style=\"{ height: (windowState?.totalHeight ?? 0) + 'px', position: 'relative' }\">\n <!-- ✅ Use a template v-for to render the default scoped slot -->\n <template v-for=\"m in windowState?.items ?? []\" :key=\"m.pageIndex\">\n <slot :meta=\"m\" />\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { onBeforeUnmount, ref, watch, watchEffect, useAttrs } from 'vue';\nimport { useThumbnailCapability, useThumbnailPlugin } from '../hooks';\nimport type { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\n\nconst props = defineProps<{ meta: ThumbMeta }>();\nconst attrs = useAttrs();\n\nconst { provides: thumbs } = useThumbnailCapability();\nconst { plugin: thumbnailPlugin } = useThumbnailPlugin();\n\nconst url = ref<string | null>(null);\nlet urlToRevoke: string | null = null;\nconst refreshTick = ref(0);\n\nlet offRefresh: (() => void) | null = null;\n\nwatchEffect((onCleanup) => {\n if (!thumbnailPlugin.value) return;\n offRefresh?.();\n offRefresh = thumbnailPlugin.value.onRefreshPages((pages) => {\n if (pages.includes(props.meta.pageIndex)) {\n refreshTick.value++;\n }\n });\n onCleanup(() => offRefresh?.());\n});\n\nfunction revoke() {\n if (urlToRevoke) {\n URL.revokeObjectURL(urlToRevoke);\n urlToRevoke = null;\n }\n}\n\nlet abortTask: (() => void) | null = null;\n\nfunction load() {\n if (!thumbs.value) return; // wait until capability exists\n\n const task = thumbs.value.renderThumb(props.meta.pageIndex, window.devicePixelRatio);\n abortTask = () =>\n task.abort({\n code: PdfErrorCode.Cancelled,\n message: 'canceled render task',\n });\n\n task.wait((blob) => {\n revoke();\n const objectUrl = URL.createObjectURL(blob);\n urlToRevoke = objectUrl;\n url.value = objectUrl;\n }, ignore);\n}\n\n/* 🔧 Re-run when:\n - page changes,\n - the plugin tells us to refresh,\n - OR the capability becomes available later.\n*/\nwatch(\n () => [props.meta.pageIndex, refreshTick.value, !!thumbs.value],\n () => {\n abortTask?.();\n load();\n },\n { immediate: true },\n);\n\nonBeforeUnmount(() => {\n abortTask?.();\n revoke();\n});\n</script>\n\n<template>\n <img v-if=\"url\" :src=\"url\" v-bind=\"attrs\" @load=\"revoke\" />\n</template>\n"],"names":["useThumbnailPlugin","usePlugin","ThumbnailPlugin","id","useThumbnailCapability","useCapability","attrs","useAttrs","plugin","thumbnailPlugin","viewportRef","ref","windowState","offWindow","offScrollTo","vue$1","watchEffect","onCleanup","value","onWindow","w","onMounted","vp","onScroll","updateWindow","scrollTop","clientHeight","addEventListener","onBeforeUnmount","removeEventListener","onScrollTo","top","behavior","nextTick","scrollTo","_openBlock","_createElementBlock","_mergeProps","style","overflowY","position","_unref","unref","_createElementVNode","_normalizeStyle","normalizeStyle","height","_a","totalHeight","createElementBlock","_Fragment","Fragment","_renderList","_b","items","m","_renderSlot","_ctx","$slots","key","pageIndex","meta","props","__props","provides","thumbs","url","urlToRevoke","refreshTick","offRefresh","revoke","URL","revokeObjectURL","onRefreshPages","pages","includes","abortTask","watch","task","renderThumb","window","devicePixelRatio","abort","code","PdfErrorCode","Cancelled","message","wait","blob","objectUrl","createObjectURL","ignore","load","immediate","src","onLoad","_hoisted_1"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../../src/vue/hooks/use-thumbnail.ts","../../src/vue/components/thumbnails-pane.vue","../../src/vue/components/thumbnail-img.vue"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/vue';\nimport { ThumbnailPlugin } from '@embedpdf/plugin-thumbnail';\n\nexport const useThumbnailPlugin = () => usePlugin<ThumbnailPlugin>(ThumbnailPlugin.id);\nexport const useThumbnailCapability = () => useCapability<ThumbnailPlugin>(ThumbnailPlugin.id);\n","<script setup lang=\"ts\">\nimport { onMounted, onBeforeUnmount, ref, watchEffect, nextTick, useAttrs } from 'vue';\nimport { useThumbnailPlugin } from '../hooks';\nimport type { WindowState } from '@embedpdf/plugin-thumbnail';\n\nconst attrs = useAttrs();\n\nconst { plugin: thumbnailPlugin } = useThumbnailPlugin();\nconst viewportRef = ref<HTMLDivElement | null>(null);\nconst windowState = ref<WindowState | null>(null);\n\nlet offWindow: (() => void) | null = null;\nlet offScrollTo: (() => void) | null = null;\n\nwatchEffect((onCleanup) => {\n if (!thumbnailPlugin.value) return;\n offWindow?.();\n offWindow = thumbnailPlugin.value.onWindow((w) => (windowState.value = w));\n onCleanup(() => offWindow?.());\n});\n\n// Setup scroll listener on mount\nonMounted(() => {\n const vp = viewportRef.value;\n if (!vp || !thumbnailPlugin.value) return;\n\n const onScroll = () => thumbnailPlugin.value!.updateWindow(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n\n // Setup resize observer for viewport changes\n const resizeObserver = new ResizeObserver(() => {\n thumbnailPlugin.value!.updateWindow(vp.scrollTop, vp.clientHeight);\n });\n resizeObserver.observe(vp);\n\n // initial push\n thumbnailPlugin.value.updateWindow(vp.scrollTop, vp.clientHeight);\n\n onBeforeUnmount(() => {\n vp.removeEventListener('scroll', onScroll);\n resizeObserver.disconnect();\n });\n});\n\n// Setup scrollTo subscription only after window is ready\nwatchEffect((onCleanup) => {\n const vp = viewportRef.value;\n if (!vp || !thumbnailPlugin.value || !windowState.value) return;\n\n offScrollTo = thumbnailPlugin.value.onScrollTo(({ top, behavior }) => {\n // Wait for Vue to finish rendering the content before scrolling\n nextTick(() => {\n vp.scrollTo({ top, behavior });\n });\n });\n\n onCleanup(() => offScrollTo?.());\n});\n\nonBeforeUnmount(() => {\n offWindow?.();\n offScrollTo?.();\n});\n</script>\n\n<template>\n <div\n ref=\"viewportRef\"\n :style=\"{\n overflowY: 'auto',\n position: 'relative',\n paddingTop: (thumbnailPlugin?.cfg?.paddingY ?? 0) + 'px',\n paddingBottom: (thumbnailPlugin?.cfg?.paddingY ?? 0) + 'px',\n height: '100%',\n }\"\n v-bind=\"attrs\"\n >\n <div :style=\"{ height: (windowState?.totalHeight ?? 0) + 'px', position: 'relative' }\">\n <!-- ✅ Use a template v-for to render the default scoped slot -->\n <template v-for=\"m in windowState?.items ?? []\" :key=\"m.pageIndex\">\n <slot :meta=\"m\" />\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { onBeforeUnmount, ref, watch, watchEffect, useAttrs } from 'vue';\nimport { useThumbnailCapability, useThumbnailPlugin } from '../hooks';\nimport type { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\n\nconst props = defineProps<{ meta: ThumbMeta }>();\nconst attrs = useAttrs();\n\nconst { provides: thumbs } = useThumbnailCapability();\nconst { plugin: thumbnailPlugin } = useThumbnailPlugin();\n\nconst url = ref<string | null>(null);\nlet urlToRevoke: string | null = null;\nconst refreshTick = ref(0);\n\nlet offRefresh: (() => void) | null = null;\n\nwatchEffect((onCleanup) => {\n if (!thumbnailPlugin.value) return;\n offRefresh?.();\n offRefresh = thumbnailPlugin.value.onRefreshPages((pages) => {\n if (pages.includes(props.meta.pageIndex)) {\n refreshTick.value++;\n }\n });\n onCleanup(() => offRefresh?.());\n});\n\nfunction revoke() {\n if (urlToRevoke) {\n URL.revokeObjectURL(urlToRevoke);\n urlToRevoke = null;\n }\n}\n\nlet abortTask: (() => void) | null = null;\n\nfunction load() {\n if (!thumbs.value) return; // wait until capability exists\n\n const task = thumbs.value.renderThumb(props.meta.pageIndex, window.devicePixelRatio);\n abortTask = () =>\n task.abort({\n code: PdfErrorCode.Cancelled,\n message: 'canceled render task',\n });\n\n task.wait((blob) => {\n revoke();\n const objectUrl = URL.createObjectURL(blob);\n urlToRevoke = objectUrl;\n url.value = objectUrl;\n }, ignore);\n}\n\n/* 🔧 Re-run when:\n - page changes,\n - the plugin tells us to refresh,\n - OR the capability becomes available later.\n*/\nwatch(\n () => [props.meta.pageIndex, refreshTick.value, !!thumbs.value],\n () => {\n abortTask?.();\n load();\n },\n { immediate: true },\n);\n\nonBeforeUnmount(() => {\n abortTask?.();\n revoke();\n});\n</script>\n\n<template>\n <img v-if=\"url\" :src=\"url\" v-bind=\"attrs\" @load=\"revoke\" />\n</template>\n"],"names":["useThumbnailPlugin","usePlugin","ThumbnailPlugin","id","useThumbnailCapability","useCapability","attrs","useAttrs","plugin","thumbnailPlugin","viewportRef","ref","windowState","offWindow","offScrollTo","vue$1","watchEffect","onCleanup","value","onWindow","w","onMounted","vp","onScroll","updateWindow","scrollTop","clientHeight","addEventListener","resizeObserver","ResizeObserver","observe","onBeforeUnmount","removeEventListener","disconnect","onScrollTo","top","behavior","nextTick","scrollTo","_openBlock","_createElementBlock","_mergeProps","style","_unref","_a","cfg","_b","paddingY","_c","_d","unref","_createElementVNode","_normalizeStyle","normalizeStyle","height","_e","totalHeight","position","createElementBlock","_Fragment","Fragment","_renderList","_f","items","m","_renderSlot","_ctx","$slots","key","pageIndex","meta","props","__props","provides","thumbs","url","urlToRevoke","refreshTick","offRefresh","revoke","URL","revokeObjectURL","onRefreshPages","pages","includes","abortTask","watch","task","renderThumb","window","devicePixelRatio","abort","code","PdfErrorCode","Cancelled","message","wait","blob","objectUrl","createObjectURL","ignore","load","immediate","src","onLoad","_hoisted_1"],"mappings":"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"}
package/dist/vue/index.js CHANGED
@@ -25,9 +25,14 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
25
25
  if (!vp || !thumbnailPlugin.value) return;
26
26
  const onScroll = () => thumbnailPlugin.value.updateWindow(vp.scrollTop, vp.clientHeight);
27
27
  vp.addEventListener("scroll", onScroll);
28
+ const resizeObserver = new ResizeObserver(() => {
29
+ thumbnailPlugin.value.updateWindow(vp.scrollTop, vp.clientHeight);
30
+ });
31
+ resizeObserver.observe(vp);
28
32
  thumbnailPlugin.value.updateWindow(vp.scrollTop, vp.clientHeight);
29
33
  onBeforeUnmount(() => {
30
34
  vp.removeEventListener("scroll", onScroll);
35
+ resizeObserver.disconnect();
31
36
  });
32
37
  });
33
38
  watchEffect((onCleanup) => {
@@ -45,16 +50,22 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
45
50
  offScrollTo == null ? void 0 : offScrollTo();
46
51
  });
47
52
  return (_ctx, _cache) => {
48
- var _a, _b;
53
+ var _a, _b, _c, _d, _e, _f;
49
54
  return openBlock(), createElementBlock("div", mergeProps({
50
55
  ref_key: "viewportRef",
51
56
  ref: viewportRef,
52
- style: { overflowY: "auto", position: "relative" }
57
+ style: {
58
+ overflowY: "auto",
59
+ position: "relative",
60
+ paddingTop: (((_b = (_a = unref(thumbnailPlugin)) == null ? void 0 : _a.cfg) == null ? void 0 : _b.paddingY) ?? 0) + "px",
61
+ paddingBottom: (((_d = (_c = unref(thumbnailPlugin)) == null ? void 0 : _c.cfg) == null ? void 0 : _d.paddingY) ?? 0) + "px",
62
+ height: "100%"
63
+ }
53
64
  }, unref(attrs)), [
54
65
  createElementVNode("div", {
55
- style: normalizeStyle({ height: (((_a = windowState.value) == null ? void 0 : _a.totalHeight) ?? 0) + "px", position: "relative" })
66
+ style: normalizeStyle({ height: (((_e = windowState.value) == null ? void 0 : _e.totalHeight) ?? 0) + "px", position: "relative" })
56
67
  }, [
57
- (openBlock(true), createElementBlock(Fragment, null, renderList(((_b = windowState.value) == null ? void 0 : _b.items) ?? [], (m) => {
68
+ (openBlock(true), createElementBlock(Fragment, null, renderList(((_f = windowState.value) == null ? void 0 : _f.items) ?? [], (m) => {
58
69
  return renderSlot(_ctx.$slots, "default", {
59
70
  key: m.pageIndex,
60
71
  meta: m
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/vue/hooks/use-thumbnail.ts","../../src/vue/components/thumbnails-pane.vue","../../src/vue/components/thumbnail-img.vue"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/vue';\nimport { ThumbnailPlugin } from '@embedpdf/plugin-thumbnail';\n\nexport const useThumbnailPlugin = () => usePlugin<ThumbnailPlugin>(ThumbnailPlugin.id);\nexport const useThumbnailCapability = () => useCapability<ThumbnailPlugin>(ThumbnailPlugin.id);\n","<script setup lang=\"ts\">\nimport { onMounted, onBeforeUnmount, ref, watchEffect, nextTick, useAttrs } from 'vue';\nimport { useThumbnailPlugin } from '../hooks';\nimport type { WindowState } from '@embedpdf/plugin-thumbnail';\n\nconst attrs = useAttrs();\n\nconst { plugin: thumbnailPlugin } = useThumbnailPlugin();\nconst viewportRef = ref<HTMLDivElement | null>(null);\nconst windowState = ref<WindowState | null>(null);\n\nlet offWindow: (() => void) | null = null;\nlet offScrollTo: (() => void) | null = null;\n\nwatchEffect((onCleanup) => {\n if (!thumbnailPlugin.value) return;\n offWindow?.();\n offWindow = thumbnailPlugin.value.onWindow((w) => (windowState.value = w));\n onCleanup(() => offWindow?.());\n});\n\n// Setup scroll listener on mount\nonMounted(() => {\n const vp = viewportRef.value;\n if (!vp || !thumbnailPlugin.value) return;\n\n const onScroll = () => thumbnailPlugin.value!.updateWindow(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n\n // initial push\n thumbnailPlugin.value.updateWindow(vp.scrollTop, vp.clientHeight);\n\n onBeforeUnmount(() => {\n vp.removeEventListener('scroll', onScroll);\n });\n});\n\n// Setup scrollTo subscription only after window is ready\nwatchEffect((onCleanup) => {\n const vp = viewportRef.value;\n if (!vp || !thumbnailPlugin.value || !windowState.value) return;\n\n offScrollTo = thumbnailPlugin.value.onScrollTo(({ top, behavior }) => {\n // Wait for Vue to finish rendering the content before scrolling\n nextTick(() => {\n vp.scrollTo({ top, behavior });\n });\n });\n\n onCleanup(() => offScrollTo?.());\n});\n\nonBeforeUnmount(() => {\n offWindow?.();\n offScrollTo?.();\n});\n</script>\n\n<template>\n <div ref=\"viewportRef\" :style=\"{ overflowY: 'auto', position: 'relative' }\" v-bind=\"attrs\">\n <div :style=\"{ height: (windowState?.totalHeight ?? 0) + 'px', position: 'relative' }\">\n <!-- ✅ Use a template v-for to render the default scoped slot -->\n <template v-for=\"m in windowState?.items ?? []\" :key=\"m.pageIndex\">\n <slot :meta=\"m\" />\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { onBeforeUnmount, ref, watch, watchEffect, useAttrs } from 'vue';\nimport { useThumbnailCapability, useThumbnailPlugin } from '../hooks';\nimport type { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\n\nconst props = defineProps<{ meta: ThumbMeta }>();\nconst attrs = useAttrs();\n\nconst { provides: thumbs } = useThumbnailCapability();\nconst { plugin: thumbnailPlugin } = useThumbnailPlugin();\n\nconst url = ref<string | null>(null);\nlet urlToRevoke: string | null = null;\nconst refreshTick = ref(0);\n\nlet offRefresh: (() => void) | null = null;\n\nwatchEffect((onCleanup) => {\n if (!thumbnailPlugin.value) return;\n offRefresh?.();\n offRefresh = thumbnailPlugin.value.onRefreshPages((pages) => {\n if (pages.includes(props.meta.pageIndex)) {\n refreshTick.value++;\n }\n });\n onCleanup(() => offRefresh?.());\n});\n\nfunction revoke() {\n if (urlToRevoke) {\n URL.revokeObjectURL(urlToRevoke);\n urlToRevoke = null;\n }\n}\n\nlet abortTask: (() => void) | null = null;\n\nfunction load() {\n if (!thumbs.value) return; // wait until capability exists\n\n const task = thumbs.value.renderThumb(props.meta.pageIndex, window.devicePixelRatio);\n abortTask = () =>\n task.abort({\n code: PdfErrorCode.Cancelled,\n message: 'canceled render task',\n });\n\n task.wait((blob) => {\n revoke();\n const objectUrl = URL.createObjectURL(blob);\n urlToRevoke = objectUrl;\n url.value = objectUrl;\n }, ignore);\n}\n\n/* 🔧 Re-run when:\n - page changes,\n - the plugin tells us to refresh,\n - OR the capability becomes available later.\n*/\nwatch(\n () => [props.meta.pageIndex, refreshTick.value, !!thumbs.value],\n () => {\n abortTask?.();\n load();\n },\n { immediate: true },\n);\n\nonBeforeUnmount(() => {\n abortTask?.();\n revoke();\n});\n</script>\n\n<template>\n <img v-if=\"url\" :src=\"url\" v-bind=\"attrs\" @load=\"revoke\" />\n</template>\n"],"names":["_openBlock","_createElementBlock","_mergeProps","_unref","_createElementVNode","_normalizeStyle","_Fragment","_renderList","_renderSlot"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../src/vue/hooks/use-thumbnail.ts","../../src/vue/components/thumbnails-pane.vue","../../src/vue/components/thumbnail-img.vue"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/vue';\nimport { ThumbnailPlugin } from '@embedpdf/plugin-thumbnail';\n\nexport const useThumbnailPlugin = () => usePlugin<ThumbnailPlugin>(ThumbnailPlugin.id);\nexport const useThumbnailCapability = () => useCapability<ThumbnailPlugin>(ThumbnailPlugin.id);\n","<script setup lang=\"ts\">\nimport { onMounted, onBeforeUnmount, ref, watchEffect, nextTick, useAttrs } from 'vue';\nimport { useThumbnailPlugin } from '../hooks';\nimport type { WindowState } from '@embedpdf/plugin-thumbnail';\n\nconst attrs = useAttrs();\n\nconst { plugin: thumbnailPlugin } = useThumbnailPlugin();\nconst viewportRef = ref<HTMLDivElement | null>(null);\nconst windowState = ref<WindowState | null>(null);\n\nlet offWindow: (() => void) | null = null;\nlet offScrollTo: (() => void) | null = null;\n\nwatchEffect((onCleanup) => {\n if (!thumbnailPlugin.value) return;\n offWindow?.();\n offWindow = thumbnailPlugin.value.onWindow((w) => (windowState.value = w));\n onCleanup(() => offWindow?.());\n});\n\n// Setup scroll listener on mount\nonMounted(() => {\n const vp = viewportRef.value;\n if (!vp || !thumbnailPlugin.value) return;\n\n const onScroll = () => thumbnailPlugin.value!.updateWindow(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n\n // Setup resize observer for viewport changes\n const resizeObserver = new ResizeObserver(() => {\n thumbnailPlugin.value!.updateWindow(vp.scrollTop, vp.clientHeight);\n });\n resizeObserver.observe(vp);\n\n // initial push\n thumbnailPlugin.value.updateWindow(vp.scrollTop, vp.clientHeight);\n\n onBeforeUnmount(() => {\n vp.removeEventListener('scroll', onScroll);\n resizeObserver.disconnect();\n });\n});\n\n// Setup scrollTo subscription only after window is ready\nwatchEffect((onCleanup) => {\n const vp = viewportRef.value;\n if (!vp || !thumbnailPlugin.value || !windowState.value) return;\n\n offScrollTo = thumbnailPlugin.value.onScrollTo(({ top, behavior }) => {\n // Wait for Vue to finish rendering the content before scrolling\n nextTick(() => {\n vp.scrollTo({ top, behavior });\n });\n });\n\n onCleanup(() => offScrollTo?.());\n});\n\nonBeforeUnmount(() => {\n offWindow?.();\n offScrollTo?.();\n});\n</script>\n\n<template>\n <div\n ref=\"viewportRef\"\n :style=\"{\n overflowY: 'auto',\n position: 'relative',\n paddingTop: (thumbnailPlugin?.cfg?.paddingY ?? 0) + 'px',\n paddingBottom: (thumbnailPlugin?.cfg?.paddingY ?? 0) + 'px',\n height: '100%',\n }\"\n v-bind=\"attrs\"\n >\n <div :style=\"{ height: (windowState?.totalHeight ?? 0) + 'px', position: 'relative' }\">\n <!-- ✅ Use a template v-for to render the default scoped slot -->\n <template v-for=\"m in windowState?.items ?? []\" :key=\"m.pageIndex\">\n <slot :meta=\"m\" />\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { onBeforeUnmount, ref, watch, watchEffect, useAttrs } from 'vue';\nimport { useThumbnailCapability, useThumbnailPlugin } from '../hooks';\nimport type { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\n\nconst props = defineProps<{ meta: ThumbMeta }>();\nconst attrs = useAttrs();\n\nconst { provides: thumbs } = useThumbnailCapability();\nconst { plugin: thumbnailPlugin } = useThumbnailPlugin();\n\nconst url = ref<string | null>(null);\nlet urlToRevoke: string | null = null;\nconst refreshTick = ref(0);\n\nlet offRefresh: (() => void) | null = null;\n\nwatchEffect((onCleanup) => {\n if (!thumbnailPlugin.value) return;\n offRefresh?.();\n offRefresh = thumbnailPlugin.value.onRefreshPages((pages) => {\n if (pages.includes(props.meta.pageIndex)) {\n refreshTick.value++;\n }\n });\n onCleanup(() => offRefresh?.());\n});\n\nfunction revoke() {\n if (urlToRevoke) {\n URL.revokeObjectURL(urlToRevoke);\n urlToRevoke = null;\n }\n}\n\nlet abortTask: (() => void) | null = null;\n\nfunction load() {\n if (!thumbs.value) return; // wait until capability exists\n\n const task = thumbs.value.renderThumb(props.meta.pageIndex, window.devicePixelRatio);\n abortTask = () =>\n task.abort({\n code: PdfErrorCode.Cancelled,\n message: 'canceled render task',\n });\n\n task.wait((blob) => {\n revoke();\n const objectUrl = URL.createObjectURL(blob);\n urlToRevoke = objectUrl;\n url.value = objectUrl;\n }, ignore);\n}\n\n/* 🔧 Re-run when:\n - page changes,\n - the plugin tells us to refresh,\n - OR the capability becomes available later.\n*/\nwatch(\n () => [props.meta.pageIndex, refreshTick.value, !!thumbs.value],\n () => {\n abortTask?.();\n load();\n },\n { immediate: true },\n);\n\nonBeforeUnmount(() => {\n abortTask?.();\n revoke();\n});\n</script>\n\n<template>\n <img v-if=\"url\" :src=\"url\" v-bind=\"attrs\" @load=\"revoke\" />\n</template>\n"],"names":["_openBlock","_createElementBlock","_mergeProps","_unref","_createElementVNode","_normalizeStyle","_Fragment","_renderList","_renderSlot"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embedpdf/plugin-thumbnail",
3
- "version": "1.3.5",
3
+ "version": "1.3.7",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.cjs",
@@ -29,23 +29,23 @@
29
29
  }
30
30
  },
31
31
  "dependencies": {
32
- "@embedpdf/models": "1.3.5"
32
+ "@embedpdf/models": "1.3.7"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/react": "^18.2.0",
36
36
  "typescript": "^5.0.0",
37
37
  "@embedpdf/build": "1.0.1",
38
- "@embedpdf/plugin-scroll": "1.3.5",
39
- "@embedpdf/core": "1.3.5",
40
- "@embedpdf/plugin-render": "1.3.5"
38
+ "@embedpdf/plugin-render": "1.3.7",
39
+ "@embedpdf/plugin-scroll": "1.3.7",
40
+ "@embedpdf/core": "1.3.7"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "react": ">=16.8.0",
44
44
  "react-dom": ">=16.8.0",
45
45
  "preact": "^10.26.4",
46
46
  "vue": ">=3.2.0",
47
- "@embedpdf/core": "1.3.5",
48
- "@embedpdf/plugin-render": "1.3.5"
47
+ "@embedpdf/core": "1.3.7",
48
+ "@embedpdf/plugin-render": "1.3.7"
49
49
  },
50
50
  "files": [
51
51
  "dist",