@embedpdf/plugin-thumbnail 1.0.16 → 1.0.18

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"),e=require("@embedpdf/models"),i="thumbnail",s={id:i,name:"Thumbnail Plugin",version:"1.0.0",provides:["thumbnail"],requires:["render"],optional:[],defaultConfig:{enabled:!0,width:150,gap:10,buffer:3,labelHeight:16}},h=class extends t.BasePlugin{constructor(e,i,s){super(e,i),this.cfg=s,this.thumbs=[],this.window=null,this.emitWindow=t.createBehaviorEmitter(),this.taskCache=new Map,this.renderCapability=this.registry.getPlugin("render").provides(),this.coreStore.onAction(t.SET_DOCUMENT,((t,e)=>{this.taskCache.clear(),this.setWindowState(e)}))}async initialize(){}setWindowState(t){const e=t.core;if(!e.document)return;const i=this.cfg.width??120,s=this.cfg.labelHeight??16,h=this.cfg.gap??8;let n=0;this.thumbs=e.document.pages.map((t=>{const e=t.size.height/t.size.width,r=Math.round(i*e),a=r+s,o={pageIndex:t.index,width:i,height:r,wrapperHeight:a,top:n,labelHeight:s};return n+=a+h,o})),this.window={start:-1,end:-1,items:[],totalHeight:n-h},this.emitWindow.emit(this.window)}buildCapability(){return{onWindow:this.emitWindow.on,setViewport:(t,e)=>this.updateWindow(t,e),renderThumb:(t,e)=>this.renderThumb(t,e)}}updateWindow(t,e){const i=this.cfg.buffer??3;let s=0,h=this.thumbs.length-1,n=0;for(;s<=h;){const e=s+h>>1,i=this.thumbs[e];i.top+i.wrapperHeight<t?s=e+1:(n=e,h=e-1)}let r=n;const a=t+e;for(;r+1<this.thumbs.length&&this.thumbs[r].top<a;)r++;r=Math.min(this.thumbs.length-1,r+i);const o=Math.max(0,n-i);this.window&&o===this.window.start&&r===this.window.end||(this.window={start:o,end:r,items:this.thumbs.slice(o,r+1),totalHeight:this.window.totalHeight},this.emitWindow.emit(this.window))}renderThumb(t,i){if(this.taskCache.has(t))return this.taskCache.get(t);const s=this.coreState.core.document.pages[t],h=(this.cfg.width??120)/s.size.width,n=this.renderCapability.renderPageRect({pageIndex:t,rect:{origin:{x:0,y:0},size:s.size},scaleFactor:h,dpr:i});return this.taskCache.set(t,n),n.wait(e.ignore,(()=>{this.taskCache.delete(t)})),n}};h.id="thumbnail";let n=h;const r={manifest:s,create:(t,e,s)=>new n(i,t,s),reducer:()=>{},initialState:{}};exports.THUMBNAIL_PLUGIN_ID=i,exports.ThumbnailPlugin=n,exports.ThumbnailPluginPackage=r,exports.manifest=s;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("@embedpdf/core"),e=require("@embedpdf/models"),i="thumbnail",s={id:i,name:"Thumbnail Plugin",version:"1.0.0",provides:["thumbnail"],requires:["render"],optional:[],defaultConfig:{enabled:!0,width:150,gap:10,buffer:3,labelHeight:16}},h=class extends t.BasePlugin{constructor(e,i,s){super(e,i),this.cfg=s,this.thumbs=[],this.window=null,this.emitWindow=t.createBehaviorEmitter(),this.refreshPages$=t.createEmitter(),this.taskCache=new Map,this.renderCapability=this.registry.getPlugin("render").provides(),this.coreStore.onAction(t.SET_DOCUMENT,((t,e)=>{this.taskCache.clear(),this.setWindowState(e)})),this.coreStore.onAction(t.REFRESH_PAGES,(t=>{this.refreshPages$.emit(t.payload);for(const e of t.payload)this.taskCache.delete(e)}))}async initialize(){}onRefreshPages(t){return this.refreshPages$.on(t)}setWindowState(t){const e=t.core;if(!e.document)return;const i=this.cfg.width??120,s=this.cfg.labelHeight??16,h=this.cfg.gap??8;let n=0;this.thumbs=e.document.pages.map((t=>{const e=t.size.height/t.size.width,r=Math.round(i*e),a=r+s,o={pageIndex:t.index,width:i,height:r,wrapperHeight:a,top:n,labelHeight:s};return n+=a+h,o})),this.window={start:-1,end:-1,items:[],totalHeight:n-h},this.emitWindow.emit(this.window)}buildCapability(){return{onWindow:this.emitWindow.on,setViewport:(t,e)=>this.updateWindow(t,e),renderThumb:(t,e)=>this.renderThumb(t,e)}}updateWindow(t,e){const i=this.cfg.buffer??3;let s=0,h=this.thumbs.length-1,n=0;for(;s<=h;){const e=s+h>>1,i=this.thumbs[e];i.top+i.wrapperHeight<t?s=e+1:(n=e,h=e-1)}let r=n;const a=t+e;for(;r+1<this.thumbs.length&&this.thumbs[r].top<a;)r++;r=Math.min(this.thumbs.length-1,r+i);const o=Math.max(0,n-i);this.window&&o===this.window.start&&r===this.window.end||(this.window={start:o,end:r,items:this.thumbs.slice(o,r+1),totalHeight:this.window.totalHeight},this.emitWindow.emit(this.window))}renderThumb(t,i){if(this.taskCache.has(t))return this.taskCache.get(t);const s=this.coreState.core.document.pages[t],h=(this.cfg.width??120)/s.size.width,n=this.renderCapability.renderPageRect({pageIndex:t,rect:{origin:{x:0,y:0},size:s.size},scaleFactor:h,dpr:i});return this.taskCache.set(t,n),n.wait(e.ignore,(()=>{this.taskCache.delete(t)})),n}};h.id="thumbnail";let n=h;const r={manifest:s,create:(t,e,s)=>new n(i,t,s),reducer:()=>{},initialState:{}};exports.THUMBNAIL_PLUGIN_ID=i,exports.ThumbnailPlugin=n,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: [],\n defaultConfig: {\n enabled: true,\n width: 150,\n gap: 10,\n buffer: 3,\n labelHeight: 16,\n },\n};\n","import {\n BasePlugin,\n CoreState,\n createBehaviorEmitter,\n PluginRegistry,\n SET_DOCUMENT,\n StoreState,\n} from '@embedpdf/core';\nimport { ThumbMeta, ThumbnailPluginConfig, WindowState } from './types';\nimport { ThumbnailCapability } from './types';\nimport { ignore, PdfErrorReason, Task } from '@embedpdf/models';\nimport { RenderCapability, RenderPlugin } from '@embedpdf/plugin-render';\n\nexport class ThumbnailPlugin extends BasePlugin<ThumbnailPluginConfig, ThumbnailCapability> {\n static readonly id = 'thumbnail' as const;\n\n private renderCapability: RenderCapability;\n private thumbs: ThumbMeta[] = [];\n private window: WindowState | null = null;\n private readonly emitWindow = createBehaviorEmitter<WindowState>();\n private readonly taskCache = new Map<number, Task<Blob, PdfErrorReason>>();\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\n this.coreStore.onAction(SET_DOCUMENT, (_action, state) => {\n this.taskCache.clear();\n this.setWindowState(state);\n });\n }\n\n /* ------------ init ------------------------------------------------ */\n async initialize(): Promise<void> {}\n\n private setWindowState(state: StoreState<CoreState>) {\n const core = state.core;\n\n if (!core.document) return;\n\n const W = this.cfg.width ?? 120;\n const L = this.cfg.labelHeight ?? 16; // label\n const GAP = this.cfg.gap ?? 8;\n\n let offset = 0;\n this.thumbs = core.document.pages.map((p) => {\n const ratio = p.size.height / p.size.width;\n const thumbH = Math.round(W * ratio);\n const wrapH = thumbH + L; // no GAP here\n\n const meta: ThumbMeta = {\n pageIndex: p.index,\n width: W,\n height: thumbH,\n wrapperHeight: wrapH,\n top: offset,\n labelHeight: L,\n };\n offset += wrapH + GAP; // GAP added *after* wrapper\n return meta;\n });\n\n this.window = {\n start: -1,\n end: -1,\n items: [],\n totalHeight: offset - GAP, // last item has no gap below\n };\n this.emitWindow.emit(this.window);\n }\n\n /* ------------ capability ----------------------------------------- */\n protected buildCapability(): ThumbnailCapability {\n return {\n onWindow: this.emitWindow.on,\n setViewport: (y, h) => this.updateWindow(y, h),\n renderThumb: (idx, dpr) => this.renderThumb(idx, dpr),\n };\n }\n\n /* ------------ windowing math ------------------------------------- */\n private updateWindow(scrollY: number, viewportH: number) {\n const BUF = this.cfg.buffer ?? 3;\n\n /* -------- find first visible wrapper ---------- */\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 /* ------------ 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 const scale = (this.cfg.width ?? 120) / page.size.width;\n\n const task = this.renderCapability.renderPageRect({\n pageIndex: idx,\n rect: { origin: { x: 0, y: 0 }, size: page.size },\n scaleFactor: scale,\n dpr,\n });\n\n this.taskCache.set(idx, task);\n\n task.wait(ignore, () => {\n this.taskCache.delete(idx);\n });\n\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, _engine, 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","_ThumbnailPlugin","BasePlugin","constructor","registry","cfg","super","this","thumbs","window","emitWindow","createBehaviorEmitter","taskCache","Map","renderCapability","getPlugin","coreStore","onAction","SET_DOCUMENT","_action","state","clear","setWindowState","initialize","core","document","W","L","GAP","offset","pages","map","p","ratio","size","height","thumbH","Math","round","wrapH","meta","pageIndex","index","wrapperHeight","top","start","end","items","totalHeight","emit","buildCapability","onWindow","on","setViewport","y","h","updateWindow","renderThumb","idx","dpr","scrollY","viewportH","BUF","low","high","length","first","mid","m","last","limit","min","max","slice","has","get","page","coreState","scale","task","renderPageRect","rect","origin","x","scaleFactor","set","wait","ignore","delete","ThumbnailPlugin","ThumbnailPluginPackage","create","_engine","config","reducer","initialState"],"mappings":"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"}
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: [],\n defaultConfig: {\n enabled: true,\n width: 150,\n gap: 10,\n buffer: 3,\n labelHeight: 16,\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 { ThumbMeta, ThumbnailPluginConfig, WindowState } from './types';\nimport { ThumbnailCapability } from './types';\nimport { ignore, PdfErrorReason, Task } from '@embedpdf/models';\nimport { RenderCapability, RenderPlugin } from '@embedpdf/plugin-render';\n\nexport class ThumbnailPlugin extends BasePlugin<ThumbnailPluginConfig, ThumbnailCapability> {\n static readonly id = 'thumbnail' as const;\n\n private renderCapability: RenderCapability;\n private thumbs: ThumbMeta[] = [];\n private window: WindowState | null = null;\n private readonly emitWindow = createBehaviorEmitter<WindowState>();\n private readonly refreshPages$ = createEmitter<number[]>();\n private readonly taskCache = new Map<number, Task<Blob, PdfErrorReason>>();\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\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\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 private setWindowState(state: StoreState<CoreState>) {\n const core = state.core;\n\n if (!core.document) return;\n\n const W = this.cfg.width ?? 120;\n const L = this.cfg.labelHeight ?? 16; // label\n const GAP = this.cfg.gap ?? 8;\n\n let offset = 0;\n this.thumbs = core.document.pages.map((p) => {\n const ratio = p.size.height / p.size.width;\n const thumbH = Math.round(W * ratio);\n const wrapH = thumbH + L; // no GAP here\n\n const meta: ThumbMeta = {\n pageIndex: p.index,\n width: W,\n height: thumbH,\n wrapperHeight: wrapH,\n top: offset,\n labelHeight: L,\n };\n offset += wrapH + GAP; // GAP added *after* wrapper\n return meta;\n });\n\n this.window = {\n start: -1,\n end: -1,\n items: [],\n totalHeight: offset - GAP, // last item has no gap below\n };\n this.emitWindow.emit(this.window);\n }\n\n /* ------------ capability ----------------------------------------- */\n protected buildCapability(): ThumbnailCapability {\n return {\n onWindow: this.emitWindow.on,\n setViewport: (y, h) => this.updateWindow(y, h),\n renderThumb: (idx, dpr) => this.renderThumb(idx, dpr),\n };\n }\n\n /* ------------ windowing math ------------------------------------- */\n private updateWindow(scrollY: number, viewportH: number) {\n const BUF = this.cfg.buffer ?? 3;\n\n /* -------- find first visible wrapper ---------- */\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 /* ------------ 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 const scale = (this.cfg.width ?? 120) / page.size.width;\n\n const task = this.renderCapability.renderPageRect({\n pageIndex: idx,\n rect: { origin: { x: 0, y: 0 }, size: page.size },\n scaleFactor: scale,\n dpr,\n });\n\n this.taskCache.set(idx, task);\n\n task.wait(ignore, () => {\n this.taskCache.delete(idx);\n });\n\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, _engine, 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","_ThumbnailPlugin","BasePlugin","constructor","registry","cfg","super","this","thumbs","window","emitWindow","createBehaviorEmitter","refreshPages$","createEmitter","taskCache","Map","renderCapability","getPlugin","coreStore","onAction","SET_DOCUMENT","_action","state","clear","setWindowState","REFRESH_PAGES","action","emit","payload","pageIdx","delete","initialize","onRefreshPages","fn","on","core","document","W","L","GAP","offset","pages","map","p","ratio","size","height","thumbH","Math","round","wrapH","meta","pageIndex","index","wrapperHeight","top","start","end","items","totalHeight","buildCapability","onWindow","setViewport","y","h","updateWindow","renderThumb","idx","dpr","scrollY","viewportH","BUF","low","high","length","first","mid","m","last","limit","min","max","slice","has","get","page","coreState","scale","task","renderPageRect","rect","origin","x","scaleFactor","set","wait","ignore","ThumbnailPlugin","ThumbnailPluginPackage","create","_engine","config","reducer","initialState"],"mappings":"gJAGaA,EAAsB,YAEtBC,EAAkD,CAC7DC,GAAIF,EACJG,KAAM,mBACNC,QAAS,QACTC,SAAU,CAAC,aACXC,SAAU,CAAC,UACXC,SAAU,GACVC,cAAe,CACbC,SAAS,EACTC,MAAO,IACPC,IAAK,GACLC,OAAQ,EACRC,YAAa,KCDJC,EAAN,cAA8BC,EAAAA,WAUnC,WAAAC,CACEd,EACAe,EACQC,GAERC,MAAMjB,EAAIe,GAFFG,KAAAF,IAAAA,EATVE,KAAQC,OAAsB,GAC9BD,KAAQE,OAA6B,KACpBF,KAAAG,WAAaC,0BACbJ,KAAAK,cAAgBC,kBAChBN,KAAAO,cAAgBC,IAS/BR,KAAKS,iBAAmBT,KAAKH,SAASa,UAAwB,UAAWzB,WAEzEe,KAAKW,UAAUC,SAASC,EAAcA,cAAA,CAACC,EAASC,KAC9Cf,KAAKO,UAAUS,QACfhB,KAAKiB,eAAeF,EAAK,IAG3Bf,KAAKW,UAAUC,SAASM,EAAeA,eAACC,IACjCnB,KAAAK,cAAce,KAAKD,EAAOE,SACpB,IAAA,MAAAC,KAAWH,EAAOE,QACtBrB,KAAAO,UAAUgB,OAAOD,EAAO,GAEhC,CAIH,gBAAME,GAA4B,CAE3B,cAAAC,CAAeC,GACb,OAAA1B,KAAKK,cAAcsB,GAAGD,EAAE,CAGzB,cAAAT,CAAeF,GACrB,MAAMa,EAAOb,EAAMa,KAEf,IAACA,EAAKC,SAAU,OAEd,MAAAC,EAAI9B,KAAKF,IAAIR,OAAS,IACtByC,EAAI/B,KAAKF,IAAIL,aAAe,GAC5BuC,EAAMhC,KAAKF,IAAIP,KAAO,EAE5B,IAAI0C,EAAS,EACbjC,KAAKC,OAAS2B,EAAKC,SAASK,MAAMC,KAAKC,IACrC,MAAMC,EAAQD,EAAEE,KAAKC,OAASH,EAAEE,KAAKhD,MAC/BkD,EAASC,KAAKC,MAAMZ,EAAIO,GACxBM,EAAQH,EAAST,EAEjBa,EAAkB,CACtBC,UAAWT,EAAEU,MACbxD,MAAOwC,EACPS,OAAQC,EACRO,cAAeJ,EACfK,IAAKf,EACLxC,YAAasC,GAGR,OADPE,GAAUU,EAAQX,EACXY,CAAA,IAGT5C,KAAKE,OAAS,CACZ+C,OAAO,EACPC,KAAK,EACLC,MAAO,GACPC,YAAanB,EAASD,GAEnBhC,KAAAG,WAAWiB,KAAKpB,KAAKE,OAAM,CAIxB,eAAAmD,GACD,MAAA,CACLC,SAAUtD,KAAKG,WAAWwB,GAC1B4B,YAAa,CAACC,EAAGC,IAAMzD,KAAK0D,aAAaF,EAAGC,GAC5CE,YAAa,CAACC,EAAKC,IAAQ7D,KAAK2D,YAAYC,EAAKC,GACnD,CAIM,YAAAH,CAAaI,EAAiBC,GAC9B,MAAAC,EAAMhE,KAAKF,IAAIN,QAAU,EAG/B,IAAIyE,EAAM,EACRC,EAAOlE,KAAKC,OAAOkE,OAAS,EAC5BC,EAAQ,EACV,KAAOH,GAAOC,GAAM,CACZ,MAAAG,EAAOJ,EAAMC,GAAS,EACtBI,EAAItE,KAAKC,OAAOoE,GAClBC,EAAEtB,IAAMsB,EAAEvB,cAAgBe,IAAeO,EAAM,GAEzCD,EAAAC,EACRH,EAAOG,EAAM,EACf,CAIF,IAAIE,EAAOH,EACX,MAAMI,EAAQV,EAAUC,EACjB,KAAAQ,EAAO,EAAIvE,KAAKC,OAAOkE,QAAUnE,KAAKC,OAAOsE,GAAMvB,IAAMwB,GAAOD,IACvEA,EAAO9B,KAAKgC,IAAIzE,KAAKC,OAAOkE,OAAS,EAAGI,EAAOP,GAE/C,MAAMf,EAAQR,KAAKiC,IAAI,EAAGN,EAAQJ,GAC9BhE,KAAKE,QAAU+C,IAAUjD,KAAKE,OAAO+C,OAASsB,IAASvE,KAAKE,OAAOgD,MAEvElD,KAAKE,OAAS,CACZ+C,QACAC,IAAKqB,EACLpB,MAAOnD,KAAKC,OAAO0E,MAAM1B,EAAOsB,EAAO,GACvCnB,YAAapD,KAAKE,OAAQkD,aAEvBpD,KAAAG,WAAWiB,KAAKpB,KAAKE,QAAM,CAI1B,WAAAyD,CAAYC,EAAaC,GAC3B,GAAA7D,KAAKO,UAAUqE,IAAIhB,GAAa,OAAA5D,KAAKO,UAAUsE,IAAIjB,GAEjDhC,MACAkD,EADO9E,KAAK+E,UAAUnD,KACVC,SAAUK,MAAM0B,GAC5BoB,GAAShF,KAAKF,IAAIR,OAAS,KAAOwF,EAAKxC,KAAKhD,MAE5C2F,EAAOjF,KAAKS,iBAAiByE,eAAe,CAChDrC,UAAWe,EACXuB,KAAM,CAAEC,OAAQ,CAAEC,EAAG,EAAG7B,EAAG,GAAKlB,KAAMwC,EAAKxC,MAC3CgD,YAAaN,EACbnB,QASK,OANF7D,KAAAO,UAAUgF,IAAI3B,EAAKqB,GAEnBA,EAAAO,KAAKC,EAAAA,QAAQ,KACXzF,KAAAO,UAAUgB,OAAOqC,EAAG,IAGpBqB,CAAA,GA5ITvF,EAAgBZ,GAAK,YADhB,IAAM4G,EAANhG,ECXA,MAAMiG,EAAgF,CAC3F9G,WACA+G,OAAQ,CAAC/F,EAAUgG,EAASC,IAAW,IAAIJ,EAAgB9G,EAAqBiB,EAAUiG,GAC1FC,QAAS,OACTC,aAAc,CAAA"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { BasePlugin, createBehaviorEmitter, SET_DOCUMENT } from "@embedpdf/core";
1
+ import { BasePlugin, createBehaviorEmitter, createEmitter, SET_DOCUMENT, REFRESH_PAGES } from "@embedpdf/core";
2
2
  import { ignore } from "@embedpdf/models";
3
3
  const THUMBNAIL_PLUGIN_ID = "thumbnail";
4
4
  const manifest = {
@@ -23,16 +23,26 @@ const _ThumbnailPlugin = class _ThumbnailPlugin extends BasePlugin {
23
23
  this.thumbs = [];
24
24
  this.window = null;
25
25
  this.emitWindow = createBehaviorEmitter();
26
+ this.refreshPages$ = createEmitter();
26
27
  this.taskCache = /* @__PURE__ */ new Map();
27
28
  this.renderCapability = this.registry.getPlugin("render").provides();
28
29
  this.coreStore.onAction(SET_DOCUMENT, (_action, state) => {
29
30
  this.taskCache.clear();
30
31
  this.setWindowState(state);
31
32
  });
33
+ this.coreStore.onAction(REFRESH_PAGES, (action) => {
34
+ this.refreshPages$.emit(action.payload);
35
+ for (const pageIdx of action.payload) {
36
+ this.taskCache.delete(pageIdx);
37
+ }
38
+ });
32
39
  }
33
40
  /* ------------ init ------------------------------------------------ */
34
41
  async initialize() {
35
42
  }
43
+ onRefreshPages(fn) {
44
+ return this.refreshPages$.on(fn);
45
+ }
36
46
  setWindowState(state) {
37
47
  const core = state.core;
38
48
  if (!core.document) return;
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: [],\n defaultConfig: {\n enabled: true,\n width: 150,\n gap: 10,\n buffer: 3,\n labelHeight: 16,\n },\n};\n","import {\n BasePlugin,\n CoreState,\n createBehaviorEmitter,\n PluginRegistry,\n SET_DOCUMENT,\n StoreState,\n} from '@embedpdf/core';\nimport { ThumbMeta, ThumbnailPluginConfig, WindowState } from './types';\nimport { ThumbnailCapability } from './types';\nimport { ignore, PdfErrorReason, Task } from '@embedpdf/models';\nimport { RenderCapability, RenderPlugin } from '@embedpdf/plugin-render';\n\nexport class ThumbnailPlugin extends BasePlugin<ThumbnailPluginConfig, ThumbnailCapability> {\n static readonly id = 'thumbnail' as const;\n\n private renderCapability: RenderCapability;\n private thumbs: ThumbMeta[] = [];\n private window: WindowState | null = null;\n private readonly emitWindow = createBehaviorEmitter<WindowState>();\n private readonly taskCache = new Map<number, Task<Blob, PdfErrorReason>>();\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\n this.coreStore.onAction(SET_DOCUMENT, (_action, state) => {\n this.taskCache.clear();\n this.setWindowState(state);\n });\n }\n\n /* ------------ init ------------------------------------------------ */\n async initialize(): Promise<void> {}\n\n private setWindowState(state: StoreState<CoreState>) {\n const core = state.core;\n\n if (!core.document) return;\n\n const W = this.cfg.width ?? 120;\n const L = this.cfg.labelHeight ?? 16; // label\n const GAP = this.cfg.gap ?? 8;\n\n let offset = 0;\n this.thumbs = core.document.pages.map((p) => {\n const ratio = p.size.height / p.size.width;\n const thumbH = Math.round(W * ratio);\n const wrapH = thumbH + L; // no GAP here\n\n const meta: ThumbMeta = {\n pageIndex: p.index,\n width: W,\n height: thumbH,\n wrapperHeight: wrapH,\n top: offset,\n labelHeight: L,\n };\n offset += wrapH + GAP; // GAP added *after* wrapper\n return meta;\n });\n\n this.window = {\n start: -1,\n end: -1,\n items: [],\n totalHeight: offset - GAP, // last item has no gap below\n };\n this.emitWindow.emit(this.window);\n }\n\n /* ------------ capability ----------------------------------------- */\n protected buildCapability(): ThumbnailCapability {\n return {\n onWindow: this.emitWindow.on,\n setViewport: (y, h) => this.updateWindow(y, h),\n renderThumb: (idx, dpr) => this.renderThumb(idx, dpr),\n };\n }\n\n /* ------------ windowing math ------------------------------------- */\n private updateWindow(scrollY: number, viewportH: number) {\n const BUF = this.cfg.buffer ?? 3;\n\n /* -------- find first visible wrapper ---------- */\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 /* ------------ 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 const scale = (this.cfg.width ?? 120) / page.size.width;\n\n const task = this.renderCapability.renderPageRect({\n pageIndex: idx,\n rect: { origin: { x: 0, y: 0 }, size: page.size },\n scaleFactor: scale,\n dpr,\n });\n\n this.taskCache.set(idx, task);\n\n task.wait(ignore, () => {\n this.taskCache.delete(idx);\n });\n\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, _engine, 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":[],"mappings":"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"}
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: [],\n defaultConfig: {\n enabled: true,\n width: 150,\n gap: 10,\n buffer: 3,\n labelHeight: 16,\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 { ThumbMeta, ThumbnailPluginConfig, WindowState } from './types';\nimport { ThumbnailCapability } from './types';\nimport { ignore, PdfErrorReason, Task } from '@embedpdf/models';\nimport { RenderCapability, RenderPlugin } from '@embedpdf/plugin-render';\n\nexport class ThumbnailPlugin extends BasePlugin<ThumbnailPluginConfig, ThumbnailCapability> {\n static readonly id = 'thumbnail' as const;\n\n private renderCapability: RenderCapability;\n private thumbs: ThumbMeta[] = [];\n private window: WindowState | null = null;\n private readonly emitWindow = createBehaviorEmitter<WindowState>();\n private readonly refreshPages$ = createEmitter<number[]>();\n private readonly taskCache = new Map<number, Task<Blob, PdfErrorReason>>();\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\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\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 private setWindowState(state: StoreState<CoreState>) {\n const core = state.core;\n\n if (!core.document) return;\n\n const W = this.cfg.width ?? 120;\n const L = this.cfg.labelHeight ?? 16; // label\n const GAP = this.cfg.gap ?? 8;\n\n let offset = 0;\n this.thumbs = core.document.pages.map((p) => {\n const ratio = p.size.height / p.size.width;\n const thumbH = Math.round(W * ratio);\n const wrapH = thumbH + L; // no GAP here\n\n const meta: ThumbMeta = {\n pageIndex: p.index,\n width: W,\n height: thumbH,\n wrapperHeight: wrapH,\n top: offset,\n labelHeight: L,\n };\n offset += wrapH + GAP; // GAP added *after* wrapper\n return meta;\n });\n\n this.window = {\n start: -1,\n end: -1,\n items: [],\n totalHeight: offset - GAP, // last item has no gap below\n };\n this.emitWindow.emit(this.window);\n }\n\n /* ------------ capability ----------------------------------------- */\n protected buildCapability(): ThumbnailCapability {\n return {\n onWindow: this.emitWindow.on,\n setViewport: (y, h) => this.updateWindow(y, h),\n renderThumb: (idx, dpr) => this.renderThumb(idx, dpr),\n };\n }\n\n /* ------------ windowing math ------------------------------------- */\n private updateWindow(scrollY: number, viewportH: number) {\n const BUF = this.cfg.buffer ?? 3;\n\n /* -------- find first visible wrapper ---------- */\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 /* ------------ 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 const scale = (this.cfg.width ?? 120) / page.size.width;\n\n const task = this.renderCapability.renderPageRect({\n pageIndex: idx,\n rect: { origin: { x: 0, y: 0 }, size: page.size },\n scaleFactor: scale,\n dpr,\n });\n\n this.taskCache.set(idx, task);\n\n task.wait(ignore, () => {\n this.taskCache.delete(idx);\n });\n\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, _engine, 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":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { BasePlugin, PluginRegistry } from '@embedpdf/core';
1
+ import { BasePlugin, PluginRegistry, Unsubscribe } from '@embedpdf/core';
2
2
  import { ThumbnailPluginConfig, ThumbnailCapability } from './types';
3
3
  export declare class ThumbnailPlugin extends BasePlugin<ThumbnailPluginConfig, ThumbnailCapability> {
4
4
  private cfg;
@@ -7,9 +7,11 @@ export declare class ThumbnailPlugin extends BasePlugin<ThumbnailPluginConfig, T
7
7
  private thumbs;
8
8
  private window;
9
9
  private readonly emitWindow;
10
+ private readonly refreshPages$;
10
11
  private readonly taskCache;
11
12
  constructor(id: string, registry: PluginRegistry, cfg: ThumbnailPluginConfig);
12
13
  initialize(): Promise<void>;
14
+ onRefreshPages(fn: (pages: number[]) => void): Unsubscribe;
13
15
  private setWindowState;
14
16
  protected buildCapability(): ThumbnailCapability;
15
17
  private updateWindow;
@@ -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"),i=()=>e.useCapability(t.ThumbnailPlugin.id);exports.ThumbImg=function({meta:e,style:t,...o}){const{provides:s}=i(),[u,c]=n.useState(),a=n.useRef(null);return 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);a.current=t,c(t)}),l.ignore),()=>{a.current?(URL.revokeObjectURL(a.current),a.current=null):null==t||t.abort({code:l.PdfErrorCode.Cancelled,message:"canceled render task"})}}),[e.pageIndex]),u?r.jsx("img",{src:u,onLoad:()=>{a.current&&(URL.revokeObjectURL(a.current),a.current=null)},style:t,...o}):null},exports.ThumbnailsPane=function({style:e,selectedPage:t,scrollOptions:l={behavior:"smooth",block:"nearest",inline:"nearest"},...o}){const{provides:s}=i(),u=n.useRef(null),[c,a]=n.useState(null);return n.useEffect((()=>null==s?void 0:s.onWindow(a)),[s]),n.useEffect((()=>{const e=u.current;if(!e)return;const t=()=>null==s?void 0:s.setViewport(e.scrollTop,e.clientHeight);return e.addEventListener("scroll",t),()=>e.removeEventListener("scroll",t)}),[s]),n.useEffect((()=>{const e=u.current;e&&s&&0===(null==c?void 0:c.items.length)&&s.setViewport(e.scrollTop,e.clientHeight)}),[c,s]),n.useEffect((()=>{if(!t||!c)return;const e=c.items.find((e=>e.pageIndex+1===t));if(!e)return;const r=u.current;if(!r)return;e.top<r.scrollTop+8?r.scrollTo({top:e.top,...l}):e.top+e.wrapperHeight+e.labelHeight>r.scrollTop+r.clientHeight-8&&r.scrollTo({top:e.top+e.wrapperHeight+e.labelHeight-r.clientHeight,...l})}),[t,c,l]),r.jsx("div",{ref:u,style:{overflowY:"auto",position:"relative",...e},...o,children:r.jsx("div",{style:{height:(null==c?void 0:c.totalHeight)??0,position:"relative"},children:null==c?void 0:c.items.map((e=>o.children(e)))})})},exports.useThumbnailCapability=i,exports.useThumbnailPlugin=()=>e.usePlugin(t.ThumbnailPlugin.id);
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"),i=()=>e.usePlugin(t.ThumbnailPlugin.id),o=()=>e.useCapability(t.ThumbnailPlugin.id);exports.ThumbImg=function({meta:e,style:t,...s}){const{provides:u}=o(),{plugin:c}=i(),[a,d]=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==u?void 0:u.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"})}}),[u,e.pageIndex,f]),a?r.jsx("img",{src:a,onLoad:()=>{p.current&&(URL.revokeObjectURL(p.current),p.current=null)},style:t,...s}):null},exports.ThumbnailsPane=function({style:e,selectedPage:t,scrollOptions:l={behavior:"smooth",block:"nearest",inline:"nearest"},...i}){const{provides:s}=o(),u=n.useRef(null),[c,a]=n.useState(null);return n.useEffect((()=>null==s?void 0:s.onWindow(a)),[s]),n.useEffect((()=>{const e=u.current;if(!e)return;const t=()=>null==s?void 0:s.setViewport(e.scrollTop,e.clientHeight);return e.addEventListener("scroll",t),()=>e.removeEventListener("scroll",t)}),[s]),n.useEffect((()=>{const e=u.current;e&&s&&0===(null==c?void 0:c.items.length)&&s.setViewport(e.scrollTop,e.clientHeight)}),[c,s]),n.useEffect((()=>{if(!t||!c)return;const e=c.items.find((e=>e.pageIndex+1===t));if(!e)return;const r=u.current;if(!r)return;e.top<r.scrollTop+8?r.scrollTo({top:e.top,...l}):e.top+e.wrapperHeight+e.labelHeight>r.scrollTop+r.clientHeight-8&&r.scrollTo({top:e.top+e.wrapperHeight+e.labelHeight-r.clientHeight,...l})}),[t,c,l]),r.jsx("div",{ref:u,style:{overflowY:"auto",position:"relative",...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=o,exports.useThumbnailPlugin=i;
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 } from '@framework';\nimport { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\nimport { useThumbnailCapability } 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 [url, setUrl] = useState<string>();\n const urlRef = useRef<string | null>(null);\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 }, [meta.pageIndex]);\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 { useThumbnailCapability } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n selectedPage?: number;\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({\n style,\n selectedPage,\n scrollOptions = { behavior: 'smooth', block: 'nearest', inline: 'nearest' },\n ...props\n}: ThumbnailsProps) {\n const { provides: thumbs } = useThumbnailCapability();\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(() => thumbs?.onWindow(setWindow), [thumbs]);\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 = () => thumbs?.setViewport(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbs]);\n\n /* 3️⃣ kick-start (or re-kick) after document change */\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbs) return;\n\n if (window?.items.length === 0) {\n thumbs.setViewport(vp.scrollTop, vp.clientHeight);\n }\n }, [window, thumbs]);\n\n /* 4️⃣ whenever selectedPage or window changes, ensure it’s visible */\n useEffect(() => {\n if (!selectedPage || !window) return;\n\n const item = window.items.find((it) => it.pageIndex + 1 === selectedPage);\n if (!item) return; // not in current window yet → wait for next update\n\n const vp = viewportRef.current;\n if (!vp) return;\n\n // Scroll only if the item is above or below the viewport “padding” zone\n const margin = 8; // px\n if (item.top < vp.scrollTop + margin) {\n vp.scrollTo({ top: item.top, ...scrollOptions });\n } else if (\n item.top + item.wrapperHeight + item.labelHeight >\n vp.scrollTop + vp.clientHeight - margin\n ) {\n vp.scrollTo({\n top: item.top + item.wrapperHeight + item.labelHeight - vp.clientHeight,\n ...scrollOptions,\n });\n }\n }, [selectedPage, window, scrollOptions]);\n\n return (\n <div ref={viewportRef} style={{ overflowY: 'auto', position: 'relative', ...style }} {...props}>\n {/* spacer keeps correct scroll height even before first window arrives */}\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":["useThumbnailCapability","useCapability","ThumbnailPlugin","id","meta","style","props","provides","thumbs","url","setUrl","useState","urlRef","useRef","useEffect","task","renderThumb","pageIndex","window","devicePixelRatio","wait","blob","objectUrl","URL","createObjectURL","current","ignore","revokeObjectURL","abort","code","PdfErrorCode","Cancelled","message","jsx","src","onLoad","selectedPage","scrollOptions","behavior","block","inline","viewportRef","setWindow","onWindow","vp","onScroll","setViewport","scrollTop","clientHeight","addEventListener","removeEventListener","items","length","item","find","it","top","scrollTo","wrapperHeight","labelHeight","ref","overflowY","position","children","height","totalHeight","map","m","usePlugin"],"mappings":"iRAIaA,EAAyB,IAAMC,gBAA+BC,EAAAA,gBAAgBC,qBCMpF,UAAkBC,KAAEA,EAAAC,MAAMA,KAAUC,IACzC,MAAQC,SAAUC,GAAWR,KACtBS,EAAKC,GAAUC,aAChBC,EAASC,SAAsB,MA8B9B,OA5BPC,EAAAA,WAAU,KACR,MAAMC,EAAO,MAAAP,OAAA,EAAAA,EAAQQ,YAAYZ,EAAKa,UAAWC,OAAOC,kBAOxD,OANM,MAAAJ,GAAAA,EAAAK,MAAMC,IACJ,MAAAC,EAAYC,IAAIC,gBAAgBH,GACtCT,EAAOa,QAAUH,EACjBZ,EAAOY,EAAS,GACfI,UAEI,KACDd,EAAOa,SACLF,IAAAI,gBAAgBf,EAAOa,SAC3Bb,EAAOa,QAAU,MAEjB,MAAAV,GAAAA,EAAMa,MAAM,CACVC,KAAMC,EAAaA,aAAAC,UACnBC,QAAS,wBACV,CAEL,GACC,CAAC5B,EAAKa,YASFR,EAAOwB,EAAAA,IAAA,MAAA,CAAIC,IAAKzB,EAAK0B,OAPJ,KAClBvB,EAAOa,UACLF,IAAAI,gBAAgBf,EAAOa,SAC3Bb,EAAOa,QAAU,KAAA,EAIgCpB,WAAkBC,IAAY,IACrF,yBCjCO,UAAwBD,MAC7BA,EAAA+B,aACAA,EAAAC,cACAA,EAAgB,CAAEC,SAAU,SAAUC,MAAO,UAAWC,OAAQ,cAC7DlC,IAEH,MAAQC,SAAUC,GAAWR,IACvByC,EAAc5B,SAAuB,OAEpCK,EAAQwB,GAAa/B,EAAAA,SAA6B,aAGzDG,EAAAA,WAAU,IAAM,MAAAN,OAAA,EAAAA,EAAQmC,SAASD,IAAY,CAAClC,IAG9CM,EAAAA,WAAU,KACR,MAAM8B,EAAKH,EAAYhB,QACvB,IAAKmB,EAAI,OACT,MAAMC,EAAW,IAAM,MAAArC,OAAA,EAAAA,EAAQsC,YAAYF,EAAGG,UAAWH,EAAGI,cAE5D,OADGJ,EAAAK,iBAAiB,SAAUJ,GACvB,IAAMD,EAAGM,oBAAoB,SAAUL,EAAQ,GACrD,CAACrC,IAGJM,EAAAA,WAAU,KACR,MAAM8B,EAAKH,EAAYhB,QAClBmB,GAAOpC,GAEiB,KAAzB,MAAAU,OAAA,EAAAA,EAAQiC,MAAMC,SAChB5C,EAAOsC,YAAYF,EAAGG,UAAWH,EAAGI,aAAY,GAEjD,CAAC9B,EAAQV,IAGZM,EAAAA,WAAU,KACJ,IAACsB,IAAiBlB,EAAQ,OAExB,MAAAmC,EAAOnC,EAAOiC,MAAMG,MAAMC,GAAOA,EAAGtC,UAAY,IAAMmB,IAC5D,IAAKiB,EAAM,OAEX,MAAMT,EAAKH,EAAYhB,QACvB,IAAKmB,EAAI,OAILS,EAAKG,IAAMZ,EAAGG,UADH,EAEbH,EAAGa,SAAS,CAAED,IAAKH,EAAKG,OAAQnB,IAEhCgB,EAAKG,IAAMH,EAAKK,cAAgBL,EAAKM,YACrCf,EAAGG,UAAYH,EAAGI,aALL,GAObJ,EAAGa,SAAS,CACVD,IAAKH,EAAKG,IAAMH,EAAKK,cAAgBL,EAAKM,YAAcf,EAAGI,gBACxDX,GACJ,GAEF,CAACD,EAAclB,EAAQmB,UAGvB,MAAI,CAAAuB,IAAKnB,EAAapC,MAAO,CAAEwD,UAAW,OAAQC,SAAU,cAAezD,MAAaC,EAEvFyD,eAAC,MAAI,CAAA1D,MAAO,CAAE2D,QAAQ,MAAA9C,SAAAA,EAAQ+C,cAAe,EAAGH,SAAU,YACvDC,SAAQ,MAAA7C,OAAA,EAAAA,EAAAiC,MAAMe,KAAKC,GAAM7D,EAAMyD,SAASI,QAIjD,8DF1EkC,IAAMC,YAA2BlE,EAAAA,gBAAgBC"}
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 { useThumbnailCapability } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n selectedPage?: number;\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({\n style,\n selectedPage,\n scrollOptions = { behavior: 'smooth', block: 'nearest', inline: 'nearest' },\n ...props\n}: ThumbnailsProps) {\n const { provides: thumbs } = useThumbnailCapability();\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(() => thumbs?.onWindow(setWindow), [thumbs]);\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 = () => thumbs?.setViewport(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbs]);\n\n /* 3️⃣ kick-start (or re-kick) after document change */\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbs) return;\n\n if (window?.items.length === 0) {\n thumbs.setViewport(vp.scrollTop, vp.clientHeight);\n }\n }, [window, thumbs]);\n\n /* 4️⃣ whenever selectedPage or window changes, ensure it’s visible */\n useEffect(() => {\n if (!selectedPage || !window) return;\n\n const item = window.items.find((it) => it.pageIndex + 1 === selectedPage);\n if (!item) return; // not in current window yet → wait for next update\n\n const vp = viewportRef.current;\n if (!vp) return;\n\n // Scroll only if the item is above or below the viewport “padding” zone\n const margin = 8; // px\n if (item.top < vp.scrollTop + margin) {\n vp.scrollTo({ top: item.top, ...scrollOptions });\n } else if (\n item.top + item.wrapperHeight + item.labelHeight >\n vp.scrollTop + vp.clientHeight - margin\n ) {\n vp.scrollTo({\n top: item.top + item.wrapperHeight + item.labelHeight - vp.clientHeight,\n ...scrollOptions,\n });\n }\n }, [selectedPage, window, scrollOptions]);\n\n return (\n <div ref={viewportRef} style={{ overflowY: 'auto', position: 'relative', ...style }} {...props}>\n {/* spacer keeps correct scroll height even before first window arrives */}\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","selectedPage","scrollOptions","behavior","block","inline","viewportRef","setWindow","onWindow","vp","onScroll","setViewport","scrollTop","clientHeight","addEventListener","removeEventListener","items","length","item","find","it","top","scrollTo","wrapperHeight","labelHeight","ref","overflowY","position","children","height","totalHeight","map","m"],"mappings":"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"}
@@ -50,8 +50,18 @@ function ThumbnailsPane({
50
50
  }
51
51
  function ThumbImg({ meta, style, ...props }) {
52
52
  const { provides: thumbs } = useThumbnailCapability();
53
+ const { plugin: thumbnailPlugin } = useThumbnailPlugin();
53
54
  const [url, setUrl] = useState();
54
55
  const urlRef = useRef(null);
56
+ const [refreshTick, setRefreshTick] = useState(0);
57
+ useEffect(() => {
58
+ if (!thumbnailPlugin) return;
59
+ return thumbnailPlugin.onRefreshPages((pages) => {
60
+ if (pages.includes(meta.pageIndex)) {
61
+ setRefreshTick((tick) => tick + 1);
62
+ }
63
+ });
64
+ }, [thumbnailPlugin]);
55
65
  useEffect(() => {
56
66
  const task = thumbs == null ? void 0 : thumbs.renderThumb(meta.pageIndex, window.devicePixelRatio);
57
67
  task == null ? void 0 : task.wait((blob) => {
@@ -70,7 +80,7 @@ function ThumbImg({ meta, style, ...props }) {
70
80
  });
71
81
  }
72
82
  };
73
- }, [meta.pageIndex]);
83
+ }, [thumbs, meta.pageIndex, refreshTick]);
74
84
  const handleImageLoad = () => {
75
85
  if (urlRef.current) {
76
86
  URL.revokeObjectURL(urlRef.current);
@@ -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 { useThumbnailCapability } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n selectedPage?: number;\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({\n style,\n selectedPage,\n scrollOptions = { behavior: 'smooth', block: 'nearest', inline: 'nearest' },\n ...props\n}: ThumbnailsProps) {\n const { provides: thumbs } = useThumbnailCapability();\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(() => thumbs?.onWindow(setWindow), [thumbs]);\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 = () => thumbs?.setViewport(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbs]);\n\n /* 3️⃣ kick-start (or re-kick) after document change */\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbs) return;\n\n if (window?.items.length === 0) {\n thumbs.setViewport(vp.scrollTop, vp.clientHeight);\n }\n }, [window, thumbs]);\n\n /* 4️⃣ whenever selectedPage or window changes, ensure it’s visible */\n useEffect(() => {\n if (!selectedPage || !window) return;\n\n const item = window.items.find((it) => it.pageIndex + 1 === selectedPage);\n if (!item) return; // not in current window yet → wait for next update\n\n const vp = viewportRef.current;\n if (!vp) return;\n\n // Scroll only if the item is above or below the viewport “padding” zone\n const margin = 8; // px\n if (item.top < vp.scrollTop + margin) {\n vp.scrollTo({ top: item.top, ...scrollOptions });\n } else if (\n item.top + item.wrapperHeight + item.labelHeight >\n vp.scrollTop + vp.clientHeight - margin\n ) {\n vp.scrollTo({\n top: item.top + item.wrapperHeight + item.labelHeight - vp.clientHeight,\n ...scrollOptions,\n });\n }\n }, [selectedPage, window, scrollOptions]);\n\n return (\n <div ref={viewportRef} style={{ overflowY: 'auto', position: 'relative', ...style }} {...props}>\n {/* spacer keeps correct scroll height even before first window arrives */}\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 } from '@framework';\nimport { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\nimport { useThumbnailCapability } 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 [url, setUrl] = useState<string>();\n const urlRef = useRef<string | null>(null);\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 }, [meta.pageIndex]);\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 { useThumbnailCapability } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n selectedPage?: number;\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({\n style,\n selectedPage,\n scrollOptions = { behavior: 'smooth', block: 'nearest', inline: 'nearest' },\n ...props\n}: ThumbnailsProps) {\n const { provides: thumbs } = useThumbnailCapability();\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(() => thumbs?.onWindow(setWindow), [thumbs]);\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 = () => thumbs?.setViewport(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbs]);\n\n /* 3️⃣ kick-start (or re-kick) after document change */\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbs) return;\n\n if (window?.items.length === 0) {\n thumbs.setViewport(vp.scrollTop, vp.clientHeight);\n }\n }, [window, thumbs]);\n\n /* 4️⃣ whenever selectedPage or window changes, ensure it’s visible */\n useEffect(() => {\n if (!selectedPage || !window) return;\n\n const item = window.items.find((it) => it.pageIndex + 1 === selectedPage);\n if (!item) return; // not in current window yet → wait for next update\n\n const vp = viewportRef.current;\n if (!vp) return;\n\n // Scroll only if the item is above or below the viewport “padding” zone\n const margin = 8; // px\n if (item.top < vp.scrollTop + margin) {\n vp.scrollTo({ top: item.top, ...scrollOptions });\n } else if (\n item.top + item.wrapperHeight + item.labelHeight >\n vp.scrollTop + vp.clientHeight - margin\n ) {\n vp.scrollTo({\n top: item.top + item.wrapperHeight + item.labelHeight - vp.clientHeight,\n ...scrollOptions,\n });\n }\n }, [selectedPage, window, scrollOptions]);\n\n return (\n <div ref={viewportRef} style={{ overflowY: 'auto', position: 'relative', ...style }} {...props}>\n {/* spacer keeps correct scroll height even before first window arrives */}\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"),i=()=>e.useCapability(t.ThumbnailPlugin.id);exports.ThumbImg=function({meta:e,style:t,...o}){const{provides:s}=i(),[u,c]=n.useState(),a=n.useRef(null);return 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);a.current=t,c(t)}),l.ignore),()=>{a.current?(URL.revokeObjectURL(a.current),a.current=null):null==t||t.abort({code:l.PdfErrorCode.Cancelled,message:"canceled render task"})}}),[e.pageIndex]),u?r.jsx("img",{src:u,onLoad:()=>{a.current&&(URL.revokeObjectURL(a.current),a.current=null)},style:t,...o}):null},exports.ThumbnailsPane=function({style:e,selectedPage:t,scrollOptions:l={behavior:"smooth",block:"nearest",inline:"nearest"},...o}){const{provides:s}=i(),u=n.useRef(null),[c,a]=n.useState(null);return n.useEffect((()=>null==s?void 0:s.onWindow(a)),[s]),n.useEffect((()=>{const e=u.current;if(!e)return;const t=()=>null==s?void 0:s.setViewport(e.scrollTop,e.clientHeight);return e.addEventListener("scroll",t),()=>e.removeEventListener("scroll",t)}),[s]),n.useEffect((()=>{const e=u.current;e&&s&&0===(null==c?void 0:c.items.length)&&s.setViewport(e.scrollTop,e.clientHeight)}),[c,s]),n.useEffect((()=>{if(!t||!c)return;const e=c.items.find((e=>e.pageIndex+1===t));if(!e)return;const r=u.current;if(!r)return;e.top<r.scrollTop+8?r.scrollTo({top:e.top,...l}):e.top+e.wrapperHeight+e.labelHeight>r.scrollTop+r.clientHeight-8&&r.scrollTo({top:e.top+e.wrapperHeight+e.labelHeight-r.clientHeight,...l})}),[t,c,l]),r.jsx("div",{ref:u,style:{overflowY:"auto",position:"relative",...e},...o,children:r.jsx("div",{style:{height:(null==c?void 0:c.totalHeight)??0,position:"relative"},children:null==c?void 0:c.items.map((e=>o.children(e)))})})},exports.useThumbnailCapability=i,exports.useThumbnailPlugin=()=>e.usePlugin(t.ThumbnailPlugin.id);
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"),i=()=>e.usePlugin(t.ThumbnailPlugin.id),o=()=>e.useCapability(t.ThumbnailPlugin.id);exports.ThumbImg=function({meta:e,style:t,...s}){const{provides:u}=o(),{plugin:c}=i(),[a,d]=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==u?void 0:u.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"})}}),[u,e.pageIndex,f]),a?r.jsx("img",{src:a,onLoad:()=>{p.current&&(URL.revokeObjectURL(p.current),p.current=null)},style:t,...s}):null},exports.ThumbnailsPane=function({style:e,selectedPage:t,scrollOptions:l={behavior:"smooth",block:"nearest",inline:"nearest"},...i}){const{provides:s}=o(),u=n.useRef(null),[c,a]=n.useState(null);return n.useEffect((()=>null==s?void 0:s.onWindow(a)),[s]),n.useEffect((()=>{const e=u.current;if(!e)return;const t=()=>null==s?void 0:s.setViewport(e.scrollTop,e.clientHeight);return e.addEventListener("scroll",t),()=>e.removeEventListener("scroll",t)}),[s]),n.useEffect((()=>{const e=u.current;e&&s&&0===(null==c?void 0:c.items.length)&&s.setViewport(e.scrollTop,e.clientHeight)}),[c,s]),n.useEffect((()=>{if(!t||!c)return;const e=c.items.find((e=>e.pageIndex+1===t));if(!e)return;const r=u.current;if(!r)return;e.top<r.scrollTop+8?r.scrollTo({top:e.top,...l}):e.top+e.wrapperHeight+e.labelHeight>r.scrollTop+r.clientHeight-8&&r.scrollTo({top:e.top+e.wrapperHeight+e.labelHeight-r.clientHeight,...l})}),[t,c,l]),r.jsx("div",{ref:u,style:{overflowY:"auto",position:"relative",...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=o,exports.useThumbnailPlugin=i;
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 } from '@framework';\nimport { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\nimport { useThumbnailCapability } 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 [url, setUrl] = useState<string>();\n const urlRef = useRef<string | null>(null);\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 }, [meta.pageIndex]);\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 { useThumbnailCapability } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n selectedPage?: number;\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({\n style,\n selectedPage,\n scrollOptions = { behavior: 'smooth', block: 'nearest', inline: 'nearest' },\n ...props\n}: ThumbnailsProps) {\n const { provides: thumbs } = useThumbnailCapability();\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(() => thumbs?.onWindow(setWindow), [thumbs]);\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 = () => thumbs?.setViewport(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbs]);\n\n /* 3️⃣ kick-start (or re-kick) after document change */\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbs) return;\n\n if (window?.items.length === 0) {\n thumbs.setViewport(vp.scrollTop, vp.clientHeight);\n }\n }, [window, thumbs]);\n\n /* 4️⃣ whenever selectedPage or window changes, ensure it’s visible */\n useEffect(() => {\n if (!selectedPage || !window) return;\n\n const item = window.items.find((it) => it.pageIndex + 1 === selectedPage);\n if (!item) return; // not in current window yet → wait for next update\n\n const vp = viewportRef.current;\n if (!vp) return;\n\n // Scroll only if the item is above or below the viewport “padding” zone\n const margin = 8; // px\n if (item.top < vp.scrollTop + margin) {\n vp.scrollTo({ top: item.top, ...scrollOptions });\n } else if (\n item.top + item.wrapperHeight + item.labelHeight >\n vp.scrollTop + vp.clientHeight - margin\n ) {\n vp.scrollTo({\n top: item.top + item.wrapperHeight + item.labelHeight - vp.clientHeight,\n ...scrollOptions,\n });\n }\n }, [selectedPage, window, scrollOptions]);\n\n return (\n <div ref={viewportRef} style={{ overflowY: 'auto', position: 'relative', ...style }} {...props}>\n {/* spacer keeps correct scroll height even before first window arrives */}\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":["useThumbnailCapability","useCapability","ThumbnailPlugin","id","meta","style","props","provides","thumbs","url","setUrl","useState","urlRef","useRef","useEffect","task","renderThumb","pageIndex","window","devicePixelRatio","wait","blob","objectUrl","URL","createObjectURL","current","ignore","revokeObjectURL","abort","code","PdfErrorCode","Cancelled","message","jsx","src","onLoad","selectedPage","scrollOptions","behavior","block","inline","viewportRef","setWindow","onWindow","vp","onScroll","setViewport","scrollTop","clientHeight","addEventListener","removeEventListener","items","length","item","find","it","top","scrollTo","wrapperHeight","labelHeight","ref","overflowY","position","children","height","totalHeight","map","m","usePlugin"],"mappings":"gPAIaA,EAAyB,IAAMC,gBAA+BC,EAAAA,gBAAgBC,qBCMpF,UAAkBC,KAAEA,EAAAC,MAAMA,KAAUC,IACzC,MAAQC,SAAUC,GAAWR,KACtBS,EAAKC,GAAUC,aAChBC,EAASC,SAAsB,MA8B9B,OA5BPC,EAAAA,WAAU,KACR,MAAMC,EAAO,MAAAP,OAAA,EAAAA,EAAQQ,YAAYZ,EAAKa,UAAWC,OAAOC,kBAOxD,OANM,MAAAJ,GAAAA,EAAAK,MAAMC,IACJ,MAAAC,EAAYC,IAAIC,gBAAgBH,GACtCT,EAAOa,QAAUH,EACjBZ,EAAOY,EAAS,GACfI,UAEI,KACDd,EAAOa,SACLF,IAAAI,gBAAgBf,EAAOa,SAC3Bb,EAAOa,QAAU,MAEjB,MAAAV,GAAAA,EAAMa,MAAM,CACVC,KAAMC,EAAaA,aAAAC,UACnBC,QAAS,wBACV,CAEL,GACC,CAAC5B,EAAKa,YASFR,EAAOwB,EAAAA,IAAA,MAAA,CAAIC,IAAKzB,EAAK0B,OAPJ,KAClBvB,EAAOa,UACLF,IAAAI,gBAAgBf,EAAOa,SAC3Bb,EAAOa,QAAU,KAAA,EAIgCpB,WAAkBC,IAAY,IACrF,yBCjCO,UAAwBD,MAC7BA,EAAA+B,aACAA,EAAAC,cACAA,EAAgB,CAAEC,SAAU,SAAUC,MAAO,UAAWC,OAAQ,cAC7DlC,IAEH,MAAQC,SAAUC,GAAWR,IACvByC,EAAc5B,SAAuB,OAEpCK,EAAQwB,GAAa/B,EAAAA,SAA6B,aAGzDG,EAAAA,WAAU,IAAM,MAAAN,OAAA,EAAAA,EAAQmC,SAASD,IAAY,CAAClC,IAG9CM,EAAAA,WAAU,KACR,MAAM8B,EAAKH,EAAYhB,QACvB,IAAKmB,EAAI,OACT,MAAMC,EAAW,IAAM,MAAArC,OAAA,EAAAA,EAAQsC,YAAYF,EAAGG,UAAWH,EAAGI,cAE5D,OADGJ,EAAAK,iBAAiB,SAAUJ,GACvB,IAAMD,EAAGM,oBAAoB,SAAUL,EAAQ,GACrD,CAACrC,IAGJM,EAAAA,WAAU,KACR,MAAM8B,EAAKH,EAAYhB,QAClBmB,GAAOpC,GAEiB,KAAzB,MAAAU,OAAA,EAAAA,EAAQiC,MAAMC,SAChB5C,EAAOsC,YAAYF,EAAGG,UAAWH,EAAGI,aAAY,GAEjD,CAAC9B,EAAQV,IAGZM,EAAAA,WAAU,KACJ,IAACsB,IAAiBlB,EAAQ,OAExB,MAAAmC,EAAOnC,EAAOiC,MAAMG,MAAMC,GAAOA,EAAGtC,UAAY,IAAMmB,IAC5D,IAAKiB,EAAM,OAEX,MAAMT,EAAKH,EAAYhB,QACvB,IAAKmB,EAAI,OAILS,EAAKG,IAAMZ,EAAGG,UADH,EAEbH,EAAGa,SAAS,CAAED,IAAKH,EAAKG,OAAQnB,IAEhCgB,EAAKG,IAAMH,EAAKK,cAAgBL,EAAKM,YACrCf,EAAGG,UAAYH,EAAGI,aALL,GAObJ,EAAGa,SAAS,CACVD,IAAKH,EAAKG,IAAMH,EAAKK,cAAgBL,EAAKM,YAAcf,EAAGI,gBACxDX,GACJ,GAEF,CAACD,EAAclB,EAAQmB,UAGvB,MAAI,CAAAuB,IAAKnB,EAAapC,MAAO,CAAEwD,UAAW,OAAQC,SAAU,cAAezD,MAAaC,EAEvFyD,eAAC,MAAI,CAAA1D,MAAO,CAAE2D,QAAQ,MAAA9C,SAAAA,EAAQ+C,cAAe,EAAGH,SAAU,YACvDC,SAAQ,MAAA7C,OAAA,EAAAA,EAAAiC,MAAMe,KAAKC,GAAM7D,EAAMyD,SAASI,QAIjD,8DF1EkC,IAAMC,YAA2BlE,EAAAA,gBAAgBC"}
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 { useThumbnailCapability } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n selectedPage?: number;\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({\n style,\n selectedPage,\n scrollOptions = { behavior: 'smooth', block: 'nearest', inline: 'nearest' },\n ...props\n}: ThumbnailsProps) {\n const { provides: thumbs } = useThumbnailCapability();\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(() => thumbs?.onWindow(setWindow), [thumbs]);\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 = () => thumbs?.setViewport(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbs]);\n\n /* 3️⃣ kick-start (or re-kick) after document change */\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbs) return;\n\n if (window?.items.length === 0) {\n thumbs.setViewport(vp.scrollTop, vp.clientHeight);\n }\n }, [window, thumbs]);\n\n /* 4️⃣ whenever selectedPage or window changes, ensure it’s visible */\n useEffect(() => {\n if (!selectedPage || !window) return;\n\n const item = window.items.find((it) => it.pageIndex + 1 === selectedPage);\n if (!item) return; // not in current window yet → wait for next update\n\n const vp = viewportRef.current;\n if (!vp) return;\n\n // Scroll only if the item is above or below the viewport “padding” zone\n const margin = 8; // px\n if (item.top < vp.scrollTop + margin) {\n vp.scrollTo({ top: item.top, ...scrollOptions });\n } else if (\n item.top + item.wrapperHeight + item.labelHeight >\n vp.scrollTop + vp.clientHeight - margin\n ) {\n vp.scrollTo({\n top: item.top + item.wrapperHeight + item.labelHeight - vp.clientHeight,\n ...scrollOptions,\n });\n }\n }, [selectedPage, window, scrollOptions]);\n\n return (\n <div ref={viewportRef} style={{ overflowY: 'auto', position: 'relative', ...style }} {...props}>\n {/* spacer keeps correct scroll height even before first window arrives */}\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","selectedPage","scrollOptions","behavior","block","inline","viewportRef","setWindow","onWindow","vp","onScroll","setViewport","scrollTop","clientHeight","addEventListener","removeEventListener","items","length","item","find","it","top","scrollTo","wrapperHeight","labelHeight","ref","overflowY","position","children","height","totalHeight","map","m"],"mappings":"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"}
@@ -49,8 +49,18 @@ function ThumbnailsPane({
49
49
  }
50
50
  function ThumbImg({ meta, style, ...props }) {
51
51
  const { provides: thumbs } = useThumbnailCapability();
52
+ const { plugin: thumbnailPlugin } = useThumbnailPlugin();
52
53
  const [url, setUrl] = useState();
53
54
  const urlRef = useRef(null);
55
+ const [refreshTick, setRefreshTick] = useState(0);
56
+ useEffect(() => {
57
+ if (!thumbnailPlugin) return;
58
+ return thumbnailPlugin.onRefreshPages((pages) => {
59
+ if (pages.includes(meta.pageIndex)) {
60
+ setRefreshTick((tick) => tick + 1);
61
+ }
62
+ });
63
+ }, [thumbnailPlugin]);
54
64
  useEffect(() => {
55
65
  const task = thumbs == null ? void 0 : thumbs.renderThumb(meta.pageIndex, window.devicePixelRatio);
56
66
  task == null ? void 0 : task.wait((blob) => {
@@ -69,7 +79,7 @@ function ThumbImg({ meta, style, ...props }) {
69
79
  });
70
80
  }
71
81
  };
72
- }, [meta.pageIndex]);
82
+ }, [thumbs, meta.pageIndex, refreshTick]);
73
83
  const handleImageLoad = () => {
74
84
  if (urlRef.current) {
75
85
  URL.revokeObjectURL(urlRef.current);
@@ -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 { useThumbnailCapability } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n selectedPage?: number;\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({\n style,\n selectedPage,\n scrollOptions = { behavior: 'smooth', block: 'nearest', inline: 'nearest' },\n ...props\n}: ThumbnailsProps) {\n const { provides: thumbs } = useThumbnailCapability();\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(() => thumbs?.onWindow(setWindow), [thumbs]);\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 = () => thumbs?.setViewport(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbs]);\n\n /* 3️⃣ kick-start (or re-kick) after document change */\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbs) return;\n\n if (window?.items.length === 0) {\n thumbs.setViewport(vp.scrollTop, vp.clientHeight);\n }\n }, [window, thumbs]);\n\n /* 4️⃣ whenever selectedPage or window changes, ensure it’s visible */\n useEffect(() => {\n if (!selectedPage || !window) return;\n\n const item = window.items.find((it) => it.pageIndex + 1 === selectedPage);\n if (!item) return; // not in current window yet → wait for next update\n\n const vp = viewportRef.current;\n if (!vp) return;\n\n // Scroll only if the item is above or below the viewport “padding” zone\n const margin = 8; // px\n if (item.top < vp.scrollTop + margin) {\n vp.scrollTo({ top: item.top, ...scrollOptions });\n } else if (\n item.top + item.wrapperHeight + item.labelHeight >\n vp.scrollTop + vp.clientHeight - margin\n ) {\n vp.scrollTo({\n top: item.top + item.wrapperHeight + item.labelHeight - vp.clientHeight,\n ...scrollOptions,\n });\n }\n }, [selectedPage, window, scrollOptions]);\n\n return (\n <div ref={viewportRef} style={{ overflowY: 'auto', position: 'relative', ...style }} {...props}>\n {/* spacer keeps correct scroll height even before first window arrives */}\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 } from '@framework';\nimport { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\nimport { useThumbnailCapability } 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 [url, setUrl] = useState<string>();\n const urlRef = useRef<string | null>(null);\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 }, [meta.pageIndex]);\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 { useThumbnailCapability } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n selectedPage?: number;\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({\n style,\n selectedPage,\n scrollOptions = { behavior: 'smooth', block: 'nearest', inline: 'nearest' },\n ...props\n}: ThumbnailsProps) {\n const { provides: thumbs } = useThumbnailCapability();\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(() => thumbs?.onWindow(setWindow), [thumbs]);\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 = () => thumbs?.setViewport(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbs]);\n\n /* 3️⃣ kick-start (or re-kick) after document change */\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbs) return;\n\n if (window?.items.length === 0) {\n thumbs.setViewport(vp.scrollTop, vp.clientHeight);\n }\n }, [window, thumbs]);\n\n /* 4️⃣ whenever selectedPage or window changes, ensure it’s visible */\n useEffect(() => {\n if (!selectedPage || !window) return;\n\n const item = window.items.find((it) => it.pageIndex + 1 === selectedPage);\n if (!item) return; // not in current window yet → wait for next update\n\n const vp = viewportRef.current;\n if (!vp) return;\n\n // Scroll only if the item is above or below the viewport “padding” zone\n const margin = 8; // px\n if (item.top < vp.scrollTop + margin) {\n vp.scrollTo({ top: item.top, ...scrollOptions });\n } else if (\n item.top + item.wrapperHeight + item.labelHeight >\n vp.scrollTop + vp.clientHeight - margin\n ) {\n vp.scrollTo({\n top: item.top + item.wrapperHeight + item.labelHeight - vp.clientHeight,\n ...scrollOptions,\n });\n }\n }, [selectedPage, window, scrollOptions]);\n\n return (\n <div ref={viewportRef} style={{ overflowY: 'auto', position: 'relative', ...style }} {...props}>\n {/* spacer keeps correct scroll height even before first window arrives */}\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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embedpdf/plugin-thumbnail",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -23,21 +23,21 @@
23
23
  }
24
24
  },
25
25
  "dependencies": {
26
- "@embedpdf/models": "1.0.16"
26
+ "@embedpdf/models": "1.0.18"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/react": "^18.2.0",
30
30
  "typescript": "^5.0.0",
31
31
  "@embedpdf/build": "1.0.0",
32
- "@embedpdf/core": "1.0.16",
33
- "@embedpdf/plugin-render": "1.0.16"
32
+ "@embedpdf/plugin-render": "1.0.18",
33
+ "@embedpdf/core": "1.0.18"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "react": ">=16.8.0",
37
37
  "react-dom": ">=16.8.0",
38
38
  "preact": "^10.26.4",
39
- "@embedpdf/core": "1.0.16",
40
- "@embedpdf/plugin-render": "1.0.16"
39
+ "@embedpdf/core": "1.0.18",
40
+ "@embedpdf/plugin-render": "1.0.18"
41
41
  },
42
42
  "files": [
43
43
  "dist",