@embedpdf/plugin-selection 2.1.2 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +253 -46
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/handlers/index.d.ts +2 -0
  6. package/dist/lib/handlers/marquee-selection.handler.d.ts +28 -0
  7. package/dist/lib/handlers/text-selection.handler.d.ts +27 -0
  8. package/dist/lib/selection-plugin.d.ts +23 -2
  9. package/dist/lib/types.d.ts +54 -1
  10. package/dist/preact/index.cjs +1 -1
  11. package/dist/preact/index.cjs.map +1 -1
  12. package/dist/preact/index.js +45 -0
  13. package/dist/preact/index.js.map +1 -1
  14. package/dist/react/index.cjs +1 -1
  15. package/dist/react/index.cjs.map +1 -1
  16. package/dist/react/index.js +45 -0
  17. package/dist/react/index.js.map +1 -1
  18. package/dist/shared/components/index.d.ts +1 -0
  19. package/dist/shared/components/marquee-selection.d.ts +23 -0
  20. package/dist/shared-preact/components/index.d.ts +1 -0
  21. package/dist/shared-preact/components/marquee-selection.d.ts +23 -0
  22. package/dist/shared-react/components/index.d.ts +1 -0
  23. package/dist/shared-react/components/marquee-selection.d.ts +23 -0
  24. package/dist/svelte/components/MarqueeSelection.svelte.d.ts +17 -0
  25. package/dist/svelte/components/index.d.ts +1 -0
  26. package/dist/svelte/index.cjs +1 -1
  27. package/dist/svelte/index.cjs.map +1 -1
  28. package/dist/svelte/index.js +58 -2
  29. package/dist/svelte/index.js.map +1 -1
  30. package/dist/vue/components/index.d.ts +1 -0
  31. package/dist/vue/components/marquee-selection.vue.d.ts +20 -0
  32. package/dist/vue/index.cjs +1 -1
  33. package/dist/vue/index.cjs.map +1 -1
  34. package/dist/vue/index.js +68 -6
  35. package/dist/vue/index.js.map +1 -1
  36. package/package.json +9 -9
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { BasePlugin, createScopedEmitter, REFRESH_PAGES } from "@embedpdf/core";
2
- import { boundingRect, Task, ignore, PdfErrorCode, PdfTaskHelper } from "@embedpdf/models";
1
+ import { clamp, BasePlugin, createScopedEmitter, REFRESH_PAGES } from "@embedpdf/core";
2
+ import { boundingRect, Task, ignore, PdfErrorCode, PdfTaskHelper, PdfPermissionFlag } from "@embedpdf/models";
3
3
  const SELECTION_PLUGIN_ID = "selection";
4
4
  const manifest = {
5
5
  id: SELECTION_PLUGIN_ID,
@@ -326,6 +326,90 @@ function mergeAdjacentRects(textRuns) {
326
326
  }
327
327
  return results;
328
328
  }
329
+ function createTextSelectionHandler(opts) {
330
+ return {
331
+ onPointerDown: (point, evt, modeId) => {
332
+ if (!opts.isEnabled(modeId)) return;
333
+ opts.onClear();
334
+ const geo = opts.getGeometry();
335
+ if (geo) {
336
+ const g = glyphAt(geo, point);
337
+ if (g !== -1) {
338
+ opts.onBegin(g);
339
+ evt.stopImmediatePropagation();
340
+ }
341
+ }
342
+ },
343
+ onPointerMove: (point, evt, modeId) => {
344
+ if (!opts.isEnabled(modeId)) return;
345
+ const geo = opts.getGeometry();
346
+ if (geo) {
347
+ const g = glyphAt(geo, point);
348
+ opts.setCursor(g !== -1 ? "text" : null);
349
+ if (opts.isSelecting() && g !== -1) {
350
+ opts.onUpdate(g);
351
+ evt.stopImmediatePropagation();
352
+ }
353
+ }
354
+ },
355
+ onPointerUp: (_point, _evt, modeId) => {
356
+ if (!opts.isEnabled(modeId)) return;
357
+ opts.onEnd();
358
+ },
359
+ onHandlerActiveEnd: (modeId) => {
360
+ if (!opts.isEnabled(modeId)) return;
361
+ opts.onClear();
362
+ }
363
+ };
364
+ }
365
+ function createMarqueeSelectionHandler(opts) {
366
+ const { pageSize, scale, minDragPx = 5 } = opts;
367
+ let start = null;
368
+ let last = null;
369
+ return {
370
+ onPointerDown: (pos, evt, modeId) => {
371
+ var _a;
372
+ if (!opts.isEnabled(modeId)) return;
373
+ start = pos;
374
+ last = { origin: { x: pos.x, y: pos.y }, size: { width: 0, height: 0 } };
375
+ opts.onBegin(pos);
376
+ (_a = evt.setPointerCapture) == null ? void 0 : _a.call(evt);
377
+ },
378
+ onPointerMove: (pos, _evt, modeId) => {
379
+ if (!start || !opts.isEnabled(modeId)) return;
380
+ const x = clamp(pos.x, 0, pageSize.width);
381
+ const y = clamp(pos.y, 0, pageSize.height);
382
+ last = {
383
+ origin: { x: Math.min(start.x, x), y: Math.min(start.y, y) },
384
+ size: { width: Math.abs(x - start.x), height: Math.abs(y - start.y) }
385
+ };
386
+ opts.onChange(last);
387
+ },
388
+ onPointerUp: (_pos, evt, modeId) => {
389
+ var _a;
390
+ if (!opts.isEnabled(modeId)) return;
391
+ if (last && start) {
392
+ const dragPx = Math.max(last.size.width, last.size.height) * scale;
393
+ if (dragPx > minDragPx) {
394
+ opts.onEnd(last);
395
+ } else {
396
+ opts.onCancel();
397
+ }
398
+ }
399
+ start = null;
400
+ last = null;
401
+ (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
402
+ },
403
+ onPointerCancel: (_pos, evt, modeId) => {
404
+ var _a;
405
+ if (!opts.isEnabled(modeId)) return;
406
+ start = null;
407
+ last = null;
408
+ opts.onCancel();
409
+ (_a = evt.releasePointerCapture) == null ? void 0 : _a.call(evt);
410
+ }
411
+ };
412
+ }
329
413
  const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
330
414
  constructor(id, registry, config) {
331
415
  var _a, _b, _c;
@@ -333,6 +417,8 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
333
417
  this.enabledModesPerDoc = /* @__PURE__ */ new Map();
334
418
  this.selecting = /* @__PURE__ */ new Map();
335
419
  this.anchor = /* @__PURE__ */ new Map();
420
+ this.marqueeEnabled = /* @__PURE__ */ new Map();
421
+ this.marqueePage = /* @__PURE__ */ new Map();
336
422
  this.pageCallbacks = /* @__PURE__ */ new Map();
337
423
  this.menuPlacement$ = createScopedEmitter((documentId, placement) => ({ documentId, placement }));
338
424
  this.selChange$ = createScopedEmitter((documentId, selection) => ({ documentId, selection }));
@@ -348,8 +434,18 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
348
434
  (documentId) => ({ documentId }),
349
435
  { cache: false }
350
436
  );
437
+ this.marqueeChange$ = createScopedEmitter((documentId, data) => ({ documentId, pageIndex: data.pageIndex, rect: data.rect }), {
438
+ cache: false
439
+ });
440
+ this.marqueeEnd$ = createScopedEmitter(
441
+ (documentId, data) => ({ documentId, pageIndex: data.pageIndex, rect: data.rect }),
442
+ {
443
+ cache: false
444
+ }
445
+ );
351
446
  this.viewportCapability = null;
352
447
  this.scrollCapability = null;
448
+ this.config = config;
353
449
  this.menuHeight = config.menuHeight ?? 40;
354
450
  const imPlugin = registry.getPlugin("interaction-manager");
355
451
  if (!imPlugin) {
@@ -379,10 +475,15 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
379
475
  /* ── life-cycle ────────────────────────────────────────── */
380
476
  onDocumentLoadingStarted(documentId) {
381
477
  this.dispatch(initSelectionState(documentId, initialSelectionDocumentState));
382
- this.enabledModesPerDoc.set(documentId, /* @__PURE__ */ new Set(["pointerMode"]));
478
+ this.enabledModesPerDoc.set(
479
+ documentId,
480
+ /* @__PURE__ */ new Map([["pointerMode", { showRects: true }]])
481
+ );
383
482
  this.pageCallbacks.set(documentId, /* @__PURE__ */ new Map());
384
483
  this.selecting.set(documentId, false);
385
484
  this.anchor.set(documentId, void 0);
485
+ const marqueeConfig = this.config.marquee;
486
+ this.marqueeEnabled.set(documentId, (marqueeConfig == null ? void 0 : marqueeConfig.enabled) !== false);
386
487
  }
387
488
  onDocumentClosed(documentId) {
388
489
  this.dispatch(cleanupSelectionState(documentId));
@@ -390,12 +491,16 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
390
491
  this.pageCallbacks.delete(documentId);
391
492
  this.selecting.delete(documentId);
392
493
  this.anchor.delete(documentId);
494
+ this.marqueeEnabled.delete(documentId);
495
+ this.marqueePage.delete(documentId);
393
496
  this.selChange$.clearScope(documentId);
394
497
  this.textRetrieved$.clearScope(documentId);
395
498
  this.copyToClipboard$.clearScope(documentId);
396
499
  this.beginSelection$.clearScope(documentId);
397
500
  this.endSelection$.clearScope(documentId);
398
501
  this.menuPlacement$.clearScope(documentId);
502
+ this.marqueeChange$.clearScope(documentId);
503
+ this.marqueeEnd$.clearScope(documentId);
399
504
  }
400
505
  async initialize() {
401
506
  }
@@ -406,6 +511,8 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
406
511
  this.beginSelection$.clear();
407
512
  this.endSelection$.clear();
408
513
  this.menuPlacement$.clear();
514
+ this.marqueeChange$.clear();
515
+ this.marqueeEnd$.clear();
409
516
  super.destroy();
410
517
  }
411
518
  /* ── capability exposed to UI / other plugins ─────────── */
@@ -423,14 +530,17 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
423
530
  clear: (docId) => this.clearSelection(getDocId(docId)),
424
531
  copyToClipboard: (docId) => this.copyToClipboard(getDocId(docId)),
425
532
  getState: (docId) => this.getDocumentState(getDocId(docId)),
426
- enableForMode: (modeId, docId) => {
533
+ enableForMode: (modeId, options, docId) => {
427
534
  var _a;
428
- return (_a = this.enabledModesPerDoc.get(getDocId(docId))) == null ? void 0 : _a.add(modeId);
535
+ return (_a = this.enabledModesPerDoc.get(getDocId(docId))) == null ? void 0 : _a.set(modeId, { showRects: true, ...options });
429
536
  },
430
537
  isEnabledForMode: (modeId, docId) => {
431
538
  var _a;
432
539
  return ((_a = this.enabledModesPerDoc.get(getDocId(docId))) == null ? void 0 : _a.has(modeId)) ?? false;
433
540
  },
541
+ // Marquee selection
542
+ setMarqueeEnabled: (enabled, docId) => this.setMarqueeEnabled(getDocId(docId), enabled),
543
+ isMarqueeEnabled: (docId) => this.isMarqueeEnabled(getDocId(docId)),
434
544
  // Document-scoped operations
435
545
  forDocument: this.createSelectionScope.bind(this),
436
546
  // Global events
@@ -438,7 +548,10 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
438
548
  onSelectionChange: this.selChange$.onGlobal,
439
549
  onTextRetrieved: this.textRetrieved$.onGlobal,
440
550
  onBeginSelection: this.beginSelection$.onGlobal,
441
- onEndSelection: this.endSelection$.onGlobal
551
+ onEndSelection: this.endSelection$.onGlobal,
552
+ // Marquee selection events
553
+ onMarqueeChange: this.marqueeChange$.onGlobal,
554
+ onMarqueeEnd: this.marqueeEnd$.onGlobal
442
555
  };
443
556
  }
444
557
  createSelectionScope(documentId) {
@@ -453,11 +566,15 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
453
566
  clear: () => this.clearSelection(documentId),
454
567
  copyToClipboard: () => this.copyToClipboard(documentId),
455
568
  getState: () => this.getDocumentState(documentId),
569
+ setMarqueeEnabled: (enabled) => this.setMarqueeEnabled(documentId, enabled),
570
+ isMarqueeEnabled: () => this.isMarqueeEnabled(documentId),
456
571
  onSelectionChange: this.selChange$.forScope(documentId),
457
572
  onTextRetrieved: this.textRetrieved$.forScope(documentId),
458
573
  onCopyToClipboard: this.copyToClipboard$.forScope(documentId),
459
574
  onBeginSelection: this.beginSelection$.forScope(documentId),
460
- onEndSelection: this.endSelection$.forScope(documentId)
575
+ onEndSelection: this.endSelection$.forScope(documentId),
576
+ onMarqueeChange: this.marqueeChange$.forScope(documentId),
577
+ onMarqueeEnd: this.marqueeEnd$.forScope(documentId)
461
578
  };
462
579
  }
463
580
  getDocumentState(documentId) {
@@ -476,6 +593,10 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
476
593
  onMenuPlacement(documentId, listener) {
477
594
  return this.menuPlacement$.forScope(documentId)(listener);
478
595
  }
596
+ /**
597
+ * Register text selection on a page. Uses `registerAlways` so any plugin
598
+ * can enable text selection for their mode via `enableForMode()`.
599
+ */
479
600
  registerSelectionOnPage(opts) {
480
601
  var _a;
481
602
  const { documentId, pageIndex, onRectsChange } = opts;
@@ -497,45 +618,19 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
497
618
  rects: selectRectsForPage(docState, pageIndex),
498
619
  boundingRect: selectBoundingRectForPage(docState, pageIndex)
499
620
  });
500
- const handlers = {
501
- onPointerDown: (point, _evt, modeId) => {
502
- if (!(enabledModes == null ? void 0 : enabledModes.has(modeId))) return;
503
- this.clearSelection(documentId);
504
- const cached = this.getDocumentState(documentId).geometry[pageIndex];
505
- if (cached) {
506
- const g = glyphAt(cached, point);
507
- if (g !== -1) {
508
- this.beginSelection(documentId, pageIndex, g);
509
- }
510
- }
511
- },
512
- onPointerMove: (point, _evt, modeId) => {
513
- if (!(enabledModes == null ? void 0 : enabledModes.has(modeId))) return;
514
- const cached = this.getDocumentState(documentId).geometry[pageIndex];
515
- if (cached) {
516
- const g = glyphAt(cached, point);
517
- if (g !== -1) {
518
- interactionScope.setCursor("selection-text", "text", 10);
519
- } else {
520
- interactionScope.removeCursor("selection-text");
521
- }
522
- if (this.selecting.get(documentId) && g !== -1) {
523
- this.updateSelection(documentId, pageIndex, g);
524
- }
525
- }
526
- },
527
- onPointerUp: (_point, _evt, modeId) => {
528
- if (!(enabledModes == null ? void 0 : enabledModes.has(modeId))) return;
529
- this.endSelection(documentId);
530
- },
531
- onHandlerActiveEnd: (modeId) => {
532
- if (!(enabledModes == null ? void 0 : enabledModes.has(modeId))) return;
533
- this.clearSelection(documentId);
534
- }
535
- };
621
+ const textHandler = createTextSelectionHandler({
622
+ getGeometry: () => this.getDocumentState(documentId).geometry[pageIndex],
623
+ isEnabled: (modeId) => (enabledModes == null ? void 0 : enabledModes.has(modeId)) ?? false,
624
+ onBegin: (g) => this.beginSelection(documentId, pageIndex, g),
625
+ onUpdate: (g) => this.updateSelection(documentId, pageIndex, g),
626
+ onEnd: () => this.endSelection(documentId),
627
+ onClear: () => this.clearSelection(documentId),
628
+ isSelecting: () => this.selecting.get(documentId) ?? false,
629
+ setCursor: (cursor) => cursor ? interactionScope.setCursor("selection-text", cursor, 10) : interactionScope.removeCursor("selection-text")
630
+ });
536
631
  const unregisterHandlers = this.interactionManagerCapability.registerAlways({
537
632
  scope: { type: "page", documentId, pageIndex },
538
- handlers
633
+ handlers: textHandler
539
634
  });
540
635
  return () => {
541
636
  var _a2;
@@ -544,6 +639,72 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
544
639
  geoTask.abort({ code: PdfErrorCode.Cancelled, message: "Cleanup" });
545
640
  };
546
641
  }
642
+ /**
643
+ * Register marquee selection on a page. Uses `registerHandlers` with `pointerMode`
644
+ * only - marquee selection is only active in the default pointer mode.
645
+ */
646
+ registerMarqueeOnPage(opts) {
647
+ var _a;
648
+ const { documentId, pageIndex, scale, onRectChange } = opts;
649
+ const docState = this.state.documents[documentId];
650
+ if (!docState) {
651
+ this.logger.warn(
652
+ "SelectionPlugin",
653
+ "RegisterMarqueeFailed",
654
+ `Cannot register marquee on page ${pageIndex} for document ${documentId}: document state not initialized.`
655
+ );
656
+ return () => {
657
+ };
658
+ }
659
+ const coreDoc = this.coreState.core.documents[documentId];
660
+ if (!coreDoc || !coreDoc.document) {
661
+ this.logger.warn(
662
+ "SelectionPlugin",
663
+ "DocumentNotFound",
664
+ `Cannot register marquee on page ${pageIndex}: document not found`
665
+ );
666
+ return () => {
667
+ };
668
+ }
669
+ const page = coreDoc.document.pages[pageIndex];
670
+ if (!page) {
671
+ this.logger.warn(
672
+ "SelectionPlugin",
673
+ "PageNotFound",
674
+ `Cannot register marquee on page ${pageIndex}: page not found`
675
+ );
676
+ return () => {
677
+ };
678
+ }
679
+ const pageSize = page.size;
680
+ const minDragPx = ((_a = this.config.marquee) == null ? void 0 : _a.minDragPx) ?? 5;
681
+ const marqueeHandler = createMarqueeSelectionHandler({
682
+ pageSize,
683
+ scale,
684
+ minDragPx,
685
+ isEnabled: () => this.marqueeEnabled.get(documentId) !== false,
686
+ onBegin: (pos) => this.beginMarquee(documentId, pageIndex, pos),
687
+ onChange: (rect) => {
688
+ this.updateMarquee(documentId, pageIndex, rect);
689
+ onRectChange(rect);
690
+ },
691
+ onEnd: (rect) => {
692
+ this.endMarquee(documentId, pageIndex, rect);
693
+ onRectChange(null);
694
+ },
695
+ onCancel: () => {
696
+ this.cancelMarquee(documentId);
697
+ onRectChange(null);
698
+ }
699
+ });
700
+ const unregisterHandlers = this.interactionManagerCapability.registerHandlers({
701
+ documentId,
702
+ pageIndex,
703
+ modeId: "pointerMode",
704
+ handlers: marqueeHandler
705
+ });
706
+ return unregisterHandlers;
707
+ }
547
708
  /**
548
709
  * Helper to calculate viewport relative metrics for a page rect.
549
710
  * Returns null if the rect cannot be converted to viewport space.
@@ -627,12 +788,14 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
627
788
  this.menuPlacement$.emit(documentId, null);
628
789
  }
629
790
  notifyPage(documentId, pageIndex) {
630
- var _a;
791
+ var _a, _b;
631
792
  const callback = (_a = this.pageCallbacks.get(documentId)) == null ? void 0 : _a.get(pageIndex);
632
793
  if (callback) {
633
794
  const docState = this.getDocumentState(documentId);
634
795
  const mode = this.interactionManagerCapability.forDocument(documentId).getActiveMode();
635
- if (mode === "pointerMode") {
796
+ const modeConfig = (_b = this.enabledModesPerDoc.get(documentId)) == null ? void 0 : _b.get(mode);
797
+ const shouldShowRects = modeConfig && modeConfig.showRects !== false;
798
+ if (shouldShowRects) {
636
799
  callback({
637
800
  rects: selectRectsForPage(docState, pageIndex),
638
801
  boundingRect: selectBoundingRectForPage(docState, pageIndex)
@@ -717,6 +880,17 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
717
880
  this.dispatch(setSlices(documentId, allSlices));
718
881
  }
719
882
  getSelectedText(documentId) {
883
+ if (!this.checkPermission(documentId, PdfPermissionFlag.CopyContents)) {
884
+ this.logger.debug(
885
+ "SelectionPlugin",
886
+ "GetSelectedText",
887
+ `Cannot get selected text: document ${documentId} lacks CopyContents permission`
888
+ );
889
+ return PdfTaskHelper.reject({
890
+ code: PdfErrorCode.Security,
891
+ message: "Document lacks CopyContents permission"
892
+ });
893
+ }
720
894
  const coreDoc = this.getCoreDocument(documentId);
721
895
  const docState = this.getDocumentState(documentId);
722
896
  if (!(coreDoc == null ? void 0 : coreDoc.document) || !docState.selection) {
@@ -739,11 +913,44 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
739
913
  return task;
740
914
  }
741
915
  copyToClipboard(documentId) {
916
+ if (!this.checkPermission(documentId, PdfPermissionFlag.CopyContents)) {
917
+ this.logger.debug(
918
+ "SelectionPlugin",
919
+ "CopyToClipboard",
920
+ `Cannot copy to clipboard: document ${documentId} lacks CopyContents permission`
921
+ );
922
+ return;
923
+ }
742
924
  const text = this.getSelectedText(documentId);
743
925
  text.wait((text2) => {
744
926
  this.copyToClipboard$.emit(documentId, text2.join("\n"));
745
927
  }, ignore);
746
928
  }
929
+ /* ── marquee selection state updates ─────────────────────── */
930
+ beginMarquee(documentId, pageIndex, _startPos) {
931
+ this.marqueePage.set(documentId, pageIndex);
932
+ }
933
+ updateMarquee(documentId, pageIndex, rect) {
934
+ this.marqueeChange$.emit(documentId, { pageIndex, rect });
935
+ }
936
+ endMarquee(documentId, pageIndex, rect) {
937
+ this.marqueeEnd$.emit(documentId, { pageIndex, rect });
938
+ this.marqueeChange$.emit(documentId, { pageIndex, rect: null });
939
+ this.marqueePage.delete(documentId);
940
+ }
941
+ cancelMarquee(documentId) {
942
+ const pageIndex = this.marqueePage.get(documentId);
943
+ if (pageIndex !== void 0) {
944
+ this.marqueeChange$.emit(documentId, { pageIndex, rect: null });
945
+ this.marqueePage.delete(documentId);
946
+ }
947
+ }
948
+ setMarqueeEnabled(documentId, enabled) {
949
+ this.marqueeEnabled.set(documentId, enabled);
950
+ }
951
+ isMarqueeEnabled(documentId) {
952
+ return this.marqueeEnabled.get(documentId) !== false;
953
+ }
747
954
  };
748
955
  _SelectionPlugin.id = "selection";
749
956
  let SelectionPlugin = _SelectionPlugin;