@embedpdf/plugin-scroll 1.4.1 → 2.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +41 -27
  9. package/dist/lib/selectors.d.ts +2 -2
  10. package/dist/lib/types.d.ts +63 -29
  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, _c;
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.scrollerLayoutEmitters = /* @__PURE__ */ new Map();
413
+ this.initialPageUsed = false;
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.initialPage = (_b = this.config) == null ? void 0 : _b.initialPage;
423
+ this.viewport.onScrollActivity((event) => {
424
+ const docState = this.getDocumentState(event.documentId);
425
+ if ((docState == null ? void 0 : docState.pageChangeState.isChanging) && !event.activity.isSmoothScrolling) {
426
+ this.completePageChange(event.documentId);
366
427
  }
367
428
  });
368
- this.viewport.onViewportChange((vp) => this.commitMetrics(this.computeMetrics(vp)), {
369
- mode: "throttle",
370
- wait: 100
429
+ (_c = this.spread) == null ? void 0 : _c.onSpreadChange((event) => {
430
+ this.refreshDocumentLayout(event.documentId);
371
431
  });
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());
432
+ this.viewport.onViewportChange((event) => {
433
+ const docState = this.getDocumentState(event.documentId);
434
+ if (!docState) return;
435
+ const computedMetrics = this.computeMetrics(event.documentId, event.metrics);
436
+ if (this.layoutReady.has(event.documentId)) {
437
+ this.commitMetrics(event.documentId, computedMetrics);
438
+ } else {
439
+ this.commitMetrics(event.documentId, {
440
+ ...computedMetrics,
441
+ scrollOffset: docState.scrollOffset
442
+ });
443
+ }
377
444
  });
378
- this.coreStore.onAction(
379
- SET_ROTATION,
380
- (_action, state) => this.refreshAll(getPagesWithRotatedSize(state.core), this.viewport.getMetrics())
445
+ }
446
+ // ─────────────────────────────────────────────────────────
447
+ // Document Lifecycle Hooks (from BasePlugin)
448
+ // ─────────────────────────────────────────────────────────
449
+ onDocumentLoadingStarted(documentId) {
450
+ const coreDoc = this.getCoreDocument(documentId);
451
+ if (!coreDoc) return;
452
+ const docState = this.createDocumentState(coreDoc);
453
+ this.dispatch(initScrollState(documentId, docState));
454
+ const strategy = this.createStrategy(docState.strategy);
455
+ this.strategies.set(documentId, strategy);
456
+ this.scrollerLayoutEmitters.set(documentId, createBehaviorEmitter());
457
+ }
458
+ onDocumentLoaded(documentId) {
459
+ var _a;
460
+ const coreDoc = this.getCoreDocument(documentId);
461
+ if (!coreDoc) return;
462
+ this.dispatch(
463
+ updateDocumentScrollState(documentId, { totalPages: ((_a = coreDoc.document) == null ? void 0 : _a.pageCount) ?? 0 })
381
464
  );
382
- this.coreStore.onAction(
383
- SET_PAGES,
384
- (_action, state) => this.refreshAll(getPagesWithRotatedSize(state.core), this.viewport.getMetrics())
465
+ this.refreshDocumentLayout(documentId);
466
+ this.logger.debug(
467
+ "ScrollPlugin",
468
+ "DocumentOpened",
469
+ `Initialized scroll state for document: ${documentId}`
385
470
  );
386
471
  }
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();
472
+ onDocumentClosed(documentId) {
473
+ this.strategies.delete(documentId);
474
+ this.layoutReady.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
+ if (this.initialPage && !this.initialPageUsed) {
530
+ this.initialPageUsed = true;
531
+ this.scrollToPage({ pageNumber: this.initialPage, behavior: "instant" }, documentId);
532
+ } else {
533
+ const viewport = this.viewport.forDocument(documentId);
534
+ viewport.scrollTo({ ...docState.scrollOffset, behavior: "instant" });
535
+ }
536
+ this.layoutReady$.emit({ documentId });
447
537
  }
448
- onScrollerData(callback) {
449
- return this.scrollerLayout$.on(callback);
538
+ clearLayoutReady(documentId) {
539
+ this.layoutReady.delete(documentId);
450
540
  }
451
- getScrollerLayout() {
452
- const scale = this.coreState.core.scale;
453
- return getScrollerLayout(this.state, scale);
541
+ // ─────────────────────────────────────────────────────────
542
+ // Capability
543
+ // ─────────────────────────────────────────────────────────
544
+ buildCapability() {
545
+ return {
546
+ // Active document operations
547
+ getCurrentPage: () => this.getCurrentPage(),
548
+ getTotalPages: () => this.getTotalPages(),
549
+ getPageChangeState: () => this.getPageChangeState(),
550
+ scrollToPage: (options) => this.scrollToPage(options),
551
+ scrollToNextPage: (behavior) => this.scrollToNextPage(behavior),
552
+ scrollToPreviousPage: (behavior) => this.scrollToPreviousPage(behavior),
553
+ getMetrics: (viewport) => this.getMetrics(viewport),
554
+ getLayout: () => this.getLayout(),
555
+ getRectPositionForPage: (page, rect, scale, rotation) => this.getRectPositionForPage(page, rect, scale, rotation),
556
+ // Document-scoped operations
557
+ forDocument: (documentId) => this.createScrollScope(documentId),
558
+ // Global settings
559
+ setScrollStrategy: (strategy, documentId) => this.setScrollStrategyForDocument(strategy, documentId),
560
+ getPageGap: () => this.state.defaultPageGap,
561
+ // Events
562
+ onPageChange: this.pageChange$.on,
563
+ onScroll: this.scroll$.on,
564
+ onLayoutChange: this.layoutChange$.on,
565
+ onLayoutReady: this.layoutReady$.on,
566
+ onPageChangeState: this.pageChangeState$.on,
567
+ onStateChange: this.state$.on
568
+ };
454
569
  }
455
- pushScrollLayout() {
456
- this.scrollerLayout$.emit(this.getScrollerLayout());
570
+ // ─────────────────────────────────────────────────────────
571
+ // Document Scoping
572
+ // ─────────────────────────────────────────────────────────
573
+ createScrollScope(documentId) {
574
+ return {
575
+ getCurrentPage: () => this.getCurrentPage(documentId),
576
+ getTotalPages: () => this.getTotalPages(documentId),
577
+ getPageChangeState: () => this.getPageChangeState(documentId),
578
+ scrollToPage: (options) => this.scrollToPage(options, documentId),
579
+ scrollToNextPage: (behavior) => this.scrollToNextPage(behavior, documentId),
580
+ scrollToPreviousPage: (behavior) => this.scrollToPreviousPage(behavior, documentId),
581
+ getSpreadPagesWithRotatedSize: () => this.getSpreadPagesWithRotatedSize(documentId),
582
+ getMetrics: (viewport) => this.getMetrics(viewport, documentId),
583
+ getLayout: () => this.getLayout(documentId),
584
+ getRectPositionForPage: (page, rect, scale, rotation) => this.getRectPositionForPage(page, rect, scale, rotation, documentId),
585
+ setScrollStrategy: (strategy) => this.setScrollStrategyForDocument(strategy, documentId),
586
+ onPageChange: (listener) => this.pageChange$.on((event) => {
587
+ if (event.documentId === documentId) listener(event);
588
+ }),
589
+ onScroll: (listener) => this.scroll$.on((event) => {
590
+ if (event.documentId === documentId) listener(event.metrics);
591
+ }),
592
+ onLayoutChange: (listener) => this.layoutChange$.on((event) => {
593
+ if (event.documentId === documentId) listener(event.layout);
594
+ })
595
+ };
457
596
  }
458
- onStoreUpdated(prevState, newState) {
459
- this.pushScrollLayout();
460
- if (prevState.pageChangeState !== newState.pageChangeState) {
461
- this.pageChangeState$.emit(newState.pageChangeState);
597
+ // ─────────────────────────────────────────────────────────
598
+ // State Helpers
599
+ // ─────────────────────────────────────────────────────────
600
+ getDocumentState(documentId) {
601
+ const id = documentId ?? this.getActiveDocumentId();
602
+ return this.state.documents[id] ?? null;
603
+ }
604
+ getDocumentStateOrThrow(documentId) {
605
+ const state = this.getDocumentState(documentId);
606
+ if (!state) {
607
+ throw new Error(`Scroll state not found for document: ${documentId ?? "active"}`);
462
608
  }
609
+ return state;
463
610
  }
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()));
611
+ getStrategy(documentId) {
612
+ const id = documentId ?? this.getActiveDocumentId();
613
+ const strategy = this.strategies.get(id);
614
+ if (!strategy) {
615
+ throw new Error(`Strategy not found for document: ${id}`);
468
616
  }
469
- if (prevState.core.rotation !== newState.core.rotation) {
470
- this.currentRotation = newState.core.rotation;
617
+ return strategy;
618
+ }
619
+ createStrategy(strategyType) {
620
+ const config = {
621
+ pageGap: this.state.defaultPageGap,
622
+ viewportGap: this.viewport.getViewportGap(),
623
+ bufferSize: this.state.defaultBufferSize
624
+ };
625
+ return strategyType === ScrollStrategy.Horizontal ? new HorizontalScrollStrategy(config) : new VerticalScrollStrategy(config);
626
+ }
627
+ createDocumentState(coreDoc) {
628
+ var _a;
629
+ return {
630
+ virtualItems: [],
631
+ totalPages: ((_a = coreDoc.document) == null ? void 0 : _a.pageCount) ?? 0,
632
+ currentPage: 1,
633
+ totalContentSize: { width: 0, height: 0 },
634
+ strategy: this.state.defaultStrategy,
635
+ pageGap: this.state.defaultPageGap,
636
+ visiblePages: [],
637
+ pageVisibilityMetrics: [],
638
+ renderedPageIndexes: [],
639
+ scrollOffset: { x: 0, y: 0 },
640
+ startSpacing: 0,
641
+ endSpacing: 0,
642
+ pageChangeState: defaultPageChangeState
643
+ };
644
+ }
645
+ // ─────────────────────────────────────────────────────────
646
+ // Page Change Management
647
+ // ─────────────────────────────────────────────────────────
648
+ startPageChange(documentId, targetPage, behavior = "smooth") {
649
+ const docState = this.getDocumentState(documentId);
650
+ if (!docState) return;
651
+ const pageChangeState = {
652
+ isChanging: true,
653
+ targetPage,
654
+ fromPage: docState.currentPage,
655
+ startTime: Date.now()
656
+ };
657
+ this.dispatch(updateDocumentScrollState(documentId, { pageChangeState }));
658
+ if (behavior === "instant") {
659
+ this.completePageChange(documentId);
471
660
  }
472
661
  }
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;
662
+ completePageChange(documentId) {
663
+ const docState = this.getDocumentState(documentId);
664
+ if (!docState || !docState.pageChangeState.isChanging) return;
665
+ const pageChangeState = {
666
+ isChanging: false,
667
+ targetPage: docState.pageChangeState.targetPage,
668
+ fromPage: docState.pageChangeState.fromPage,
669
+ startTime: docState.pageChangeState.startTime
670
+ };
671
+ this.dispatch(updateDocumentScrollState(documentId, { pageChangeState }));
672
+ }
673
+ // ─────────────────────────────────────────────────────────
674
+ // Layout & Metrics Computation
675
+ // ─────────────────────────────────────────────────────────
676
+ computeLayout(documentId, pages) {
677
+ const strategy = this.getStrategy(documentId);
678
+ const virtualItems = strategy.createVirtualItems(pages);
679
+ const totalContentSize = strategy.getTotalContentSize(virtualItems);
680
+ return { virtualItems, totalContentSize };
681
+ }
682
+ computeMetrics(documentId, vp, items) {
683
+ const coreDocState = this.getCoreDocumentOrThrow(documentId);
684
+ const docState = this.getDocumentState(documentId);
685
+ const strategy = this.getStrategy(documentId);
686
+ if (!docState) throw new Error(`Document state not found: ${documentId}`);
687
+ return strategy.handleScroll(vp, items ?? docState.virtualItems, coreDocState.scale);
688
+ }
689
+ // ─────────────────────────────────────────────────────────
690
+ // Commit (Single Source of Truth)
691
+ // ─────────────────────────────────────────────────────────
692
+ commitMetrics(documentId, metrics) {
693
+ const docState = this.getDocumentState(documentId);
694
+ if (!docState) return;
695
+ this.dispatch(updateDocumentScrollState(documentId, metrics));
696
+ this.scroll$.emit({ documentId, metrics });
697
+ if (metrics.currentPage !== docState.currentPage) {
698
+ this.pageChange$.emit({
699
+ documentId,
700
+ pageNumber: metrics.currentPage,
701
+ totalPages: docState.totalPages
702
+ });
703
+ }
704
+ this.pushScrollerLayout(documentId);
705
+ }
706
+ pushScrollerLayout(documentId) {
707
+ const emitter = this.scrollerLayoutEmitters.get(documentId);
708
+ if (!emitter) return;
709
+ try {
710
+ const layout = this.getScrollerLayout(documentId);
711
+ emitter.emit(layout);
712
+ } catch (error) {
480
713
  }
481
- this.strategy = newStrategy === ScrollStrategy.Horizontal ? new HorizontalScrollStrategy(this.strategyConfig) : new VerticalScrollStrategy(this.strategyConfig);
714
+ }
715
+ refreshDocumentLayout(documentId) {
716
+ const coreDoc = this.coreState.core.documents[documentId];
717
+ const docState = this.getDocumentState(documentId);
718
+ if (!coreDoc || !docState || coreDoc.status !== "loaded") return;
719
+ const pages = this.getSpreadPagesWithRotatedSize(documentId);
720
+ const layout = this.computeLayout(documentId, pages);
721
+ const viewport = this.viewport.forDocument(documentId);
722
+ const metrics = this.computeMetrics(documentId, viewport.getMetrics(), layout.virtualItems);
482
723
  this.dispatch(
483
- updateScrollState({
484
- strategy: newStrategy
724
+ updateDocumentScrollState(documentId, {
725
+ ...layout,
726
+ ...metrics
485
727
  })
486
728
  );
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
- }
729
+ this.layoutChange$.emit({ documentId, layout });
730
+ this.pushScrollerLayout(documentId);
731
+ }
732
+ getSpreadPagesWithRotatedSize(documentId) {
733
+ var _a, _b;
734
+ const id = documentId ?? this.getActiveDocumentId();
735
+ const coreDoc = this.coreState.core.documents[id];
736
+ if (!coreDoc) throw new Error(`Document ${id} not loaded`);
737
+ const spreadPages = ((_a = this.spread) == null ? void 0 : _a.forDocument(id).getSpreadPages()) || ((_b = coreDoc.document) == null ? void 0 : _b.pages.map((page) => [page])) || [];
738
+ return spreadPages.map(
739
+ (spread) => spread.map((page) => ({
740
+ ...page,
741
+ rotatedSize: transformSize(page.size, coreDoc.rotation, 1)
742
+ }))
743
+ );
497
744
  }
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
- };
745
+ // ─────────────────────────────────────────────────────────
746
+ // Core Operations
747
+ // ─────────────────────────────────────────────────────────
748
+ getCurrentPage(documentId) {
749
+ return this.getDocumentStateOrThrow(documentId).currentPage;
750
+ }
751
+ getTotalPages(documentId) {
752
+ return this.getDocumentStateOrThrow(documentId).totalPages;
518
753
  }
519
- scrollToPage(options) {
754
+ getPageChangeState(documentId) {
755
+ return this.getDocumentStateOrThrow(documentId).pageChangeState;
756
+ }
757
+ scrollToPage(options, documentId) {
758
+ const id = documentId ?? this.getActiveDocumentId();
759
+ const docState = this.getDocumentStateOrThrow(id);
760
+ const strategy = this.getStrategy(id);
761
+ const coreDoc = this.getCoreDocumentOrThrow(id);
520
762
  const { pageNumber, behavior = "smooth", pageCoordinates, center = false } = options;
521
- this.startPageChange(pageNumber, behavior);
522
- const virtualItems = this.getVirtualItemsFromState();
523
- const position = this.strategy.getScrollPositionForPage(
763
+ this.startPageChange(id, pageNumber, behavior);
764
+ const position = strategy.getScrollPositionForPage(
524
765
  pageNumber,
525
- virtualItems,
526
- this.currentScale,
527
- this.currentRotation,
766
+ docState.virtualItems,
767
+ coreDoc.scale,
768
+ coreDoc.rotation,
528
769
  pageCoordinates
529
770
  );
530
771
  if (position) {
531
- this.viewport.scrollTo({ ...position, behavior, center });
772
+ const viewport = this.viewport.forDocument(id);
773
+ viewport.scrollTo({ ...position, behavior, center });
532
774
  } else {
533
- this.completePageChange();
775
+ this.completePageChange(id);
534
776
  }
535
777
  }
536
- scrollToNextPage(behavior = "smooth") {
537
- const virtualItems = this.getVirtualItemsFromState();
538
- const currentItemIndex = virtualItems.findIndex(
539
- (item) => item.pageNumbers.includes(this.currentPage)
778
+ scrollToNextPage(behavior = "smooth", documentId) {
779
+ const id = documentId ?? this.getActiveDocumentId();
780
+ const docState = this.getDocumentStateOrThrow(id);
781
+ const strategy = this.getStrategy(id);
782
+ const coreDoc = this.getCoreDocumentOrThrow(id);
783
+ const currentItemIndex = docState.virtualItems.findIndex(
784
+ (item) => item.pageNumbers.includes(docState.currentPage)
540
785
  );
541
- if (currentItemIndex >= 0 && currentItemIndex < virtualItems.length - 1) {
542
- const nextItem = virtualItems[currentItemIndex + 1];
786
+ if (currentItemIndex >= 0 && currentItemIndex < docState.virtualItems.length - 1) {
787
+ const nextItem = docState.virtualItems[currentItemIndex + 1];
543
788
  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
789
+ this.startPageChange(id, targetPage, behavior);
790
+ const position = strategy.getScrollPositionForPage(
791
+ targetPage,
792
+ docState.virtualItems,
793
+ coreDoc.scale,
794
+ coreDoc.rotation
550
795
  );
551
796
  if (position) {
552
- this.viewport.scrollTo({ ...position, behavior });
797
+ const viewport = this.viewport.forDocument(id);
798
+ viewport.scrollTo({ ...position, behavior });
553
799
  } else {
554
- this.completePageChange();
800
+ this.completePageChange(id);
555
801
  }
556
802
  }
557
803
  }
558
- scrollToPreviousPage(behavior = "smooth") {
559
- const virtualItems = this.getVirtualItemsFromState();
560
- const currentItemIndex = virtualItems.findIndex(
561
- (item) => item.pageNumbers.includes(this.currentPage)
804
+ scrollToPreviousPage(behavior = "smooth", documentId) {
805
+ const id = documentId ?? this.getActiveDocumentId();
806
+ const docState = this.getDocumentStateOrThrow(id);
807
+ const strategy = this.getStrategy(id);
808
+ const coreDoc = this.coreState.core.documents[id];
809
+ const currentItemIndex = docState.virtualItems.findIndex(
810
+ (item) => item.pageNumbers.includes(docState.currentPage)
562
811
  );
563
812
  if (currentItemIndex > 0) {
564
- const prevItem = virtualItems[currentItemIndex - 1];
813
+ const prevItem = docState.virtualItems[currentItemIndex - 1];
565
814
  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
815
+ this.startPageChange(id, targetPage, behavior);
816
+ const position = strategy.getScrollPositionForPage(
817
+ targetPage,
818
+ docState.virtualItems,
819
+ coreDoc.scale,
820
+ coreDoc.rotation
572
821
  );
573
822
  if (position) {
574
- this.viewport.scrollTo({ ...position, behavior });
823
+ const viewport = this.viewport.forDocument(id);
824
+ viewport.scrollTo({ ...position, behavior });
575
825
  } else {
576
- this.completePageChange();
826
+ this.completePageChange(id);
577
827
  }
578
828
  }
579
829
  }
580
- getMetrics(viewport) {
581
- const metrics = viewport || this.viewport.getMetrics();
582
- const virtualItems = this.getVirtualItemsFromState();
583
- return this.strategy.handleScroll(metrics, virtualItems, this.currentScale);
830
+ getMetrics(viewport, documentId) {
831
+ const id = documentId ?? this.getActiveDocumentId();
832
+ if (viewport) {
833
+ return this.computeMetrics(id, viewport);
834
+ }
835
+ const viewportScope = this.viewport.forDocument(id);
836
+ return this.computeMetrics(id, viewportScope.getMetrics());
584
837
  }
585
- getLayout() {
838
+ getLayout(documentId) {
839
+ const docState = this.getDocumentStateOrThrow(documentId);
586
840
  return {
587
- virtualItems: this.state.virtualItems,
588
- totalContentSize: this.state.totalContentSize
841
+ virtualItems: docState.virtualItems,
842
+ totalContentSize: docState.totalContentSize
589
843
  };
590
844
  }
591
- getRectPositionForPage(pageIndex, rect, scale, rotation) {
592
- return this.strategy.getRectPositionForPage(
845
+ getRectPositionForPage(pageIndex, rect, scale, rotation, documentId) {
846
+ const id = documentId ?? this.getActiveDocumentId();
847
+ const docState = this.getDocumentStateOrThrow(id);
848
+ const strategy = this.getStrategy(id);
849
+ const coreDoc = this.getCoreDocumentOrThrow(id);
850
+ return strategy.getRectPositionForPage(
593
851
  pageIndex + 1,
594
- this.state.virtualItems,
595
- scale ?? this.currentScale,
596
- rotation ?? this.currentRotation,
852
+ docState.virtualItems,
853
+ scale ?? coreDoc.scale,
854
+ rotation ?? coreDoc.rotation,
597
855
  rect
598
856
  );
599
857
  }
858
+ setScrollStrategyForDocument(newStrategy, documentId) {
859
+ const id = documentId ?? this.getActiveDocumentId();
860
+ const docState = this.getDocumentState(id);
861
+ if (!docState || docState.strategy === newStrategy) return;
862
+ const strategy = this.createStrategy(newStrategy);
863
+ this.strategies.set(id, strategy);
864
+ this.dispatch(setScrollStrategy(id, newStrategy));
865
+ this.refreshDocumentLayout(id);
866
+ }
867
+ // ─────────────────────────────────────────────────────────
868
+ // Store Update Handlers
869
+ // ─────────────────────────────────────────────────────────
870
+ onStoreUpdated(prevState, newState) {
871
+ for (const documentId in newState.documents) {
872
+ const prevDoc = prevState.documents[documentId];
873
+ const newDoc = newState.documents[documentId];
874
+ if (prevDoc !== newDoc) {
875
+ this.state$.emit(newDoc);
876
+ if ((prevDoc == null ? void 0 : prevDoc.pageChangeState) !== newDoc.pageChangeState) {
877
+ this.pageChangeState$.emit({
878
+ documentId,
879
+ state: newDoc.pageChangeState
880
+ });
881
+ }
882
+ this.pushScrollerLayout(documentId);
883
+ }
884
+ }
885
+ }
886
+ // ─────────────────────────────────────────────────────────
887
+ // Lifecycle
888
+ // ─────────────────────────────────────────────────────────
600
889
  async initialize() {
890
+ this.logger.info("ScrollPlugin", "Initialize", "Scroll plugin initialized");
601
891
  }
602
892
  async destroy() {
603
- this.layout$.clear();
604
- this.scroll$.clear();
893
+ this.strategies.clear();
894
+ this.layoutReady.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