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