@embedpdf/plugin-selection 1.5.0 → 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.
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +445 -166
- package/dist/index.js.map +1 -1
- package/dist/lib/actions.d.ts +61 -24
- package/dist/lib/reducer.d.ts +2 -1
- package/dist/lib/selection-plugin.d.ts +25 -5
- package/dist/lib/selectors.d.ts +7 -7
- package/dist/lib/types.d.ts +62 -9
- package/dist/preact/index.cjs +1 -1
- package/dist/preact/index.cjs.map +1 -1
- package/dist/preact/index.js +88 -34
- package/dist/preact/index.js.map +1 -1
- package/dist/preact/utils.d.ts +1 -0
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +88 -34
- package/dist/react/index.js.map +1 -1
- package/dist/react/utils.d.ts +1 -0
- package/dist/shared/components/selection-layer.d.ts +7 -2
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/types.d.ts +7 -0
- package/dist/shared-preact/components/selection-layer.d.ts +7 -2
- package/dist/shared-preact/index.d.ts +1 -0
- package/dist/shared-preact/types.d.ts +7 -0
- package/dist/shared-react/components/selection-layer.d.ts +7 -2
- package/dist/shared-react/index.d.ts +1 -0
- package/dist/shared-react/types.d.ts +7 -0
- package/dist/svelte/components/SelectionLayer.svelte.d.ts +13 -2
- package/dist/svelte/index.cjs +1 -1
- package/dist/svelte/index.cjs.map +1 -1
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.js +162 -34
- package/dist/svelte/index.js.map +1 -1
- package/dist/svelte/types.d.ts +7 -0
- package/dist/vue/components/copy-to-clipboard.vue.d.ts +2 -1
- package/dist/vue/components/selection-layer.vue.d.ts +27 -3
- package/dist/vue/index.cjs +1 -1
- package/dist/vue/index.cjs.map +1 -1
- package/dist/vue/index.d.ts +1 -0
- package/dist/vue/index.js +137 -43
- package/dist/vue/index.js.map +1 -1
- package/dist/vue/types.d.ts +7 -0
- package/package.json +9 -8
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BasePlugin,
|
|
1
|
+
import { BasePlugin, createScopedEmitter, REFRESH_PAGES } from "@embedpdf/core";
|
|
2
2
|
import { boundingRect, Task, ignore, PdfErrorCode, PdfTaskHelper } from "@embedpdf/models";
|
|
3
3
|
const SELECTION_PLUGIN_ID = "selection";
|
|
4
4
|
const manifest = {
|
|
@@ -7,36 +7,156 @@ const manifest = {
|
|
|
7
7
|
version: "1.0.0",
|
|
8
8
|
provides: ["selection"],
|
|
9
9
|
requires: ["interaction-manager"],
|
|
10
|
-
optional: [],
|
|
10
|
+
optional: ["viewport", "scroll"],
|
|
11
11
|
defaultConfig: {
|
|
12
12
|
enabled: true
|
|
13
13
|
}
|
|
14
14
|
};
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
15
|
+
const INIT_SELECTION_STATE = "SELECTION/INIT_STATE";
|
|
16
|
+
const CLEANUP_SELECTION_STATE = "SELECTION/CLEANUP_STATE";
|
|
17
|
+
const CACHE_PAGE_GEOMETRY = "SELECTION/CACHE_PAGE_GEOMETRY";
|
|
18
|
+
const SET_SELECTION = "SELECTION/SET_SELECTION";
|
|
19
|
+
const START_SELECTION = "SELECTION/START_SELECTION";
|
|
20
|
+
const END_SELECTION = "SELECTION/END_SELECTION";
|
|
21
|
+
const CLEAR_SELECTION = "SELECTION/CLEAR_SELECTION";
|
|
22
|
+
const SET_RECTS = "SELECTION/SET_RECTS";
|
|
23
|
+
const SET_SLICES = "SELECTION/SET_SLICES";
|
|
24
|
+
const RESET = "SELECTION/RESET";
|
|
25
|
+
const initSelectionState = (documentId, state) => ({
|
|
26
|
+
type: INIT_SELECTION_STATE,
|
|
27
|
+
payload: { documentId, state }
|
|
28
|
+
});
|
|
29
|
+
const cleanupSelectionState = (documentId) => ({
|
|
30
|
+
type: CLEANUP_SELECTION_STATE,
|
|
31
|
+
payload: documentId
|
|
32
|
+
});
|
|
33
|
+
const cachePageGeometry = (documentId, page, geo) => ({
|
|
24
34
|
type: CACHE_PAGE_GEOMETRY,
|
|
25
|
-
payload: { page, geo }
|
|
35
|
+
payload: { documentId, page, geo }
|
|
26
36
|
});
|
|
27
|
-
const setSelection = (sel) => ({
|
|
37
|
+
const setSelection = (documentId, sel) => ({
|
|
28
38
|
type: SET_SELECTION,
|
|
29
|
-
payload: sel
|
|
39
|
+
payload: { documentId, selection: sel }
|
|
40
|
+
});
|
|
41
|
+
const startSelection = (documentId) => ({
|
|
42
|
+
type: START_SELECTION,
|
|
43
|
+
payload: { documentId }
|
|
30
44
|
});
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
const endSelection = (documentId) => ({
|
|
46
|
+
type: END_SELECTION,
|
|
47
|
+
payload: { documentId }
|
|
48
|
+
});
|
|
49
|
+
const clearSelection = (documentId) => ({
|
|
50
|
+
type: CLEAR_SELECTION,
|
|
51
|
+
payload: { documentId }
|
|
52
|
+
});
|
|
53
|
+
const setRects = (documentId, allRects) => ({
|
|
35
54
|
type: SET_RECTS,
|
|
36
|
-
payload: allRects
|
|
55
|
+
payload: { documentId, rects: allRects }
|
|
56
|
+
});
|
|
57
|
+
const setSlices = (documentId, slices) => ({ type: SET_SLICES, payload: { documentId, slices } });
|
|
58
|
+
const initialSelectionDocumentState = {
|
|
59
|
+
geometry: {},
|
|
60
|
+
rects: {},
|
|
61
|
+
slices: {},
|
|
62
|
+
selection: null,
|
|
63
|
+
active: false,
|
|
64
|
+
selecting: false
|
|
65
|
+
};
|
|
66
|
+
const initialState = {
|
|
67
|
+
documents: {}
|
|
68
|
+
};
|
|
69
|
+
const updateDocState = (state, documentId, newDocState) => ({
|
|
70
|
+
...state,
|
|
71
|
+
documents: {
|
|
72
|
+
...state.documents,
|
|
73
|
+
[documentId]: newDocState
|
|
74
|
+
}
|
|
37
75
|
});
|
|
38
|
-
const
|
|
39
|
-
|
|
76
|
+
const selectionReducer = (state = initialState, action) => {
|
|
77
|
+
switch (action.type) {
|
|
78
|
+
case INIT_SELECTION_STATE: {
|
|
79
|
+
const { documentId, state: docState } = action.payload;
|
|
80
|
+
return updateDocState(state, documentId, docState);
|
|
81
|
+
}
|
|
82
|
+
case CLEANUP_SELECTION_STATE: {
|
|
83
|
+
const documentId = action.payload;
|
|
84
|
+
const { [documentId]: removed, ...remaining } = state.documents;
|
|
85
|
+
return {
|
|
86
|
+
...state,
|
|
87
|
+
documents: remaining
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
case CACHE_PAGE_GEOMETRY: {
|
|
91
|
+
const { documentId, page, geo } = action.payload;
|
|
92
|
+
const docState = state.documents[documentId];
|
|
93
|
+
if (!docState) return state;
|
|
94
|
+
return updateDocState(state, documentId, {
|
|
95
|
+
...docState,
|
|
96
|
+
geometry: { ...docState.geometry, [page]: geo }
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
case SET_SELECTION: {
|
|
100
|
+
const { documentId, selection } = action.payload;
|
|
101
|
+
const docState = state.documents[documentId];
|
|
102
|
+
if (!docState) return state;
|
|
103
|
+
return updateDocState(state, documentId, {
|
|
104
|
+
...docState,
|
|
105
|
+
selection,
|
|
106
|
+
active: true
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
case START_SELECTION: {
|
|
110
|
+
const { documentId } = action.payload;
|
|
111
|
+
const docState = state.documents[documentId];
|
|
112
|
+
if (!docState) return state;
|
|
113
|
+
return updateDocState(state, documentId, {
|
|
114
|
+
...docState,
|
|
115
|
+
selecting: true,
|
|
116
|
+
selection: null,
|
|
117
|
+
rects: {}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
case END_SELECTION: {
|
|
121
|
+
const { documentId } = action.payload;
|
|
122
|
+
const docState = state.documents[documentId];
|
|
123
|
+
if (!docState) return state;
|
|
124
|
+
return updateDocState(state, documentId, { ...docState, selecting: false });
|
|
125
|
+
}
|
|
126
|
+
case CLEAR_SELECTION: {
|
|
127
|
+
const { documentId } = action.payload;
|
|
128
|
+
const docState = state.documents[documentId];
|
|
129
|
+
if (!docState) return state;
|
|
130
|
+
return updateDocState(state, documentId, {
|
|
131
|
+
...docState,
|
|
132
|
+
selecting: false,
|
|
133
|
+
selection: null,
|
|
134
|
+
rects: {},
|
|
135
|
+
active: false
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
case SET_RECTS: {
|
|
139
|
+
const { documentId, rects } = action.payload;
|
|
140
|
+
const docState = state.documents[documentId];
|
|
141
|
+
if (!docState) return state;
|
|
142
|
+
return updateDocState(state, documentId, { ...docState, rects });
|
|
143
|
+
}
|
|
144
|
+
case SET_SLICES: {
|
|
145
|
+
const { documentId, slices } = action.payload;
|
|
146
|
+
const docState = state.documents[documentId];
|
|
147
|
+
if (!docState) return state;
|
|
148
|
+
return updateDocState(state, documentId, { ...docState, slices });
|
|
149
|
+
}
|
|
150
|
+
case RESET: {
|
|
151
|
+
const { documentId } = action.payload;
|
|
152
|
+
const docState = state.documents[documentId];
|
|
153
|
+
if (!docState) return state;
|
|
154
|
+
return updateDocState(state, documentId, initialSelectionDocumentState);
|
|
155
|
+
}
|
|
156
|
+
default:
|
|
157
|
+
return state;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
40
160
|
function selectRectsForPage(state, page) {
|
|
41
161
|
return state.rects[page] ?? [];
|
|
42
162
|
}
|
|
@@ -208,266 +328,425 @@ function mergeAdjacentRects(textRuns) {
|
|
|
208
328
|
}
|
|
209
329
|
const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
|
|
210
330
|
constructor(id, registry) {
|
|
211
|
-
var _a;
|
|
331
|
+
var _a, _b, _c;
|
|
212
332
|
super(id, registry);
|
|
213
|
-
this.
|
|
214
|
-
this.selecting =
|
|
333
|
+
this.enabledModesPerDoc = /* @__PURE__ */ new Map();
|
|
334
|
+
this.selecting = /* @__PURE__ */ new Map();
|
|
335
|
+
this.anchor = /* @__PURE__ */ new Map();
|
|
215
336
|
this.pageCallbacks = /* @__PURE__ */ new Map();
|
|
216
|
-
this.
|
|
217
|
-
this.
|
|
218
|
-
this.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
this.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
});
|
|
337
|
+
this.menuPlacement$ = createScopedEmitter((documentId, placement) => ({ documentId, placement }));
|
|
338
|
+
this.selChange$ = createScopedEmitter((documentId, selection) => ({ documentId, selection }));
|
|
339
|
+
this.textRetrieved$ = createScopedEmitter(
|
|
340
|
+
(documentId, text) => ({ documentId, text })
|
|
341
|
+
);
|
|
342
|
+
this.copyToClipboard$ = createScopedEmitter(
|
|
343
|
+
(documentId, text) => ({ documentId, text }),
|
|
344
|
+
{ cache: false }
|
|
345
|
+
);
|
|
346
|
+
this.beginSelection$ = createScopedEmitter((documentId, data) => ({ documentId, page: data.page, index: data.index }), { cache: false });
|
|
347
|
+
this.endSelection$ = createScopedEmitter(
|
|
348
|
+
(documentId) => ({ documentId }),
|
|
349
|
+
{ cache: false }
|
|
350
|
+
);
|
|
351
|
+
this.viewportCapability = null;
|
|
352
|
+
this.scrollCapability = null;
|
|
353
|
+
const imPlugin = registry.getPlugin("interaction-manager");
|
|
354
|
+
if (!imPlugin) {
|
|
355
|
+
throw new Error("SelectionPlugin: InteractionManagerPlugin is required.");
|
|
356
|
+
}
|
|
357
|
+
this.interactionManagerCapability = imPlugin.provides();
|
|
358
|
+
this.viewportCapability = ((_a = registry.getPlugin("viewport")) == null ? void 0 : _a.provides()) ?? null;
|
|
359
|
+
this.scrollCapability = ((_b = registry.getPlugin("scroll")) == null ? void 0 : _b.provides()) ?? null;
|
|
226
360
|
this.coreStore.onAction(REFRESH_PAGES, (action) => {
|
|
227
|
-
const
|
|
361
|
+
const { documentId, pageIndexes } = action.payload;
|
|
362
|
+
const tasks = pageIndexes.map(
|
|
363
|
+
(pageIndex) => this.getNewPageGeometryAndCache(documentId, pageIndex)
|
|
364
|
+
);
|
|
228
365
|
Task.all(tasks).wait(() => {
|
|
229
|
-
|
|
230
|
-
this.notifyPage(
|
|
366
|
+
pageIndexes.forEach((pageIndex) => {
|
|
367
|
+
this.notifyPage(documentId, pageIndex);
|
|
231
368
|
});
|
|
232
369
|
}, ignore);
|
|
233
370
|
});
|
|
371
|
+
(_c = this.viewportCapability) == null ? void 0 : _c.onViewportChange(
|
|
372
|
+
(event) => {
|
|
373
|
+
this.recalculateMenuPlacement(event.documentId);
|
|
374
|
+
},
|
|
375
|
+
{ mode: "throttle", wait: 100 }
|
|
376
|
+
);
|
|
234
377
|
}
|
|
235
378
|
/* ── life-cycle ────────────────────────────────────────── */
|
|
379
|
+
onDocumentLoadingStarted(documentId) {
|
|
380
|
+
this.dispatch(initSelectionState(documentId, initialSelectionDocumentState));
|
|
381
|
+
this.enabledModesPerDoc.set(documentId, /* @__PURE__ */ new Set(["pointerMode"]));
|
|
382
|
+
this.pageCallbacks.set(documentId, /* @__PURE__ */ new Map());
|
|
383
|
+
this.selecting.set(documentId, false);
|
|
384
|
+
this.anchor.set(documentId, void 0);
|
|
385
|
+
}
|
|
386
|
+
onDocumentClosed(documentId) {
|
|
387
|
+
this.dispatch(cleanupSelectionState(documentId));
|
|
388
|
+
this.enabledModesPerDoc.delete(documentId);
|
|
389
|
+
this.pageCallbacks.delete(documentId);
|
|
390
|
+
this.selecting.delete(documentId);
|
|
391
|
+
this.anchor.delete(documentId);
|
|
392
|
+
this.selChange$.clearScope(documentId);
|
|
393
|
+
this.textRetrieved$.clearScope(documentId);
|
|
394
|
+
this.copyToClipboard$.clearScope(documentId);
|
|
395
|
+
this.beginSelection$.clearScope(documentId);
|
|
396
|
+
this.endSelection$.clearScope(documentId);
|
|
397
|
+
this.menuPlacement$.clearScope(documentId);
|
|
398
|
+
}
|
|
236
399
|
async initialize() {
|
|
237
400
|
}
|
|
238
401
|
async destroy() {
|
|
239
402
|
this.selChange$.clear();
|
|
403
|
+
this.textRetrieved$.clear();
|
|
404
|
+
this.copyToClipboard$.clear();
|
|
405
|
+
this.beginSelection$.clear();
|
|
406
|
+
this.endSelection$.clear();
|
|
407
|
+
this.menuPlacement$.clear();
|
|
408
|
+
super.destroy();
|
|
240
409
|
}
|
|
241
410
|
/* ── capability exposed to UI / other plugins ─────────── */
|
|
242
411
|
buildCapability() {
|
|
412
|
+
const getDocId = (documentId) => documentId ?? this.getActiveDocumentId();
|
|
413
|
+
return {
|
|
414
|
+
// Active document operations
|
|
415
|
+
getFormattedSelection: (docId) => getFormattedSelection(this.getDocumentState(getDocId(docId))),
|
|
416
|
+
getFormattedSelectionForPage: (p, docId) => getFormattedSelectionForPage(this.getDocumentState(getDocId(docId)), p),
|
|
417
|
+
getHighlightRectsForPage: (p, docId) => selectRectsForPage(this.getDocumentState(getDocId(docId)), p),
|
|
418
|
+
getHighlightRects: (docId) => this.getDocumentState(getDocId(docId)).rects,
|
|
419
|
+
getBoundingRectForPage: (p, docId) => selectBoundingRectForPage(this.getDocumentState(getDocId(docId)), p),
|
|
420
|
+
getBoundingRects: (docId) => selectBoundingRectsForAllPages(this.getDocumentState(getDocId(docId))),
|
|
421
|
+
getSelectedText: (docId) => this.getSelectedText(getDocId(docId)),
|
|
422
|
+
clear: (docId) => this.clearSelection(getDocId(docId)),
|
|
423
|
+
copyToClipboard: (docId) => this.copyToClipboard(getDocId(docId)),
|
|
424
|
+
getState: (docId) => this.getDocumentState(getDocId(docId)),
|
|
425
|
+
enableForMode: (modeId, docId) => {
|
|
426
|
+
var _a;
|
|
427
|
+
return (_a = this.enabledModesPerDoc.get(getDocId(docId))) == null ? void 0 : _a.add(modeId);
|
|
428
|
+
},
|
|
429
|
+
isEnabledForMode: (modeId, docId) => {
|
|
430
|
+
var _a;
|
|
431
|
+
return ((_a = this.enabledModesPerDoc.get(getDocId(docId))) == null ? void 0 : _a.has(modeId)) ?? false;
|
|
432
|
+
},
|
|
433
|
+
// Document-scoped operations
|
|
434
|
+
forDocument: this.createSelectionScope.bind(this),
|
|
435
|
+
// Global events
|
|
436
|
+
onCopyToClipboard: this.copyToClipboard$.onGlobal,
|
|
437
|
+
onSelectionChange: this.selChange$.onGlobal,
|
|
438
|
+
onTextRetrieved: this.textRetrieved$.onGlobal,
|
|
439
|
+
onBeginSelection: this.beginSelection$.onGlobal,
|
|
440
|
+
onEndSelection: this.endSelection$.onGlobal
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
createSelectionScope(documentId) {
|
|
243
444
|
return {
|
|
244
|
-
getFormattedSelection: () => getFormattedSelection(this.
|
|
245
|
-
getFormattedSelectionForPage: (p) => getFormattedSelectionForPage(this.
|
|
246
|
-
getHighlightRectsForPage: (p) => selectRectsForPage(this.
|
|
247
|
-
getHighlightRects: () => this.
|
|
248
|
-
getBoundingRectForPage: (p) => selectBoundingRectForPage(this.
|
|
249
|
-
getBoundingRects: () => selectBoundingRectsForAllPages(this.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
isEnabledForMode: (id) => this.enabledModes.has(id),
|
|
260
|
-
getState: () => this.state
|
|
445
|
+
getFormattedSelection: () => getFormattedSelection(this.getDocumentState(documentId)),
|
|
446
|
+
getFormattedSelectionForPage: (p) => getFormattedSelectionForPage(this.getDocumentState(documentId), p),
|
|
447
|
+
getHighlightRectsForPage: (p) => selectRectsForPage(this.getDocumentState(documentId), p),
|
|
448
|
+
getHighlightRects: () => this.getDocumentState(documentId).rects,
|
|
449
|
+
getBoundingRectForPage: (p) => selectBoundingRectForPage(this.getDocumentState(documentId), p),
|
|
450
|
+
getBoundingRects: () => selectBoundingRectsForAllPages(this.getDocumentState(documentId)),
|
|
451
|
+
getSelectedText: () => this.getSelectedText(documentId),
|
|
452
|
+
clear: () => this.clearSelection(documentId),
|
|
453
|
+
copyToClipboard: () => this.copyToClipboard(documentId),
|
|
454
|
+
getState: () => this.getDocumentState(documentId),
|
|
455
|
+
onSelectionChange: this.selChange$.forScope(documentId),
|
|
456
|
+
onTextRetrieved: this.textRetrieved$.forScope(documentId),
|
|
457
|
+
onCopyToClipboard: this.copyToClipboard$.forScope(documentId),
|
|
458
|
+
onBeginSelection: this.beginSelection$.forScope(documentId),
|
|
459
|
+
onEndSelection: this.endSelection$.forScope(documentId)
|
|
261
460
|
};
|
|
262
461
|
}
|
|
462
|
+
getDocumentState(documentId) {
|
|
463
|
+
const state = this.state.documents[documentId];
|
|
464
|
+
if (!state) {
|
|
465
|
+
throw new Error(`Selection state not found for document: ${documentId}`);
|
|
466
|
+
}
|
|
467
|
+
return state;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Subscribe to menu placement changes for a specific document
|
|
471
|
+
* @param documentId - The document ID to subscribe to
|
|
472
|
+
* @param listener - Callback function that receives placement updates
|
|
473
|
+
* @returns Unsubscribe function
|
|
474
|
+
*/
|
|
475
|
+
onMenuPlacement(documentId, listener) {
|
|
476
|
+
return this.menuPlacement$.forScope(documentId)(listener);
|
|
477
|
+
}
|
|
263
478
|
registerSelectionOnPage(opts) {
|
|
264
|
-
|
|
479
|
+
var _a;
|
|
480
|
+
const { documentId, pageIndex, onRectsChange } = opts;
|
|
481
|
+
const docState = this.state.documents[documentId];
|
|
482
|
+
if (!docState) {
|
|
265
483
|
this.logger.warn(
|
|
266
484
|
"SelectionPlugin",
|
|
267
|
-
"
|
|
268
|
-
|
|
485
|
+
"RegisterFailed",
|
|
486
|
+
`Cannot register selection on page ${pageIndex} for document ${documentId}: document state not initialized.`
|
|
269
487
|
);
|
|
270
488
|
return () => {
|
|
271
489
|
};
|
|
272
490
|
}
|
|
273
|
-
|
|
274
|
-
this.
|
|
275
|
-
const
|
|
491
|
+
(_a = this.pageCallbacks.get(documentId)) == null ? void 0 : _a.set(pageIndex, onRectsChange);
|
|
492
|
+
const geoTask = this.getOrLoadGeometry(documentId, pageIndex);
|
|
493
|
+
const interactionScope = this.interactionManagerCapability.forDocument(documentId);
|
|
494
|
+
const enabledModes = this.enabledModesPerDoc.get(documentId);
|
|
276
495
|
onRectsChange({
|
|
277
|
-
rects: selectRectsForPage(
|
|
278
|
-
boundingRect: selectBoundingRectForPage(
|
|
496
|
+
rects: selectRectsForPage(docState, pageIndex),
|
|
497
|
+
boundingRect: selectBoundingRectForPage(docState, pageIndex)
|
|
279
498
|
});
|
|
280
499
|
const handlers = {
|
|
281
500
|
onPointerDown: (point, _evt, modeId) => {
|
|
282
|
-
if (!
|
|
283
|
-
this.clearSelection();
|
|
284
|
-
const cached = this.
|
|
501
|
+
if (!(enabledModes == null ? void 0 : enabledModes.has(modeId))) return;
|
|
502
|
+
this.clearSelection(documentId);
|
|
503
|
+
const cached = this.getDocumentState(documentId).geometry[pageIndex];
|
|
285
504
|
if (cached) {
|
|
286
505
|
const g = glyphAt(cached, point);
|
|
287
506
|
if (g !== -1) {
|
|
288
|
-
this.beginSelection(pageIndex, g);
|
|
507
|
+
this.beginSelection(documentId, pageIndex, g);
|
|
289
508
|
}
|
|
290
509
|
}
|
|
291
510
|
},
|
|
292
511
|
onPointerMove: (point, _evt, modeId) => {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const cached = this.state.geometry[pageIndex];
|
|
512
|
+
if (!(enabledModes == null ? void 0 : enabledModes.has(modeId))) return;
|
|
513
|
+
const cached = this.getDocumentState(documentId).geometry[pageIndex];
|
|
296
514
|
if (cached) {
|
|
297
515
|
const g = glyphAt(cached, point);
|
|
298
516
|
if (g !== -1) {
|
|
299
|
-
|
|
517
|
+
interactionScope.setCursor("selection-text", "text", 10);
|
|
300
518
|
} else {
|
|
301
|
-
|
|
519
|
+
interactionScope.removeCursor("selection-text");
|
|
302
520
|
}
|
|
303
|
-
if (this.selecting && g !== -1) {
|
|
304
|
-
this.updateSelection(pageIndex, g);
|
|
521
|
+
if (this.selecting.get(documentId) && g !== -1) {
|
|
522
|
+
this.updateSelection(documentId, pageIndex, g);
|
|
305
523
|
}
|
|
306
524
|
}
|
|
307
525
|
},
|
|
308
526
|
onPointerUp: (_point, _evt, modeId) => {
|
|
309
|
-
if (!
|
|
310
|
-
this.endSelection();
|
|
527
|
+
if (!(enabledModes == null ? void 0 : enabledModes.has(modeId))) return;
|
|
528
|
+
this.endSelection(documentId);
|
|
311
529
|
},
|
|
312
530
|
onHandlerActiveEnd: (modeId) => {
|
|
313
|
-
if (!
|
|
314
|
-
this.clearSelection();
|
|
531
|
+
if (!(enabledModes == null ? void 0 : enabledModes.has(modeId))) return;
|
|
532
|
+
this.clearSelection(documentId);
|
|
315
533
|
}
|
|
316
534
|
};
|
|
317
535
|
const unregisterHandlers = this.interactionManagerCapability.registerAlways({
|
|
318
|
-
scope: { type: "page", pageIndex },
|
|
536
|
+
scope: { type: "page", documentId, pageIndex },
|
|
319
537
|
handlers
|
|
320
538
|
});
|
|
321
539
|
return () => {
|
|
540
|
+
var _a2;
|
|
322
541
|
unregisterHandlers();
|
|
323
|
-
this.pageCallbacks.delete(pageIndex);
|
|
542
|
+
(_a2 = this.pageCallbacks.get(documentId)) == null ? void 0 : _a2.delete(pageIndex);
|
|
324
543
|
geoTask.abort({ code: PdfErrorCode.Cancelled, message: "Cleanup" });
|
|
325
544
|
};
|
|
326
545
|
}
|
|
327
|
-
|
|
546
|
+
/**
|
|
547
|
+
* Helper to calculate viewport relative metrics for a page rect.
|
|
548
|
+
* Returns null if the rect cannot be converted to viewport space.
|
|
549
|
+
*/
|
|
550
|
+
getPlacementMetrics(documentId, pageIndex, rect, vpMetrics) {
|
|
328
551
|
var _a;
|
|
329
|
-
const
|
|
552
|
+
const scrollScope = (_a = this.scrollCapability) == null ? void 0 : _a.forDocument(documentId);
|
|
553
|
+
const viewportRect = scrollScope == null ? void 0 : scrollScope.getRectPositionForPage(pageIndex, rect);
|
|
554
|
+
if (!viewportRect) return null;
|
|
555
|
+
const rectTopInView = viewportRect.origin.y - vpMetrics.scrollTop;
|
|
556
|
+
const rectBottomInView = viewportRect.origin.y + viewportRect.size.height - vpMetrics.scrollTop;
|
|
557
|
+
return {
|
|
558
|
+
pageIndex,
|
|
559
|
+
rect,
|
|
560
|
+
// Original Page Rect
|
|
561
|
+
spaceAbove: rectTopInView,
|
|
562
|
+
spaceBelow: vpMetrics.clientHeight - rectBottomInView,
|
|
563
|
+
isBottomVisible: rectBottomInView > 0 && rectBottomInView <= vpMetrics.clientHeight,
|
|
564
|
+
isTopVisible: rectTopInView >= 0 && rectTopInView < vpMetrics.clientHeight
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
recalculateMenuPlacement(documentId) {
|
|
568
|
+
const docState = this.state.documents[documentId];
|
|
569
|
+
if (!docState) return;
|
|
570
|
+
if (docState.selecting || docState.selection === null) {
|
|
571
|
+
this.menuPlacement$.emit(documentId, null);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
const bounds = selectBoundingRectsForAllPages(docState);
|
|
575
|
+
if (bounds.length === 0) {
|
|
576
|
+
this.menuPlacement$.emit(documentId, null);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const tail = bounds[bounds.length - 1];
|
|
580
|
+
if (!this.viewportCapability || !this.scrollCapability) {
|
|
581
|
+
this.menuPlacement$.emit(documentId, {
|
|
582
|
+
pageIndex: tail.page,
|
|
583
|
+
rect: tail.rect,
|
|
584
|
+
spaceAbove: 0,
|
|
585
|
+
spaceBelow: Infinity,
|
|
586
|
+
// Pretend we have infinite space below to prevent auto-flipping
|
|
587
|
+
suggestTop: false,
|
|
588
|
+
isVisible: true
|
|
589
|
+
// Assume visible
|
|
590
|
+
});
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
const viewportScope = this.viewportCapability.forDocument(documentId);
|
|
594
|
+
const vpMetrics = viewportScope.getMetrics();
|
|
595
|
+
const MENU_HEIGHT_GUESS = 40;
|
|
596
|
+
const head = bounds[0];
|
|
597
|
+
const tailMetrics = this.getPlacementMetrics(documentId, tail.page, tail.rect, vpMetrics);
|
|
598
|
+
const headMetrics = this.getPlacementMetrics(documentId, head.page, head.rect, vpMetrics);
|
|
599
|
+
if (tailMetrics) {
|
|
600
|
+
if (tailMetrics.isBottomVisible && tailMetrics.spaceBelow > MENU_HEIGHT_GUESS) {
|
|
601
|
+
this.menuPlacement$.emit(documentId, {
|
|
602
|
+
...tailMetrics,
|
|
603
|
+
suggestTop: false,
|
|
604
|
+
isVisible: true
|
|
605
|
+
});
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (headMetrics) {
|
|
610
|
+
if (headMetrics.isTopVisible) {
|
|
611
|
+
this.menuPlacement$.emit(documentId, {
|
|
612
|
+
...headMetrics,
|
|
613
|
+
suggestTop: true,
|
|
614
|
+
isVisible: true
|
|
615
|
+
});
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (tailMetrics && tailMetrics.isBottomVisible) {
|
|
620
|
+
this.menuPlacement$.emit(documentId, {
|
|
621
|
+
...tailMetrics,
|
|
622
|
+
suggestTop: false,
|
|
623
|
+
isVisible: true
|
|
624
|
+
});
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
this.menuPlacement$.emit(documentId, null);
|
|
628
|
+
}
|
|
629
|
+
notifyPage(documentId, pageIndex) {
|
|
630
|
+
var _a;
|
|
631
|
+
const callback = (_a = this.pageCallbacks.get(documentId)) == null ? void 0 : _a.get(pageIndex);
|
|
330
632
|
if (callback) {
|
|
331
|
-
const
|
|
633
|
+
const docState = this.getDocumentState(documentId);
|
|
634
|
+
const mode = this.interactionManagerCapability.forDocument(documentId).getActiveMode();
|
|
332
635
|
if (mode === "pointerMode") {
|
|
333
636
|
callback({
|
|
334
|
-
rects: selectRectsForPage(
|
|
335
|
-
boundingRect: selectBoundingRectForPage(
|
|
637
|
+
rects: selectRectsForPage(docState, pageIndex),
|
|
638
|
+
boundingRect: selectBoundingRectForPage(docState, pageIndex)
|
|
336
639
|
});
|
|
337
640
|
} else {
|
|
338
641
|
callback({ rects: [], boundingRect: null });
|
|
339
642
|
}
|
|
340
643
|
}
|
|
341
644
|
}
|
|
342
|
-
notifyAllPages() {
|
|
343
|
-
|
|
344
|
-
|
|
645
|
+
notifyAllPages(documentId) {
|
|
646
|
+
var _a;
|
|
647
|
+
(_a = this.pageCallbacks.get(documentId)) == null ? void 0 : _a.forEach((_, pageIndex) => {
|
|
648
|
+
this.notifyPage(documentId, pageIndex);
|
|
345
649
|
});
|
|
346
650
|
}
|
|
347
|
-
getNewPageGeometryAndCache(pageIdx) {
|
|
348
|
-
|
|
651
|
+
getNewPageGeometryAndCache(documentId, pageIdx) {
|
|
652
|
+
const coreDoc = this.getCoreDocument(documentId);
|
|
653
|
+
if (!coreDoc || !coreDoc.document)
|
|
349
654
|
return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: "Doc Not Found" });
|
|
350
|
-
const page =
|
|
351
|
-
const task = this.engine.getPageGeometry(
|
|
655
|
+
const page = coreDoc.document.pages.find((p) => p.index === pageIdx);
|
|
656
|
+
const task = this.engine.getPageGeometry(coreDoc.document, page);
|
|
352
657
|
task.wait((geo) => {
|
|
353
|
-
this.dispatch(cachePageGeometry(pageIdx, geo));
|
|
658
|
+
this.dispatch(cachePageGeometry(documentId, pageIdx, geo));
|
|
354
659
|
}, ignore);
|
|
355
660
|
return task;
|
|
356
661
|
}
|
|
357
662
|
/* ── geometry cache ───────────────────────────────────── */
|
|
358
|
-
getOrLoadGeometry(pageIdx) {
|
|
359
|
-
const cached = this.
|
|
663
|
+
getOrLoadGeometry(documentId, pageIdx) {
|
|
664
|
+
const cached = this.getDocumentState(documentId).geometry[pageIdx];
|
|
360
665
|
if (cached) return PdfTaskHelper.resolve(cached);
|
|
361
|
-
return this.getNewPageGeometryAndCache(pageIdx);
|
|
666
|
+
return this.getNewPageGeometryAndCache(documentId, pageIdx);
|
|
362
667
|
}
|
|
363
668
|
/* ── selection state updates ───────────────────────────── */
|
|
364
|
-
beginSelection(page, index) {
|
|
365
|
-
this.selecting
|
|
366
|
-
this.anchor
|
|
367
|
-
this.dispatch(startSelection());
|
|
368
|
-
this.beginSelection$.emit({ page, index });
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
this.
|
|
373
|
-
this.
|
|
374
|
-
this.endSelection
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
this.
|
|
380
|
-
this.
|
|
381
|
-
this.
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
669
|
+
beginSelection(documentId, page, index) {
|
|
670
|
+
this.selecting.set(documentId, true);
|
|
671
|
+
this.anchor.set(documentId, { page, index });
|
|
672
|
+
this.dispatch(startSelection(documentId));
|
|
673
|
+
this.beginSelection$.emit(documentId, { page, index });
|
|
674
|
+
this.recalculateMenuPlacement(documentId);
|
|
675
|
+
}
|
|
676
|
+
endSelection(documentId) {
|
|
677
|
+
this.selecting.set(documentId, false);
|
|
678
|
+
this.anchor.set(documentId, void 0);
|
|
679
|
+
this.dispatch(endSelection(documentId));
|
|
680
|
+
this.endSelection$.emit(documentId);
|
|
681
|
+
this.recalculateMenuPlacement(documentId);
|
|
682
|
+
}
|
|
683
|
+
clearSelection(documentId) {
|
|
684
|
+
this.selecting.set(documentId, false);
|
|
685
|
+
this.anchor.set(documentId, void 0);
|
|
686
|
+
this.dispatch(clearSelection(documentId));
|
|
687
|
+
this.selChange$.emit(documentId, null);
|
|
688
|
+
this.menuPlacement$.emit(documentId, null);
|
|
689
|
+
this.notifyAllPages(documentId);
|
|
690
|
+
}
|
|
691
|
+
updateSelection(documentId, page, index) {
|
|
692
|
+
if (!this.selecting.get(documentId) || !this.anchor.get(documentId)) return;
|
|
693
|
+
const a = this.anchor.get(documentId);
|
|
386
694
|
const forward = page > a.page || page === a.page && index >= a.index;
|
|
387
695
|
const start = forward ? a : { page, index };
|
|
388
696
|
const end = forward ? { page, index } : a;
|
|
389
697
|
const range = { start, end };
|
|
390
|
-
this.dispatch(setSelection(range));
|
|
391
|
-
this.updateRectsAndSlices(range);
|
|
392
|
-
this.selChange$.emit(range);
|
|
698
|
+
this.dispatch(setSelection(documentId, range));
|
|
699
|
+
this.updateRectsAndSlices(documentId, range);
|
|
700
|
+
this.selChange$.emit(documentId, range);
|
|
393
701
|
for (let p = range.start.page; p <= range.end.page; p++) {
|
|
394
|
-
this.notifyPage(p);
|
|
702
|
+
this.notifyPage(documentId, p);
|
|
395
703
|
}
|
|
396
704
|
}
|
|
397
|
-
updateRectsAndSlices(range) {
|
|
705
|
+
updateRectsAndSlices(documentId, range) {
|
|
706
|
+
const docState = this.getDocumentState(documentId);
|
|
398
707
|
const allRects = {};
|
|
399
708
|
const allSlices = {};
|
|
400
709
|
for (let p = range.start.page; p <= range.end.page; p++) {
|
|
401
|
-
const geo =
|
|
710
|
+
const geo = docState.geometry[p];
|
|
402
711
|
const sb = sliceBounds(range, geo, p);
|
|
403
712
|
if (!sb) continue;
|
|
404
713
|
allRects[p] = rectsWithinSlice(geo, sb.from, sb.to);
|
|
405
714
|
allSlices[p] = { start: sb.from, count: sb.to - sb.from + 1 };
|
|
406
715
|
}
|
|
407
|
-
this.dispatch(setRects(allRects));
|
|
408
|
-
this.dispatch(setSlices(allSlices));
|
|
716
|
+
this.dispatch(setRects(documentId, allRects));
|
|
717
|
+
this.dispatch(setSlices(documentId, allSlices));
|
|
409
718
|
}
|
|
410
|
-
getSelectedText() {
|
|
411
|
-
|
|
719
|
+
getSelectedText(documentId) {
|
|
720
|
+
const coreDoc = this.getCoreDocument(documentId);
|
|
721
|
+
const docState = this.getDocumentState(documentId);
|
|
722
|
+
if (!(coreDoc == null ? void 0 : coreDoc.document) || !docState.selection) {
|
|
412
723
|
return PdfTaskHelper.reject({
|
|
413
724
|
code: PdfErrorCode.NotFound,
|
|
414
725
|
message: "Doc Not Found or No Selection"
|
|
415
726
|
});
|
|
416
727
|
}
|
|
417
|
-
const sel =
|
|
728
|
+
const sel = docState.selection;
|
|
418
729
|
const req = [];
|
|
419
730
|
for (let p = sel.start.page; p <= sel.end.page; p++) {
|
|
420
|
-
const s =
|
|
731
|
+
const s = docState.slices[p];
|
|
421
732
|
if (s) req.push({ pageIndex: p, charIndex: s.start, charCount: s.count });
|
|
422
733
|
}
|
|
423
734
|
if (req.length === 0) return PdfTaskHelper.resolve([]);
|
|
424
|
-
const task = this.engine.getTextSlices(
|
|
735
|
+
const task = this.engine.getTextSlices(coreDoc.document, req);
|
|
425
736
|
task.wait((text) => {
|
|
426
|
-
this.textRetrieved$.emit(text);
|
|
737
|
+
this.textRetrieved$.emit(documentId, text);
|
|
427
738
|
}, ignore);
|
|
428
739
|
return task;
|
|
429
740
|
}
|
|
430
|
-
copyToClipboard() {
|
|
431
|
-
const text = this.getSelectedText();
|
|
741
|
+
copyToClipboard(documentId) {
|
|
742
|
+
const text = this.getSelectedText(documentId);
|
|
432
743
|
text.wait((text2) => {
|
|
433
|
-
this.copyToClipboard$.emit(text2.join("\n"));
|
|
744
|
+
this.copyToClipboard$.emit(documentId, text2.join("\n"));
|
|
434
745
|
}, ignore);
|
|
435
746
|
}
|
|
436
747
|
};
|
|
437
748
|
_SelectionPlugin.id = "selection";
|
|
438
749
|
let SelectionPlugin = _SelectionPlugin;
|
|
439
|
-
const initialState = {
|
|
440
|
-
geometry: {},
|
|
441
|
-
rects: {},
|
|
442
|
-
slices: {},
|
|
443
|
-
selection: null,
|
|
444
|
-
active: false,
|
|
445
|
-
selecting: false
|
|
446
|
-
};
|
|
447
|
-
const selectionReducer = (state = initialState, action) => {
|
|
448
|
-
switch (action.type) {
|
|
449
|
-
case CACHE_PAGE_GEOMETRY: {
|
|
450
|
-
const { page, geo } = action.payload;
|
|
451
|
-
return { ...state, geometry: { ...state.geometry, [page]: geo } };
|
|
452
|
-
}
|
|
453
|
-
case SET_SELECTION:
|
|
454
|
-
return { ...state, selection: action.payload, active: true };
|
|
455
|
-
case START_SELECTION:
|
|
456
|
-
return { ...state, selecting: true, selection: null, rects: {} };
|
|
457
|
-
case END_SELECTION:
|
|
458
|
-
return { ...state, selecting: false };
|
|
459
|
-
case CLEAR_SELECTION:
|
|
460
|
-
return { ...state, selecting: false, selection: null, rects: {}, active: false };
|
|
461
|
-
case SET_RECTS:
|
|
462
|
-
return { ...state, rects: action.payload };
|
|
463
|
-
case SET_SLICES:
|
|
464
|
-
return { ...state, slices: action.payload };
|
|
465
|
-
case RESET:
|
|
466
|
-
return initialState;
|
|
467
|
-
default:
|
|
468
|
-
return state;
|
|
469
|
-
}
|
|
470
|
-
};
|
|
471
750
|
const SelectionPluginPackage = {
|
|
472
751
|
manifest,
|
|
473
752
|
create: (registry) => new SelectionPlugin(SELECTION_PLUGIN_ID, registry),
|