@embedpdf/plugin-scroll 1.5.0 → 2.0.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +522 -266
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/actions.d.ts +30 -27
  6. package/dist/lib/index.d.ts +1 -0
  7. package/dist/lib/reducer.d.ts +3 -4
  8. package/dist/lib/scroll-plugin.d.ts +40 -27
  9. package/dist/lib/selectors.d.ts +2 -2
  10. package/dist/lib/types.d.ts +65 -30
  11. package/dist/preact/adapter.d.ts +1 -1
  12. package/dist/preact/index.cjs +1 -1
  13. package/dist/preact/index.cjs.map +1 -1
  14. package/dist/preact/index.js +33 -114
  15. package/dist/preact/index.js.map +1 -1
  16. package/dist/react/adapter.d.ts +1 -1
  17. package/dist/react/index.cjs +1 -1
  18. package/dist/react/index.cjs.map +1 -1
  19. package/dist/react/index.js +33 -114
  20. package/dist/react/index.js.map +1 -1
  21. package/dist/shared/components/scroller.d.ts +4 -4
  22. package/dist/shared/hooks/use-scroll.d.ts +4 -15
  23. package/dist/shared-preact/components/scroller.d.ts +4 -4
  24. package/dist/shared-preact/hooks/use-scroll.d.ts +4 -15
  25. package/dist/shared-react/components/scroller.d.ts +4 -4
  26. package/dist/shared-react/hooks/use-scroll.d.ts +4 -15
  27. package/dist/svelte/components/Scroller.svelte.d.ts +4 -4
  28. package/dist/svelte/hooks/use-scroll.svelte.d.ts +11 -5
  29. package/dist/svelte/index.cjs +1 -1
  30. package/dist/svelte/index.cjs.map +1 -1
  31. package/dist/svelte/index.js +90 -102
  32. package/dist/svelte/index.js.map +1 -1
  33. package/dist/vue/components/scroller.vue.d.ts +6 -9
  34. package/dist/vue/hooks/use-scroll.d.ts +12 -8
  35. package/dist/vue/index.cjs +1 -1
  36. package/dist/vue/index.cjs.map +1 -1
  37. package/dist/vue/index.js +115 -115
  38. package/dist/vue/index.js.map +1 -1
  39. package/package.json +7 -6
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { BasePlugin, createBehaviorEmitter, SET_DOCUMENT, getPagesWithRotatedSize, SET_ROTATION, SET_PAGES, SET_SCALE } from "@embedpdf/core";
2
- import { scalePosition, transformPosition, transformRect, Rotation } from "@embedpdf/models";
1
+ import { BasePlugin, createBehaviorEmitter } from "@embedpdf/core";
2
+ import { scalePosition, transformPosition, transformRect, transformSize } from "@embedpdf/models";
3
3
  var ScrollStrategy = /* @__PURE__ */ ((ScrollStrategy2) => {
4
4
  ScrollStrategy2["Vertical"] = "vertical";
5
5
  ScrollStrategy2["Horizontal"] = "horizontal";
@@ -296,31 +296,99 @@ class HorizontalScrollStrategy extends BaseScrollStrategy {
296
296
  return viewport.clientWidth;
297
297
  }
298
298
  }
299
- const UPDATE_SCROLL_STATE = "UPDATE_SCROLL_STATE";
300
- const SET_DESIRED_SCROLL_POSITION = "SET_DESIRED_SCROLL_POSITION";
301
- const UPDATE_TOTAL_PAGES = "UPDATE_TOTAL_PAGES";
302
- const SET_PAGE_CHANGE_STATE = "SET_PAGE_CHANGE_STATE";
303
- function updateScrollState(payload) {
304
- return { type: UPDATE_SCROLL_STATE, payload };
299
+ const INIT_SCROLL_STATE = "INIT_SCROLL_STATE";
300
+ const CLEANUP_SCROLL_STATE = "CLEANUP_SCROLL_STATE";
301
+ const UPDATE_DOCUMENT_SCROLL_STATE = "UPDATE_DOCUMENT_SCROLL_STATE";
302
+ const SET_SCROLL_STRATEGY = "SET_SCROLL_STRATEGY";
303
+ function initScrollState(documentId, state) {
304
+ return { type: INIT_SCROLL_STATE, payload: { documentId, state } };
305
305
  }
306
- function updateTotalPages(payload) {
307
- return { type: UPDATE_TOTAL_PAGES, payload };
306
+ function cleanupScrollState(documentId) {
307
+ return { type: CLEANUP_SCROLL_STATE, payload: documentId };
308
308
  }
309
- function setPageChangeState(payload) {
310
- return { type: SET_PAGE_CHANGE_STATE, payload };
309
+ function updateDocumentScrollState(documentId, state) {
310
+ return { type: UPDATE_DOCUMENT_SCROLL_STATE, payload: { documentId, state } };
311
311
  }
312
- const getScrollerLayout = (state, scale) => {
312
+ function setScrollStrategy(documentId, strategy) {
313
+ return { type: SET_SCROLL_STRATEGY, payload: { documentId, strategy } };
314
+ }
315
+ const defaultPageChangeState = {
316
+ isChanging: false,
317
+ targetPage: 1,
318
+ fromPage: 1,
319
+ startTime: 0
320
+ };
321
+ const initialState = (_coreState, config) => ({
322
+ defaultStrategy: config.defaultStrategy ?? ScrollStrategy.Vertical,
323
+ defaultPageGap: config.defaultPageGap ?? 10,
324
+ defaultBufferSize: config.defaultBufferSize ?? 2,
325
+ documents: {}
326
+ });
327
+ const scrollReducer = (state, action) => {
328
+ switch (action.type) {
329
+ case INIT_SCROLL_STATE: {
330
+ const { documentId, state: docState } = action.payload;
331
+ return {
332
+ ...state,
333
+ documents: {
334
+ ...state.documents,
335
+ [documentId]: docState
336
+ }
337
+ };
338
+ }
339
+ case CLEANUP_SCROLL_STATE: {
340
+ const { [action.payload]: removed, ...remaining } = state.documents;
341
+ return {
342
+ ...state,
343
+ documents: remaining
344
+ };
345
+ }
346
+ case UPDATE_DOCUMENT_SCROLL_STATE: {
347
+ const { documentId, state: updates } = action.payload;
348
+ const docState = state.documents[documentId];
349
+ if (!docState) return state;
350
+ return {
351
+ ...state,
352
+ documents: {
353
+ ...state.documents,
354
+ [documentId]: {
355
+ ...docState,
356
+ ...updates
357
+ }
358
+ }
359
+ };
360
+ }
361
+ case SET_SCROLL_STRATEGY: {
362
+ const { documentId, strategy } = action.payload;
363
+ const docState = state.documents[documentId];
364
+ if (!docState) return state;
365
+ return {
366
+ ...state,
367
+ documents: {
368
+ ...state.documents,
369
+ [documentId]: {
370
+ ...docState,
371
+ strategy
372
+ }
373
+ }
374
+ };
375
+ }
376
+ default:
377
+ return state;
378
+ }
379
+ };
380
+ const getScrollerLayout = (documentState, scale) => {
313
381
  return {
314
- startSpacing: state.startSpacing,
315
- endSpacing: state.endSpacing,
316
- totalWidth: state.totalContentSize.width * scale,
317
- totalHeight: state.totalContentSize.height * scale,
318
- pageGap: state.pageGap * scale,
319
- strategy: state.strategy,
320
- items: state.renderedPageIndexes.map((idx) => {
382
+ startSpacing: documentState.startSpacing,
383
+ endSpacing: documentState.endSpacing,
384
+ totalWidth: documentState.totalContentSize.width * scale,
385
+ totalHeight: documentState.totalContentSize.height * scale,
386
+ pageGap: documentState.pageGap * scale,
387
+ strategy: documentState.strategy,
388
+ items: documentState.renderedPageIndexes.map((idx) => {
321
389
  return {
322
- ...state.virtualItems[idx],
323
- pageLayouts: state.virtualItems[idx].pageLayouts.map((layout) => {
390
+ ...documentState.virtualItems[idx],
391
+ pageLayouts: documentState.virtualItems[idx].pageLayouts.map((layout) => {
324
392
  return {
325
393
  ...layout,
326
394
  rotatedWidth: layout.rotatedWidth * scale,
@@ -335,278 +403,505 @@ const getScrollerLayout = (state, scale) => {
335
403
  };
336
404
  const _ScrollPlugin = class _ScrollPlugin extends BasePlugin {
337
405
  constructor(id, registry, config) {
338
- var _a, _b, _c, _d;
406
+ var _a, _b;
339
407
  super(id, registry);
340
408
  this.id = id;
341
409
  this.config = config;
342
- this.currentScale = 1;
343
- this.currentRotation = Rotation.Degree0;
344
- this.currentPage = 1;
345
- this.layoutReady = false;
346
- this.pageChangeState$ = createBehaviorEmitter();
347
- this.layout$ = createBehaviorEmitter();
348
- this.scroll$ = createBehaviorEmitter();
349
- this.state$ = createBehaviorEmitter();
350
- this.scrollerLayout$ = createBehaviorEmitter();
410
+ this.strategies = /* @__PURE__ */ new Map();
411
+ this.layoutReady = /* @__PURE__ */ new Set();
412
+ this.initialLayoutFired = /* @__PURE__ */ new Set();
413
+ this.scrollerLayoutEmitters = /* @__PURE__ */ new Map();
351
414
  this.pageChange$ = createBehaviorEmitter();
415
+ this.scroll$ = createBehaviorEmitter();
416
+ this.layoutChange$ = createBehaviorEmitter();
417
+ this.pageChangeState$ = createBehaviorEmitter();
352
418
  this.layoutReady$ = createBehaviorEmitter();
419
+ this.state$ = createBehaviorEmitter();
353
420
  this.viewport = this.registry.getPlugin("viewport").provides();
354
- this.strategyConfig = {
355
- pageGap: ((_a = this.config) == null ? void 0 : _a.pageGap) ?? 10,
356
- viewportGap: this.viewport.getViewportGap(),
357
- bufferSize: ((_b = this.config) == null ? void 0 : _b.bufferSize) ?? 2
358
- };
359
- this.strategy = ((_c = this.config) == null ? void 0 : _c.strategy) === ScrollStrategy.Horizontal ? new HorizontalScrollStrategy(this.strategyConfig) : new VerticalScrollStrategy(this.strategyConfig);
360
- this.initialPage = (_d = this.config) == null ? void 0 : _d.initialPage;
361
- this.currentScale = this.coreState.core.scale;
362
- this.currentRotation = this.coreState.core.rotation;
363
- this.viewport.onScrollActivity((activity) => {
364
- if (this.state.pageChangeState.isChanging && !activity.isSmoothScrolling) {
365
- this.completePageChange();
421
+ this.spread = ((_a = this.registry.getPlugin("spread")) == null ? void 0 : _a.provides()) ?? null;
422
+ this.viewport.onScrollActivity((event) => {
423
+ const docState = this.getDocumentState(event.documentId);
424
+ if ((docState == null ? void 0 : docState.pageChangeState.isChanging) && !event.activity.isSmoothScrolling) {
425
+ this.completePageChange(event.documentId);
366
426
  }
367
427
  });
368
- this.viewport.onViewportChange((vp) => this.commitMetrics(this.computeMetrics(vp)), {
369
- mode: "throttle",
370
- wait: 100
428
+ (_b = this.spread) == null ? void 0 : _b.onSpreadChange((event) => {
429
+ this.refreshDocumentLayout(event.documentId);
371
430
  });
372
- this.coreStore.onAction(SET_DOCUMENT, (_action, state) => {
373
- const totalPages = state.core.pages.length;
374
- this.dispatch(updateTotalPages(totalPages));
375
- this.pageChange$.emit({ pageNumber: this.currentPage, totalPages });
376
- this.refreshAll(getPagesWithRotatedSize(state.core), this.viewport.getMetrics());
431
+ this.viewport.onViewportChange((event) => {
432
+ const docState = this.getDocumentState(event.documentId);
433
+ if (!docState) return;
434
+ const computedMetrics = this.computeMetrics(event.documentId, event.metrics);
435
+ if (this.layoutReady.has(event.documentId)) {
436
+ this.commitMetrics(event.documentId, computedMetrics);
437
+ } else {
438
+ this.commitMetrics(event.documentId, {
439
+ ...computedMetrics,
440
+ scrollOffset: docState.scrollOffset
441
+ });
442
+ }
377
443
  });
378
- this.coreStore.onAction(
379
- SET_ROTATION,
380
- (_action, state) => this.refreshAll(getPagesWithRotatedSize(state.core), this.viewport.getMetrics())
444
+ }
445
+ // ─────────────────────────────────────────────────────────
446
+ // Document Lifecycle Hooks (from BasePlugin)
447
+ // ─────────────────────────────────────────────────────────
448
+ onDocumentLoadingStarted(documentId) {
449
+ const coreDoc = this.getCoreDocument(documentId);
450
+ if (!coreDoc) return;
451
+ const docState = this.createDocumentState(coreDoc);
452
+ this.dispatch(initScrollState(documentId, docState));
453
+ const strategy = this.createStrategy(docState.strategy);
454
+ this.strategies.set(documentId, strategy);
455
+ this.scrollerLayoutEmitters.set(documentId, createBehaviorEmitter());
456
+ }
457
+ onDocumentLoaded(documentId) {
458
+ var _a;
459
+ const coreDoc = this.getCoreDocument(documentId);
460
+ if (!coreDoc) return;
461
+ this.dispatch(
462
+ updateDocumentScrollState(documentId, { totalPages: ((_a = coreDoc.document) == null ? void 0 : _a.pageCount) ?? 0 })
381
463
  );
382
- this.coreStore.onAction(
383
- SET_PAGES,
384
- (_action, state) => this.refreshAll(getPagesWithRotatedSize(state.core), this.viewport.getMetrics())
464
+ this.refreshDocumentLayout(documentId);
465
+ this.logger.debug(
466
+ "ScrollPlugin",
467
+ "DocumentOpened",
468
+ `Initialized scroll state for document: ${documentId}`
385
469
  );
386
470
  }
387
- startPageChange(targetPage, behavior = "smooth") {
388
- const pageChangeState = {
389
- isChanging: true,
390
- targetPage,
391
- fromPage: this.currentPage,
392
- startTime: Date.now()
393
- };
394
- this.dispatch(setPageChangeState(pageChangeState));
395
- if (behavior === "instant") {
396
- this.completePageChange();
471
+ onDocumentClosed(documentId) {
472
+ this.strategies.delete(documentId);
473
+ this.layoutReady.delete(documentId);
474
+ this.initialLayoutFired.delete(documentId);
475
+ const emitter = this.scrollerLayoutEmitters.get(documentId);
476
+ if (emitter) {
477
+ emitter.clear();
478
+ this.scrollerLayoutEmitters.delete(documentId);
397
479
  }
480
+ this.dispatch(cleanupScrollState(documentId));
481
+ this.logger.debug(
482
+ "ScrollPlugin",
483
+ "DocumentClosed",
484
+ `Cleaned up scroll state for document: ${documentId}`
485
+ );
398
486
  }
399
- completePageChange() {
400
- if (!this.state.pageChangeState.isChanging) return;
401
- const pageChangeState = {
402
- isChanging: false,
403
- targetPage: this.state.pageChangeState.targetPage,
404
- fromPage: this.state.pageChangeState.fromPage,
405
- startTime: this.state.pageChangeState.startTime
406
- };
407
- this.dispatch(setPageChangeState(pageChangeState));
408
- }
409
- /* ------------------------------------------------------------------ */
410
- /* ᴄᴏᴍᴘᴜᴛᴇʀs */
411
- /* ------------------------------------------------------------------ */
412
- computeLayout(pages) {
413
- const virtualItems = this.strategy.createVirtualItems(pages);
414
- const totalContentSize = this.strategy.getTotalContentSize(virtualItems);
415
- return { virtualItems, totalContentSize };
487
+ onScaleChanged(documentId) {
488
+ const coreDoc = this.coreState.core.documents[documentId];
489
+ if (!coreDoc || coreDoc.status !== "loaded") return;
490
+ const viewportScope = this.viewport.forDocument(documentId);
491
+ const metrics = this.computeMetrics(documentId, viewportScope.getMetrics());
492
+ this.commitMetrics(documentId, metrics);
416
493
  }
417
- computeMetrics(vp, items = this.state.virtualItems) {
418
- return this.strategy.handleScroll(vp, items, this.currentScale);
419
- }
420
- /* ------------------------------------------------------------------ */
421
- /* ᴄᴏᴍᴍɪᴛ (single source of truth) */
422
- /* ------------------------------------------------------------------ */
423
- commit(stateDelta, emit) {
424
- this.dispatch(updateScrollState(stateDelta));
425
- if (emit == null ? void 0 : emit.layout) this.layout$.emit(emit.layout);
426
- if (emit == null ? void 0 : emit.metrics) {
427
- this.scroll$.emit(emit.metrics);
428
- if (emit.metrics.currentPage !== this.currentPage) {
429
- this.currentPage = emit.metrics.currentPage;
430
- this.pageChange$.emit({ pageNumber: this.currentPage, totalPages: this.state.totalPages });
431
- }
432
- }
433
- this.scrollerLayout$.emit(this.getScrollerLayout());
494
+ onRotationChanged(documentId) {
495
+ this.refreshDocumentLayout(documentId);
434
496
  }
435
- /* convenience wrappers */
436
- commitMetrics(metrics) {
437
- this.commit(metrics, { metrics });
497
+ // ─────────────────────────────────────────────────────────
498
+ // Public API for Components (Scroller Layout)
499
+ // ─────────────────────────────────────────────────────────
500
+ /**
501
+ * Subscribe to scroller layout updates for a specific document
502
+ * This is the key method for the Scroller component to stay reactive
503
+ */
504
+ onScrollerData(documentId, callback) {
505
+ const emitter = this.scrollerLayoutEmitters.get(documentId);
506
+ if (!emitter) {
507
+ throw new Error(`No scroller layout emitter found for document: ${documentId}`);
508
+ }
509
+ return emitter.on(callback);
438
510
  }
439
- /* full re-compute after page-spread or initialisation */
440
- refreshAll(pages, vp) {
441
- const layout = this.computeLayout(pages);
442
- const metrics = this.computeMetrics(vp, layout.virtualItems);
443
- this.commit({ ...layout, ...metrics }, { layout, metrics });
511
+ /**
512
+ * Get current scroller layout for a document
513
+ */
514
+ getScrollerLayout(documentId) {
515
+ const docState = this.getDocumentState(documentId);
516
+ const coreDoc = this.getCoreDocumentOrThrow(documentId);
517
+ if (!docState || !coreDoc) {
518
+ throw new Error(`Cannot get scroller layout for document: ${documentId}`);
519
+ }
520
+ return getScrollerLayout(docState, coreDoc.scale);
444
521
  }
445
- getVirtualItemsFromState() {
446
- return this.state.virtualItems || [];
522
+ setLayoutReady(documentId) {
523
+ if (this.layoutReady.has(documentId)) {
524
+ return;
525
+ }
526
+ const docState = this.getDocumentState(documentId);
527
+ if (!docState) return;
528
+ this.layoutReady.add(documentId);
529
+ const isInitial = !this.initialLayoutFired.has(documentId);
530
+ if (isInitial) {
531
+ this.initialLayoutFired.add(documentId);
532
+ }
533
+ const viewport = this.viewport.forDocument(documentId);
534
+ viewport.scrollTo({ ...docState.scrollOffset, behavior: "instant" });
535
+ this.layoutReady$.emit({ documentId, isInitial });
447
536
  }
448
- onScrollerData(callback) {
449
- return this.scrollerLayout$.on(callback);
537
+ clearLayoutReady(documentId) {
538
+ this.layoutReady.delete(documentId);
450
539
  }
451
- getScrollerLayout() {
452
- const scale = this.coreState.core.scale;
453
- return getScrollerLayout(this.state, scale);
540
+ // ─────────────────────────────────────────────────────────
541
+ // Capability
542
+ // ─────────────────────────────────────────────────────────
543
+ buildCapability() {
544
+ return {
545
+ // Active document operations
546
+ getCurrentPage: () => this.getCurrentPage(),
547
+ getTotalPages: () => this.getTotalPages(),
548
+ getPageChangeState: () => this.getPageChangeState(),
549
+ scrollToPage: (options) => this.scrollToPage(options),
550
+ scrollToNextPage: (behavior) => this.scrollToNextPage(behavior),
551
+ scrollToPreviousPage: (behavior) => this.scrollToPreviousPage(behavior),
552
+ getMetrics: (viewport) => this.getMetrics(viewport),
553
+ getLayout: () => this.getLayout(),
554
+ getRectPositionForPage: (page, rect, scale, rotation) => this.getRectPositionForPage(page, rect, scale, rotation),
555
+ // Document-scoped operations
556
+ forDocument: (documentId) => this.createScrollScope(documentId),
557
+ // Global settings
558
+ setScrollStrategy: (strategy, documentId) => this.setScrollStrategyForDocument(strategy, documentId),
559
+ getPageGap: () => this.state.defaultPageGap,
560
+ // Events
561
+ onPageChange: this.pageChange$.on,
562
+ onScroll: this.scroll$.on,
563
+ onLayoutChange: this.layoutChange$.on,
564
+ onLayoutReady: this.layoutReady$.on,
565
+ onPageChangeState: this.pageChangeState$.on,
566
+ onStateChange: this.state$.on
567
+ };
454
568
  }
455
- pushScrollLayout() {
456
- this.scrollerLayout$.emit(this.getScrollerLayout());
569
+ // ─────────────────────────────────────────────────────────
570
+ // Document Scoping
571
+ // ─────────────────────────────────────────────────────────
572
+ createScrollScope(documentId) {
573
+ return {
574
+ getCurrentPage: () => this.getCurrentPage(documentId),
575
+ getTotalPages: () => this.getTotalPages(documentId),
576
+ getPageChangeState: () => this.getPageChangeState(documentId),
577
+ scrollToPage: (options) => this.scrollToPage(options, documentId),
578
+ scrollToNextPage: (behavior) => this.scrollToNextPage(behavior, documentId),
579
+ scrollToPreviousPage: (behavior) => this.scrollToPreviousPage(behavior, documentId),
580
+ getSpreadPagesWithRotatedSize: () => this.getSpreadPagesWithRotatedSize(documentId),
581
+ getMetrics: (viewport) => this.getMetrics(viewport, documentId),
582
+ getLayout: () => this.getLayout(documentId),
583
+ getRectPositionForPage: (page, rect, scale, rotation) => this.getRectPositionForPage(page, rect, scale, rotation, documentId),
584
+ setScrollStrategy: (strategy) => this.setScrollStrategyForDocument(strategy, documentId),
585
+ onPageChange: (listener) => this.pageChange$.on((event) => {
586
+ if (event.documentId === documentId) listener(event);
587
+ }),
588
+ onScroll: (listener) => this.scroll$.on((event) => {
589
+ if (event.documentId === documentId) listener(event.metrics);
590
+ }),
591
+ onLayoutChange: (listener) => this.layoutChange$.on((event) => {
592
+ if (event.documentId === documentId) listener(event.layout);
593
+ })
594
+ };
457
595
  }
458
- onStoreUpdated(prevState, newState) {
459
- this.pushScrollLayout();
460
- if (prevState.pageChangeState !== newState.pageChangeState) {
461
- this.pageChangeState$.emit(newState.pageChangeState);
596
+ // ─────────────────────────────────────────────────────────
597
+ // State Helpers
598
+ // ─────────────────────────────────────────────────────────
599
+ getDocumentState(documentId) {
600
+ const id = documentId ?? this.getActiveDocumentId();
601
+ return this.state.documents[id] ?? null;
602
+ }
603
+ getDocumentStateOrThrow(documentId) {
604
+ const state = this.getDocumentState(documentId);
605
+ if (!state) {
606
+ throw new Error(`Scroll state not found for document: ${documentId ?? "active"}`);
462
607
  }
608
+ return state;
463
609
  }
464
- onCoreStoreUpdated(prevState, newState) {
465
- if (prevState.core.scale !== newState.core.scale) {
466
- this.currentScale = newState.core.scale;
467
- this.commitMetrics(this.computeMetrics(this.viewport.getMetrics()));
610
+ getStrategy(documentId) {
611
+ const id = documentId ?? this.getActiveDocumentId();
612
+ const strategy = this.strategies.get(id);
613
+ if (!strategy) {
614
+ throw new Error(`Strategy not found for document: ${id}`);
468
615
  }
469
- if (prevState.core.rotation !== newState.core.rotation) {
470
- this.currentRotation = newState.core.rotation;
616
+ return strategy;
617
+ }
618
+ createStrategy(strategyType) {
619
+ const config = {
620
+ pageGap: this.state.defaultPageGap,
621
+ viewportGap: this.viewport.getViewportGap(),
622
+ bufferSize: this.state.defaultBufferSize
623
+ };
624
+ return strategyType === ScrollStrategy.Horizontal ? new HorizontalScrollStrategy(config) : new VerticalScrollStrategy(config);
625
+ }
626
+ createDocumentState(coreDoc) {
627
+ var _a;
628
+ return {
629
+ virtualItems: [],
630
+ totalPages: ((_a = coreDoc.document) == null ? void 0 : _a.pageCount) ?? 0,
631
+ currentPage: 1,
632
+ totalContentSize: { width: 0, height: 0 },
633
+ strategy: this.state.defaultStrategy,
634
+ pageGap: this.state.defaultPageGap,
635
+ visiblePages: [],
636
+ pageVisibilityMetrics: [],
637
+ renderedPageIndexes: [],
638
+ scrollOffset: { x: 0, y: 0 },
639
+ startSpacing: 0,
640
+ endSpacing: 0,
641
+ pageChangeState: defaultPageChangeState
642
+ };
643
+ }
644
+ // ─────────────────────────────────────────────────────────
645
+ // Page Change Management
646
+ // ─────────────────────────────────────────────────────────
647
+ startPageChange(documentId, targetPage, behavior = "smooth") {
648
+ const docState = this.getDocumentState(documentId);
649
+ if (!docState) return;
650
+ const pageChangeState = {
651
+ isChanging: true,
652
+ targetPage,
653
+ fromPage: docState.currentPage,
654
+ startTime: Date.now()
655
+ };
656
+ this.dispatch(updateDocumentScrollState(documentId, { pageChangeState }));
657
+ if (behavior === "instant") {
658
+ this.completePageChange(documentId);
471
659
  }
472
660
  }
473
- /**
474
- * Change the scroll strategy at runtime (e.g., vertical <-> horizontal)
475
- * @param newStrategy ScrollStrategy.Horizontal or ScrollStrategy.Vertical
476
- */
477
- setScrollStrategy(newStrategy) {
478
- if (newStrategy === ScrollStrategy.Horizontal && this.strategy instanceof HorizontalScrollStrategy || newStrategy === ScrollStrategy.Vertical && this.strategy instanceof VerticalScrollStrategy) {
479
- return;
661
+ completePageChange(documentId) {
662
+ const docState = this.getDocumentState(documentId);
663
+ if (!docState || !docState.pageChangeState.isChanging) return;
664
+ const pageChangeState = {
665
+ isChanging: false,
666
+ targetPage: docState.pageChangeState.targetPage,
667
+ fromPage: docState.pageChangeState.fromPage,
668
+ startTime: docState.pageChangeState.startTime
669
+ };
670
+ this.dispatch(updateDocumentScrollState(documentId, { pageChangeState }));
671
+ }
672
+ // ─────────────────────────────────────────────────────────
673
+ // Layout & Metrics Computation
674
+ // ─────────────────────────────────────────────────────────
675
+ computeLayout(documentId, pages) {
676
+ const strategy = this.getStrategy(documentId);
677
+ const virtualItems = strategy.createVirtualItems(pages);
678
+ const totalContentSize = strategy.getTotalContentSize(virtualItems);
679
+ return { virtualItems, totalContentSize };
680
+ }
681
+ computeMetrics(documentId, vp, items) {
682
+ const coreDocState = this.getCoreDocumentOrThrow(documentId);
683
+ const docState = this.getDocumentState(documentId);
684
+ const strategy = this.getStrategy(documentId);
685
+ if (!docState) throw new Error(`Document state not found: ${documentId}`);
686
+ return strategy.handleScroll(vp, items ?? docState.virtualItems, coreDocState.scale);
687
+ }
688
+ // ─────────────────────────────────────────────────────────
689
+ // Commit (Single Source of Truth)
690
+ // ─────────────────────────────────────────────────────────
691
+ commitMetrics(documentId, metrics) {
692
+ const docState = this.getDocumentState(documentId);
693
+ if (!docState) return;
694
+ this.dispatch(updateDocumentScrollState(documentId, metrics));
695
+ this.scroll$.emit({ documentId, metrics });
696
+ if (metrics.currentPage !== docState.currentPage) {
697
+ this.pageChange$.emit({
698
+ documentId,
699
+ pageNumber: metrics.currentPage,
700
+ totalPages: docState.totalPages
701
+ });
702
+ }
703
+ this.pushScrollerLayout(documentId);
704
+ }
705
+ pushScrollerLayout(documentId) {
706
+ const emitter = this.scrollerLayoutEmitters.get(documentId);
707
+ if (!emitter) return;
708
+ try {
709
+ const layout = this.getScrollerLayout(documentId);
710
+ emitter.emit(layout);
711
+ } catch (error) {
480
712
  }
481
- this.strategy = newStrategy === ScrollStrategy.Horizontal ? new HorizontalScrollStrategy(this.strategyConfig) : new VerticalScrollStrategy(this.strategyConfig);
713
+ }
714
+ refreshDocumentLayout(documentId) {
715
+ const coreDoc = this.coreState.core.documents[documentId];
716
+ const docState = this.getDocumentState(documentId);
717
+ if (!coreDoc || !docState || coreDoc.status !== "loaded") return;
718
+ const pages = this.getSpreadPagesWithRotatedSize(documentId);
719
+ const layout = this.computeLayout(documentId, pages);
720
+ const viewport = this.viewport.forDocument(documentId);
721
+ const metrics = this.computeMetrics(documentId, viewport.getMetrics(), layout.virtualItems);
482
722
  this.dispatch(
483
- updateScrollState({
484
- strategy: newStrategy
723
+ updateDocumentScrollState(documentId, {
724
+ ...layout,
725
+ ...metrics
485
726
  })
486
727
  );
487
- const pages = getPagesWithRotatedSize(this.coreState.core);
488
- this.refreshAll(pages, this.viewport.getMetrics());
489
- }
490
- setLayoutReady() {
491
- if (this.layoutReady) return;
492
- this.layoutReady = true;
493
- this.layoutReady$.emit(true);
494
- if (this.initialPage) {
495
- this.scrollToPage({ pageNumber: this.initialPage, behavior: "instant" });
496
- }
728
+ this.layoutChange$.emit({ documentId, layout });
729
+ this.pushScrollerLayout(documentId);
730
+ }
731
+ getSpreadPagesWithRotatedSize(documentId) {
732
+ var _a, _b;
733
+ const id = documentId ?? this.getActiveDocumentId();
734
+ const coreDoc = this.coreState.core.documents[id];
735
+ if (!coreDoc) throw new Error(`Document ${id} not loaded`);
736
+ const spreadPages = ((_a = this.spread) == null ? void 0 : _a.forDocument(id).getSpreadPages()) || ((_b = coreDoc.document) == null ? void 0 : _b.pages.map((page) => [page])) || [];
737
+ return spreadPages.map(
738
+ (spread) => spread.map((page) => ({
739
+ ...page,
740
+ rotatedSize: transformSize(page.size, coreDoc.rotation, 1)
741
+ }))
742
+ );
497
743
  }
498
- buildCapability() {
499
- return {
500
- onStateChange: this.state$.on,
501
- onLayoutChange: this.layout$.on,
502
- onScroll: this.scroll$.on,
503
- onPageChange: this.pageChange$.on,
504
- onLayoutReady: this.layoutReady$.on,
505
- onPageChangeState: this.pageChangeState$.on,
506
- getCurrentPage: () => this.currentPage,
507
- getTotalPages: () => this.state.totalPages,
508
- getPageChangeState: () => this.state.pageChangeState,
509
- scrollToPage: this.scrollToPage.bind(this),
510
- scrollToNextPage: this.scrollToNextPage.bind(this),
511
- scrollToPreviousPage: this.scrollToPreviousPage.bind(this),
512
- getMetrics: this.getMetrics.bind(this),
513
- getLayout: this.getLayout.bind(this),
514
- getRectPositionForPage: this.getRectPositionForPage.bind(this),
515
- getPageGap: () => this.state.pageGap,
516
- setScrollStrategy: (strategy) => this.setScrollStrategy(strategy)
517
- };
744
+ // ─────────────────────────────────────────────────────────
745
+ // Core Operations
746
+ // ─────────────────────────────────────────────────────────
747
+ getCurrentPage(documentId) {
748
+ return this.getDocumentStateOrThrow(documentId).currentPage;
749
+ }
750
+ getTotalPages(documentId) {
751
+ return this.getDocumentStateOrThrow(documentId).totalPages;
518
752
  }
519
- scrollToPage(options) {
753
+ getPageChangeState(documentId) {
754
+ return this.getDocumentStateOrThrow(documentId).pageChangeState;
755
+ }
756
+ scrollToPage(options, documentId) {
757
+ const id = documentId ?? this.getActiveDocumentId();
758
+ const docState = this.getDocumentStateOrThrow(id);
759
+ const strategy = this.getStrategy(id);
760
+ const coreDoc = this.getCoreDocumentOrThrow(id);
520
761
  const { pageNumber, behavior = "smooth", pageCoordinates, center = false } = options;
521
- this.startPageChange(pageNumber, behavior);
522
- const virtualItems = this.getVirtualItemsFromState();
523
- const position = this.strategy.getScrollPositionForPage(
762
+ this.startPageChange(id, pageNumber, behavior);
763
+ const position = strategy.getScrollPositionForPage(
524
764
  pageNumber,
525
- virtualItems,
526
- this.currentScale,
527
- this.currentRotation,
765
+ docState.virtualItems,
766
+ coreDoc.scale,
767
+ coreDoc.rotation,
528
768
  pageCoordinates
529
769
  );
530
770
  if (position) {
531
- this.viewport.scrollTo({ ...position, behavior, center });
771
+ const viewport = this.viewport.forDocument(id);
772
+ viewport.scrollTo({ ...position, behavior, center });
532
773
  } else {
533
- this.completePageChange();
774
+ this.completePageChange(id);
534
775
  }
535
776
  }
536
- scrollToNextPage(behavior = "smooth") {
537
- const virtualItems = this.getVirtualItemsFromState();
538
- const currentItemIndex = virtualItems.findIndex(
539
- (item) => item.pageNumbers.includes(this.currentPage)
777
+ scrollToNextPage(behavior = "smooth", documentId) {
778
+ const id = documentId ?? this.getActiveDocumentId();
779
+ const docState = this.getDocumentStateOrThrow(id);
780
+ const strategy = this.getStrategy(id);
781
+ const coreDoc = this.getCoreDocumentOrThrow(id);
782
+ const currentItemIndex = docState.virtualItems.findIndex(
783
+ (item) => item.pageNumbers.includes(docState.currentPage)
540
784
  );
541
- if (currentItemIndex >= 0 && currentItemIndex < virtualItems.length - 1) {
542
- const nextItem = virtualItems[currentItemIndex + 1];
785
+ if (currentItemIndex >= 0 && currentItemIndex < docState.virtualItems.length - 1) {
786
+ const nextItem = docState.virtualItems[currentItemIndex + 1];
543
787
  const targetPage = nextItem.pageNumbers[0];
544
- this.startPageChange(targetPage, behavior);
545
- const position = this.strategy.getScrollPositionForPage(
546
- nextItem.pageNumbers[0],
547
- virtualItems,
548
- this.currentScale,
549
- this.currentRotation
788
+ this.startPageChange(id, targetPage, behavior);
789
+ const position = strategy.getScrollPositionForPage(
790
+ targetPage,
791
+ docState.virtualItems,
792
+ coreDoc.scale,
793
+ coreDoc.rotation
550
794
  );
551
795
  if (position) {
552
- this.viewport.scrollTo({ ...position, behavior });
796
+ const viewport = this.viewport.forDocument(id);
797
+ viewport.scrollTo({ ...position, behavior });
553
798
  } else {
554
- this.completePageChange();
799
+ this.completePageChange(id);
555
800
  }
556
801
  }
557
802
  }
558
- scrollToPreviousPage(behavior = "smooth") {
559
- const virtualItems = this.getVirtualItemsFromState();
560
- const currentItemIndex = virtualItems.findIndex(
561
- (item) => item.pageNumbers.includes(this.currentPage)
803
+ scrollToPreviousPage(behavior = "smooth", documentId) {
804
+ const id = documentId ?? this.getActiveDocumentId();
805
+ const docState = this.getDocumentStateOrThrow(id);
806
+ const strategy = this.getStrategy(id);
807
+ const coreDoc = this.coreState.core.documents[id];
808
+ const currentItemIndex = docState.virtualItems.findIndex(
809
+ (item) => item.pageNumbers.includes(docState.currentPage)
562
810
  );
563
811
  if (currentItemIndex > 0) {
564
- const prevItem = virtualItems[currentItemIndex - 1];
812
+ const prevItem = docState.virtualItems[currentItemIndex - 1];
565
813
  const targetPage = prevItem.pageNumbers[0];
566
- this.startPageChange(targetPage, behavior);
567
- const position = this.strategy.getScrollPositionForPage(
568
- prevItem.pageNumbers[0],
569
- virtualItems,
570
- this.currentScale,
571
- this.currentRotation
814
+ this.startPageChange(id, targetPage, behavior);
815
+ const position = strategy.getScrollPositionForPage(
816
+ targetPage,
817
+ docState.virtualItems,
818
+ coreDoc.scale,
819
+ coreDoc.rotation
572
820
  );
573
821
  if (position) {
574
- this.viewport.scrollTo({ ...position, behavior });
822
+ const viewport = this.viewport.forDocument(id);
823
+ viewport.scrollTo({ ...position, behavior });
575
824
  } else {
576
- this.completePageChange();
825
+ this.completePageChange(id);
577
826
  }
578
827
  }
579
828
  }
580
- getMetrics(viewport) {
581
- const metrics = viewport || this.viewport.getMetrics();
582
- const virtualItems = this.getVirtualItemsFromState();
583
- return this.strategy.handleScroll(metrics, virtualItems, this.currentScale);
829
+ getMetrics(viewport, documentId) {
830
+ const id = documentId ?? this.getActiveDocumentId();
831
+ if (viewport) {
832
+ return this.computeMetrics(id, viewport);
833
+ }
834
+ const viewportScope = this.viewport.forDocument(id);
835
+ return this.computeMetrics(id, viewportScope.getMetrics());
584
836
  }
585
- getLayout() {
837
+ getLayout(documentId) {
838
+ const docState = this.getDocumentStateOrThrow(documentId);
586
839
  return {
587
- virtualItems: this.state.virtualItems,
588
- totalContentSize: this.state.totalContentSize
840
+ virtualItems: docState.virtualItems,
841
+ totalContentSize: docState.totalContentSize
589
842
  };
590
843
  }
591
- getRectPositionForPage(pageIndex, rect, scale, rotation) {
592
- return this.strategy.getRectPositionForPage(
844
+ getRectPositionForPage(pageIndex, rect, scale, rotation, documentId) {
845
+ const id = documentId ?? this.getActiveDocumentId();
846
+ const docState = this.getDocumentStateOrThrow(id);
847
+ const strategy = this.getStrategy(id);
848
+ const coreDoc = this.getCoreDocumentOrThrow(id);
849
+ return strategy.getRectPositionForPage(
593
850
  pageIndex + 1,
594
- this.state.virtualItems,
595
- scale ?? this.currentScale,
596
- rotation ?? this.currentRotation,
851
+ docState.virtualItems,
852
+ scale ?? coreDoc.scale,
853
+ rotation ?? coreDoc.rotation,
597
854
  rect
598
855
  );
599
856
  }
857
+ setScrollStrategyForDocument(newStrategy, documentId) {
858
+ const id = documentId ?? this.getActiveDocumentId();
859
+ const docState = this.getDocumentState(id);
860
+ if (!docState || docState.strategy === newStrategy) return;
861
+ const strategy = this.createStrategy(newStrategy);
862
+ this.strategies.set(id, strategy);
863
+ this.dispatch(setScrollStrategy(id, newStrategy));
864
+ this.refreshDocumentLayout(id);
865
+ }
866
+ // ─────────────────────────────────────────────────────────
867
+ // Store Update Handlers
868
+ // ─────────────────────────────────────────────────────────
869
+ onStoreUpdated(prevState, newState) {
870
+ for (const documentId in newState.documents) {
871
+ const prevDoc = prevState.documents[documentId];
872
+ const newDoc = newState.documents[documentId];
873
+ if (prevDoc !== newDoc) {
874
+ this.state$.emit(newDoc);
875
+ if ((prevDoc == null ? void 0 : prevDoc.pageChangeState) !== newDoc.pageChangeState) {
876
+ this.pageChangeState$.emit({
877
+ documentId,
878
+ state: newDoc.pageChangeState
879
+ });
880
+ }
881
+ this.pushScrollerLayout(documentId);
882
+ }
883
+ }
884
+ }
885
+ // ─────────────────────────────────────────────────────────
886
+ // Lifecycle
887
+ // ─────────────────────────────────────────────────────────
600
888
  async initialize() {
889
+ this.logger.info("ScrollPlugin", "Initialize", "Scroll plugin initialized");
601
890
  }
602
891
  async destroy() {
603
- this.layout$.clear();
604
- this.scroll$.clear();
892
+ this.strategies.clear();
893
+ this.layoutReady.clear();
894
+ this.initialLayoutFired.clear();
895
+ for (const emitter of this.scrollerLayoutEmitters.values()) {
896
+ emitter.clear();
897
+ }
898
+ this.scrollerLayoutEmitters.clear();
605
899
  this.pageChange$.clear();
606
- this.state$.clear();
607
- this.scrollerLayout$.clear();
608
- this.layoutReady$.clear();
900
+ this.scroll$.clear();
901
+ this.layoutChange$.clear();
609
902
  this.pageChangeState$.clear();
903
+ this.layoutReady$.clear();
904
+ this.state$.clear();
610
905
  super.destroy();
611
906
  }
612
907
  };
@@ -619,52 +914,12 @@ const manifest = {
619
914
  version: "1.0.0",
620
915
  provides: ["scroll"],
621
916
  requires: ["viewport"],
622
- optional: [],
917
+ optional: ["spread"],
623
918
  defaultConfig: {
624
919
  enabled: true,
625
- pageGap: 10
626
- }
627
- };
628
- const defaultScrollMetrics = {
629
- currentPage: 1,
630
- visiblePages: [],
631
- pageVisibilityMetrics: [],
632
- renderedPageIndexes: [],
633
- scrollOffset: { x: 0, y: 0 },
634
- startSpacing: 0,
635
- endSpacing: 0
636
- };
637
- const defaultPageChangeState = {
638
- isChanging: false,
639
- targetPage: 1,
640
- fromPage: 1,
641
- startTime: 0
642
- };
643
- const initialState = (coreState, config) => ({
644
- virtualItems: [],
645
- totalPages: coreState.pages.length,
646
- totalContentSize: { width: 0, height: 0 },
647
- desiredScrollPosition: { x: 0, y: 0 },
648
- strategy: config.strategy ?? ScrollStrategy.Vertical,
649
- pageGap: config.pageGap ?? 10,
650
- scale: coreState.scale,
651
- pageChangeState: defaultPageChangeState,
652
- ...defaultScrollMetrics
653
- });
654
- const scrollReducer = (state, action) => {
655
- switch (action.type) {
656
- case UPDATE_TOTAL_PAGES:
657
- return { ...state, totalPages: action.payload };
658
- case SET_SCALE:
659
- return { ...state, scale: action.payload };
660
- case UPDATE_SCROLL_STATE:
661
- return { ...state, ...action.payload };
662
- case SET_DESIRED_SCROLL_POSITION:
663
- return { ...state, desiredScrollPosition: action.payload };
664
- case SET_PAGE_CHANGE_STATE:
665
- return { ...state, pageChangeState: action.payload };
666
- default:
667
- return state;
920
+ defaultPageGap: 10,
921
+ defaultBufferSize: 4,
922
+ defaultStrategy: ScrollStrategy.Vertical
668
923
  }
669
924
  };
670
925
  const ScrollPluginPackage = {
@@ -678,6 +933,7 @@ export {
678
933
  ScrollPlugin,
679
934
  ScrollPluginPackage,
680
935
  ScrollStrategy,
936
+ getScrollerLayout,
681
937
  manifest
682
938
  };
683
939
  //# sourceMappingURL=index.js.map