@embedpdf/plugin-viewport 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 +499 -111
- package/dist/index.js.map +1 -1
- package/dist/lib/actions.d.ts +77 -9
- package/dist/lib/types.d.ts +55 -12
- package/dist/lib/viewport-plugin.d.ts +24 -9
- package/dist/preact/index.cjs +1 -1
- package/dist/preact/index.cjs.map +1 -1
- package/dist/preact/index.js +40 -15
- package/dist/preact/index.js.map +1 -1
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +40 -15
- package/dist/react/index.js.map +1 -1
- package/dist/shared/components/viewport.d.ts +5 -1
- package/dist/shared/hooks/use-viewport-ref.d.ts +1 -1
- package/dist/shared/hooks/use-viewport.d.ts +11 -1
- package/dist/shared-preact/components/viewport.d.ts +5 -1
- package/dist/shared-preact/hooks/use-viewport-ref.d.ts +1 -1
- package/dist/shared-preact/hooks/use-viewport.d.ts +11 -1
- package/dist/shared-react/components/viewport.d.ts +5 -1
- package/dist/shared-react/hooks/use-viewport-ref.d.ts +1 -1
- package/dist/shared-react/hooks/use-viewport.d.ts +11 -1
- package/dist/svelte/components/Viewport.svelte.d.ts +4 -0
- package/dist/svelte/hooks/use-viewport-ref.svelte.d.ts +5 -1
- package/dist/svelte/hooks/use-viewport.svelte.d.ts +15 -4
- package/dist/svelte/index.cjs +1 -1
- package/dist/svelte/index.cjs.map +1 -1
- package/dist/svelte/index.js +86 -21
- package/dist/svelte/index.js.map +1 -1
- package/dist/vue/components/viewport.vue.d.ts +9 -2
- package/dist/vue/hooks/use-viewport-ref.d.ts +6 -1
- package/dist/vue/hooks/use-viewport.d.ts +12 -1
- package/dist/vue/index.cjs +1 -1
- package/dist/vue/index.cjs.map +1 -1
- package/dist/vue/index.js +66 -38
- package/dist/vue/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -13,37 +13,52 @@ const manifest = {
|
|
|
13
13
|
scrollEndDelay: 300
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
|
+
const INIT_VIEWPORT_STATE = "INIT_VIEWPORT_STATE";
|
|
17
|
+
const CLEANUP_VIEWPORT_STATE = "CLEANUP_VIEWPORT_STATE";
|
|
18
|
+
const REGISTER_VIEWPORT = "REGISTER_VIEWPORT";
|
|
19
|
+
const UNREGISTER_VIEWPORT = "UNREGISTER_VIEWPORT";
|
|
16
20
|
const SET_VIEWPORT_METRICS = "SET_VIEWPORT_METRICS";
|
|
17
21
|
const SET_VIEWPORT_SCROLL_METRICS = "SET_VIEWPORT_SCROLL_METRICS";
|
|
18
22
|
const SET_VIEWPORT_GAP = "SET_VIEWPORT_GAP";
|
|
19
23
|
const SET_SCROLL_ACTIVITY = "SET_SCROLL_ACTIVITY";
|
|
20
24
|
const SET_SMOOTH_SCROLL_ACTIVITY = "SET_SMOOTH_SCROLL_ACTIVITY";
|
|
25
|
+
const SET_ACTIVE_VIEWPORT_DOCUMENT = "SET_ACTIVE_VIEWPORT_DOCUMENT";
|
|
26
|
+
const ADD_VIEWPORT_GATE = "ADD_VIEWPORT_GATE";
|
|
27
|
+
const REMOVE_VIEWPORT_GATE = "REMOVE_VIEWPORT_GATE";
|
|
28
|
+
function initViewportState(documentId) {
|
|
29
|
+
return { type: INIT_VIEWPORT_STATE, payload: { documentId } };
|
|
30
|
+
}
|
|
31
|
+
function cleanupViewportState(documentId) {
|
|
32
|
+
return { type: CLEANUP_VIEWPORT_STATE, payload: { documentId } };
|
|
33
|
+
}
|
|
34
|
+
function registerViewport(documentId) {
|
|
35
|
+
return { type: REGISTER_VIEWPORT, payload: { documentId } };
|
|
36
|
+
}
|
|
37
|
+
function unregisterViewport(documentId) {
|
|
38
|
+
return { type: UNREGISTER_VIEWPORT, payload: { documentId } };
|
|
39
|
+
}
|
|
21
40
|
function setViewportGap(viewportGap) {
|
|
22
|
-
return {
|
|
23
|
-
type: SET_VIEWPORT_GAP,
|
|
24
|
-
payload: viewportGap
|
|
25
|
-
};
|
|
41
|
+
return { type: SET_VIEWPORT_GAP, payload: viewportGap };
|
|
26
42
|
}
|
|
27
|
-
function setViewportMetrics(
|
|
28
|
-
return {
|
|
29
|
-
type: SET_VIEWPORT_METRICS,
|
|
30
|
-
payload: viewportMetrics
|
|
31
|
-
};
|
|
43
|
+
function setViewportMetrics(documentId, metrics) {
|
|
44
|
+
return { type: SET_VIEWPORT_METRICS, payload: { documentId, metrics } };
|
|
32
45
|
}
|
|
33
|
-
function setViewportScrollMetrics(scrollMetrics) {
|
|
34
|
-
return {
|
|
35
|
-
type: SET_VIEWPORT_SCROLL_METRICS,
|
|
36
|
-
payload: scrollMetrics
|
|
37
|
-
};
|
|
46
|
+
function setViewportScrollMetrics(documentId, scrollMetrics) {
|
|
47
|
+
return { type: SET_VIEWPORT_SCROLL_METRICS, payload: { documentId, scrollMetrics } };
|
|
38
48
|
}
|
|
39
|
-
function setScrollActivity(isScrolling) {
|
|
40
|
-
return { type: SET_SCROLL_ACTIVITY, payload: isScrolling };
|
|
49
|
+
function setScrollActivity(documentId, isScrolling) {
|
|
50
|
+
return { type: SET_SCROLL_ACTIVITY, payload: { documentId, isScrolling } };
|
|
41
51
|
}
|
|
42
|
-
function setSmoothScrollActivity(isSmoothScrolling) {
|
|
43
|
-
return { type: SET_SMOOTH_SCROLL_ACTIVITY, payload: isSmoothScrolling };
|
|
52
|
+
function setSmoothScrollActivity(documentId, isSmoothScrolling) {
|
|
53
|
+
return { type: SET_SMOOTH_SCROLL_ACTIVITY, payload: { documentId, isSmoothScrolling } };
|
|
44
54
|
}
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
function addViewportGate(documentId, key) {
|
|
56
|
+
return { type: ADD_VIEWPORT_GATE, payload: { documentId, key } };
|
|
57
|
+
}
|
|
58
|
+
function removeViewportGate(documentId, key) {
|
|
59
|
+
return { type: REMOVE_VIEWPORT_GATE, payload: { documentId, key } };
|
|
60
|
+
}
|
|
61
|
+
const initialViewportDocumentState = {
|
|
47
62
|
viewportMetrics: {
|
|
48
63
|
width: 0,
|
|
49
64
|
height: 0,
|
|
@@ -53,50 +68,198 @@ const initialState = {
|
|
|
53
68
|
clientHeight: 0,
|
|
54
69
|
scrollWidth: 0,
|
|
55
70
|
scrollHeight: 0,
|
|
56
|
-
relativePosition: {
|
|
57
|
-
x: 0,
|
|
58
|
-
y: 0
|
|
59
|
-
}
|
|
71
|
+
relativePosition: { x: 0, y: 0 }
|
|
60
72
|
},
|
|
61
73
|
isScrolling: false,
|
|
62
|
-
isSmoothScrolling: false
|
|
74
|
+
isSmoothScrolling: false,
|
|
75
|
+
gates: /* @__PURE__ */ new Set()
|
|
76
|
+
};
|
|
77
|
+
const initialState = {
|
|
78
|
+
viewportGap: 0,
|
|
79
|
+
documents: {},
|
|
80
|
+
activeViewports: /* @__PURE__ */ new Set(),
|
|
81
|
+
activeDocumentId: null
|
|
63
82
|
};
|
|
64
83
|
const viewportReducer = (state = initialState, action) => {
|
|
65
84
|
switch (action.type) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
85
|
+
// ─────────────────────────────────────────────────────────
|
|
86
|
+
// State Persistence (Document Lifecycle)
|
|
87
|
+
// ─────────────────────────────────────────────────────────
|
|
88
|
+
case INIT_VIEWPORT_STATE: {
|
|
89
|
+
const { documentId } = action.payload;
|
|
90
|
+
return {
|
|
91
|
+
...state,
|
|
92
|
+
documents: {
|
|
93
|
+
...state.documents,
|
|
94
|
+
[documentId]: { ...initialViewportDocumentState, gates: /* @__PURE__ */ new Set() }
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
case CLEANUP_VIEWPORT_STATE: {
|
|
99
|
+
const { documentId } = action.payload;
|
|
100
|
+
const { [documentId]: removed, ...remainingDocs } = state.documents;
|
|
101
|
+
const newActiveViewports = new Set(state.activeViewports);
|
|
102
|
+
newActiveViewports.delete(documentId);
|
|
103
|
+
return {
|
|
104
|
+
...state,
|
|
105
|
+
documents: remainingDocs,
|
|
106
|
+
activeViewports: newActiveViewports,
|
|
107
|
+
activeDocumentId: state.activeDocumentId === documentId ? null : state.activeDocumentId
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// ─────────────────────────────────────────────────────────
|
|
111
|
+
// Viewport Registration (DOM Lifecycle)
|
|
112
|
+
// ─────────────────────────────────────────────────────────
|
|
113
|
+
case REGISTER_VIEWPORT: {
|
|
114
|
+
const { documentId } = action.payload;
|
|
115
|
+
const newActiveViewports = new Set(state.activeViewports);
|
|
116
|
+
newActiveViewports.add(documentId);
|
|
117
|
+
return {
|
|
118
|
+
...state,
|
|
119
|
+
activeViewports: newActiveViewports,
|
|
120
|
+
// Set as active if no active document
|
|
121
|
+
activeDocumentId: state.activeDocumentId ?? documentId
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
case UNREGISTER_VIEWPORT: {
|
|
125
|
+
const { documentId } = action.payload;
|
|
126
|
+
const newActiveViewports = new Set(state.activeViewports);
|
|
127
|
+
newActiveViewports.delete(documentId);
|
|
128
|
+
return {
|
|
129
|
+
...state,
|
|
130
|
+
activeViewports: newActiveViewports
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
case SET_ACTIVE_VIEWPORT_DOCUMENT: {
|
|
134
|
+
return {
|
|
135
|
+
...state,
|
|
136
|
+
activeDocumentId: action.payload
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// ─────────────────────────────────────────────────────────
|
|
140
|
+
// Viewport Operations
|
|
141
|
+
// ─────────────────────────────────────────────────────────
|
|
142
|
+
case SET_VIEWPORT_GAP: {
|
|
143
|
+
return {
|
|
144
|
+
...state,
|
|
145
|
+
viewportGap: action.payload
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
case SET_VIEWPORT_METRICS: {
|
|
149
|
+
const { documentId, metrics } = action.payload;
|
|
150
|
+
const viewport = state.documents[documentId];
|
|
151
|
+
if (!viewport) return state;
|
|
69
152
|
return {
|
|
70
153
|
...state,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
154
|
+
documents: {
|
|
155
|
+
...state.documents,
|
|
156
|
+
[documentId]: {
|
|
157
|
+
...viewport,
|
|
158
|
+
viewportMetrics: {
|
|
159
|
+
width: metrics.width,
|
|
160
|
+
height: metrics.height,
|
|
161
|
+
scrollTop: metrics.scrollTop,
|
|
162
|
+
scrollLeft: metrics.scrollLeft,
|
|
163
|
+
clientWidth: metrics.clientWidth,
|
|
164
|
+
clientHeight: metrics.clientHeight,
|
|
165
|
+
scrollWidth: metrics.scrollWidth,
|
|
166
|
+
scrollHeight: metrics.scrollHeight,
|
|
167
|
+
relativePosition: {
|
|
168
|
+
x: metrics.scrollWidth <= metrics.clientWidth ? 0 : metrics.scrollLeft / (metrics.scrollWidth - metrics.clientWidth),
|
|
169
|
+
y: metrics.scrollHeight <= metrics.clientHeight ? 0 : metrics.scrollTop / (metrics.scrollHeight - metrics.clientHeight)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
83
172
|
}
|
|
84
173
|
}
|
|
85
174
|
};
|
|
86
|
-
|
|
175
|
+
}
|
|
176
|
+
case SET_VIEWPORT_SCROLL_METRICS: {
|
|
177
|
+
const { documentId, scrollMetrics } = action.payload;
|
|
178
|
+
const viewport = state.documents[documentId];
|
|
179
|
+
if (!viewport) return state;
|
|
87
180
|
return {
|
|
88
181
|
...state,
|
|
89
|
-
|
|
90
|
-
...state.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
182
|
+
documents: {
|
|
183
|
+
...state.documents,
|
|
184
|
+
[documentId]: {
|
|
185
|
+
...viewport,
|
|
186
|
+
viewportMetrics: {
|
|
187
|
+
...viewport.viewportMetrics,
|
|
188
|
+
scrollTop: scrollMetrics.scrollTop,
|
|
189
|
+
scrollLeft: scrollMetrics.scrollLeft
|
|
190
|
+
},
|
|
191
|
+
isScrolling: true
|
|
192
|
+
}
|
|
193
|
+
}
|
|
95
194
|
};
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
195
|
+
}
|
|
196
|
+
case SET_SCROLL_ACTIVITY: {
|
|
197
|
+
const { documentId, isScrolling } = action.payload;
|
|
198
|
+
const viewport = state.documents[documentId];
|
|
199
|
+
if (!viewport) return state;
|
|
200
|
+
return {
|
|
201
|
+
...state,
|
|
202
|
+
documents: {
|
|
203
|
+
...state.documents,
|
|
204
|
+
[documentId]: {
|
|
205
|
+
...viewport,
|
|
206
|
+
isScrolling
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
case SET_SMOOTH_SCROLL_ACTIVITY: {
|
|
212
|
+
const { documentId, isSmoothScrolling } = action.payload;
|
|
213
|
+
const viewport = state.documents[documentId];
|
|
214
|
+
if (!viewport) return state;
|
|
215
|
+
return {
|
|
216
|
+
...state,
|
|
217
|
+
documents: {
|
|
218
|
+
...state.documents,
|
|
219
|
+
[documentId]: {
|
|
220
|
+
...viewport,
|
|
221
|
+
isSmoothScrolling
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// ─────────────────────────────────────────────────────────
|
|
227
|
+
// Named Gate Operations
|
|
228
|
+
// ─────────────────────────────────────────────────────────
|
|
229
|
+
case ADD_VIEWPORT_GATE: {
|
|
230
|
+
const { documentId, key } = action.payload;
|
|
231
|
+
const viewport = state.documents[documentId];
|
|
232
|
+
if (!viewport) return state;
|
|
233
|
+
const newGates = new Set(viewport.gates);
|
|
234
|
+
newGates.add(key);
|
|
235
|
+
return {
|
|
236
|
+
...state,
|
|
237
|
+
documents: {
|
|
238
|
+
...state.documents,
|
|
239
|
+
[documentId]: {
|
|
240
|
+
...viewport,
|
|
241
|
+
gates: newGates
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
case REMOVE_VIEWPORT_GATE: {
|
|
247
|
+
const { documentId, key } = action.payload;
|
|
248
|
+
const viewport = state.documents[documentId];
|
|
249
|
+
if (!viewport) return state;
|
|
250
|
+
const newGates = new Set(viewport.gates);
|
|
251
|
+
newGates.delete(key);
|
|
252
|
+
return {
|
|
253
|
+
...state,
|
|
254
|
+
documents: {
|
|
255
|
+
...state.documents,
|
|
256
|
+
[documentId]: {
|
|
257
|
+
...viewport,
|
|
258
|
+
gates: newGates
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
100
263
|
default:
|
|
101
264
|
return state;
|
|
102
265
|
}
|
|
@@ -108,108 +271,333 @@ const _ViewportPlugin = class _ViewportPlugin extends BasePlugin {
|
|
|
108
271
|
this.viewportResize$ = createBehaviorEmitter();
|
|
109
272
|
this.viewportMetrics$ = createBehaviorEmitter();
|
|
110
273
|
this.scrollMetrics$ = createBehaviorEmitter();
|
|
111
|
-
this.scrollReq$ = createEmitter();
|
|
112
274
|
this.scrollActivity$ = createBehaviorEmitter();
|
|
113
|
-
this.
|
|
275
|
+
this.gateState$ = createBehaviorEmitter();
|
|
276
|
+
this.scrollRequests$ = /* @__PURE__ */ new Map();
|
|
277
|
+
this.rectProviders = /* @__PURE__ */ new Map();
|
|
114
278
|
if (config.viewportGap) {
|
|
115
279
|
this.dispatch(setViewportGap(config.viewportGap));
|
|
116
280
|
}
|
|
117
281
|
this.scrollEndDelay = config.scrollEndDelay || 100;
|
|
118
282
|
}
|
|
283
|
+
// ─────────────────────────────────────────────────────────
|
|
284
|
+
// Document Lifecycle (from BasePlugin)
|
|
285
|
+
// ─────────────────────────────────────────────────────────
|
|
286
|
+
onDocumentLoadingStarted(documentId) {
|
|
287
|
+
this.dispatch(initViewportState(documentId));
|
|
288
|
+
this.scrollRequests$.set(documentId, createEmitter());
|
|
289
|
+
this.logger.debug(
|
|
290
|
+
"ViewportPlugin",
|
|
291
|
+
"DocumentOpened",
|
|
292
|
+
`Initialized viewport state for document: ${documentId}`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
onDocumentClosed(documentId) {
|
|
296
|
+
var _a;
|
|
297
|
+
this.dispatch(cleanupViewportState(documentId));
|
|
298
|
+
(_a = this.scrollRequests$.get(documentId)) == null ? void 0 : _a.clear();
|
|
299
|
+
this.scrollRequests$.delete(documentId);
|
|
300
|
+
this.rectProviders.delete(documentId);
|
|
301
|
+
this.logger.debug(
|
|
302
|
+
"ViewportPlugin",
|
|
303
|
+
"DocumentClosed",
|
|
304
|
+
`Cleaned up viewport state for document: ${documentId}`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
// ─────────────────────────────────────────────────────────
|
|
308
|
+
// Capability
|
|
309
|
+
// ─────────────────────────────────────────────────────────
|
|
119
310
|
buildCapability() {
|
|
120
311
|
return {
|
|
312
|
+
// Global
|
|
121
313
|
getViewportGap: () => this.state.viewportGap,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
var _a;
|
|
125
|
-
return ((_a = this.rectProvider) == null ? void 0 : _a.call(this)) ?? {
|
|
126
|
-
origin: { x: 0, y: 0 },
|
|
127
|
-
size: { width: 0, height: 0 }
|
|
128
|
-
};
|
|
129
|
-
},
|
|
314
|
+
// Active document operations
|
|
315
|
+
getMetrics: () => this.getMetrics(),
|
|
130
316
|
scrollTo: (pos) => this.scrollTo(pos),
|
|
131
|
-
isScrolling: () => this.
|
|
132
|
-
isSmoothScrolling: () => this.
|
|
133
|
-
|
|
317
|
+
isScrolling: () => this.isScrolling(),
|
|
318
|
+
isSmoothScrolling: () => this.isSmoothScrolling(),
|
|
319
|
+
isGated: (documentId) => this.isGated(documentId),
|
|
320
|
+
hasGate: (key, documentId) => this.hasGate(key, documentId),
|
|
321
|
+
getGates: (documentId) => this.getGates(documentId),
|
|
322
|
+
getBoundingRect: () => this.getBoundingRect(),
|
|
323
|
+
// Document-scoped operations
|
|
324
|
+
forDocument: (documentId) => this.createViewportScope(documentId),
|
|
325
|
+
gate: (key, documentId) => this.gate(key, documentId),
|
|
326
|
+
releaseGate: (key, documentId) => this.releaseGate(key, documentId),
|
|
327
|
+
// Check if viewport is currently mounted
|
|
328
|
+
isViewportMounted: (documentId) => this.state.activeViewports.has(documentId),
|
|
329
|
+
// Events
|
|
134
330
|
onViewportChange: this.viewportMetrics$.on,
|
|
135
331
|
onViewportResize: this.viewportResize$.on,
|
|
136
|
-
|
|
332
|
+
onScrollChange: this.scrollMetrics$.on,
|
|
333
|
+
onScrollActivity: this.scrollActivity$.on,
|
|
334
|
+
onGateChange: this.gateState$.on
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
// ─────────────────────────────────────────────────────────
|
|
338
|
+
// Document Scoping
|
|
339
|
+
// ─────────────────────────────────────────────────────────
|
|
340
|
+
createViewportScope(documentId) {
|
|
341
|
+
return {
|
|
342
|
+
getMetrics: () => this.getMetrics(documentId),
|
|
343
|
+
scrollTo: (pos) => this.scrollTo(pos, documentId),
|
|
344
|
+
isScrolling: () => this.isScrolling(documentId),
|
|
345
|
+
isSmoothScrolling: () => this.isSmoothScrolling(documentId),
|
|
346
|
+
isGated: () => this.isGated(documentId),
|
|
347
|
+
hasGate: (key) => this.hasGate(key, documentId),
|
|
348
|
+
getGates: () => this.getGates(documentId),
|
|
349
|
+
gate: (key) => this.gate(key, documentId),
|
|
350
|
+
releaseGate: (key) => this.releaseGate(key, documentId),
|
|
351
|
+
getBoundingRect: () => this.getBoundingRect(documentId),
|
|
352
|
+
onViewportChange: (listener) => this.viewportMetrics$.on((event) => {
|
|
353
|
+
if (event.documentId === documentId) listener(event.metrics);
|
|
354
|
+
}),
|
|
355
|
+
onScrollChange: (listener) => this.scrollMetrics$.on((event) => {
|
|
356
|
+
if (event.documentId === documentId) listener(event.scrollMetrics);
|
|
357
|
+
}),
|
|
358
|
+
onScrollActivity: (listener) => this.scrollActivity$.on((event) => {
|
|
359
|
+
if (event.documentId === documentId) listener(event.activity);
|
|
360
|
+
}),
|
|
361
|
+
onGateChange: (listener) => this.gateState$.on((event) => {
|
|
362
|
+
if ((event == null ? void 0 : event.documentId) === documentId) listener(event);
|
|
363
|
+
})
|
|
137
364
|
};
|
|
138
365
|
}
|
|
139
|
-
|
|
366
|
+
// ─────────────────────────────────────────────────────────
|
|
367
|
+
// Viewport Registration (Public API for components)
|
|
368
|
+
// ─────────────────────────────────────────────────────────
|
|
369
|
+
registerViewport(documentId) {
|
|
370
|
+
if (!this.state.documents[documentId]) {
|
|
371
|
+
throw new Error(
|
|
372
|
+
`Cannot register viewport for ${documentId}: document state not found. Document must be opened before registering viewport.`
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
if (!this.state.activeViewports.has(documentId)) {
|
|
376
|
+
this.dispatch(registerViewport(documentId));
|
|
377
|
+
this.logger.debug(
|
|
378
|
+
"ViewportPlugin",
|
|
379
|
+
"RegisterViewport",
|
|
380
|
+
`Registered viewport (DOM mounted) for document: ${documentId}`
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
unregisterViewport(documentId) {
|
|
140
385
|
if (this.registry.isDestroyed()) return;
|
|
141
|
-
this.
|
|
142
|
-
|
|
386
|
+
if (this.state.activeViewports.has(documentId)) {
|
|
387
|
+
this.dispatch(unregisterViewport(documentId));
|
|
388
|
+
this.rectProviders.delete(documentId);
|
|
389
|
+
this.logger.debug(
|
|
390
|
+
"ViewportPlugin",
|
|
391
|
+
"UnregisterViewport",
|
|
392
|
+
`Unregistered viewport (DOM unmounted) for document: ${documentId}. State preserved.`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// ─────────────────────────────────────────────────────────
|
|
397
|
+
// Per-Document Operations
|
|
398
|
+
// ─────────────────────────────────────────────────────────
|
|
399
|
+
setViewportResizeMetrics(documentId, metrics) {
|
|
400
|
+
if (this.registry.isDestroyed()) return;
|
|
401
|
+
this.dispatch(setViewportMetrics(documentId, metrics));
|
|
402
|
+
const viewport = this.state.documents[documentId];
|
|
403
|
+
if (viewport) {
|
|
404
|
+
this.viewportResize$.emit({
|
|
405
|
+
documentId,
|
|
406
|
+
metrics: viewport.viewportMetrics
|
|
407
|
+
});
|
|
408
|
+
}
|
|
143
409
|
}
|
|
144
|
-
setViewportScrollMetrics(scrollMetrics) {
|
|
410
|
+
setViewportScrollMetrics(documentId, scrollMetrics) {
|
|
145
411
|
if (this.registry.isDestroyed()) return;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
412
|
+
const viewport = this.state.documents[documentId];
|
|
413
|
+
if (!viewport) return;
|
|
414
|
+
if (scrollMetrics.scrollTop !== viewport.viewportMetrics.scrollTop || scrollMetrics.scrollLeft !== viewport.viewportMetrics.scrollLeft) {
|
|
415
|
+
this.dispatch(setViewportScrollMetrics(documentId, scrollMetrics));
|
|
416
|
+
this.bumpScrollActivity(documentId);
|
|
149
417
|
this.scrollMetrics$.emit({
|
|
150
|
-
|
|
151
|
-
|
|
418
|
+
documentId,
|
|
419
|
+
scrollMetrics
|
|
152
420
|
});
|
|
153
421
|
}
|
|
154
422
|
}
|
|
155
|
-
onScrollRequest(listener) {
|
|
156
|
-
|
|
423
|
+
onScrollRequest(documentId, listener) {
|
|
424
|
+
const emitter = this.scrollRequests$.get(documentId);
|
|
425
|
+
if (!emitter) {
|
|
426
|
+
throw new Error(
|
|
427
|
+
`Cannot subscribe to scroll requests for ${documentId}: document state not initialized`
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
return emitter.on(listener);
|
|
157
431
|
}
|
|
158
|
-
registerBoundingRectProvider(provider) {
|
|
159
|
-
|
|
432
|
+
registerBoundingRectProvider(documentId, provider) {
|
|
433
|
+
if (provider) {
|
|
434
|
+
this.rectProviders.set(documentId, provider);
|
|
435
|
+
} else {
|
|
436
|
+
this.rectProviders.delete(documentId);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// ─────────────────────────────────────────────────────────
|
|
440
|
+
// Public Gating API
|
|
441
|
+
// ─────────────────────────────────────────────────────────
|
|
442
|
+
gate(key, documentId) {
|
|
443
|
+
const viewport = this.state.documents[documentId];
|
|
444
|
+
if (!viewport) {
|
|
445
|
+
this.logger.warn(
|
|
446
|
+
"ViewportPlugin",
|
|
447
|
+
"GateViewport",
|
|
448
|
+
`Cannot gate viewport for ${documentId}: document not found`
|
|
449
|
+
);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (!viewport.gates.has(key)) {
|
|
453
|
+
this.dispatch(addViewportGate(documentId, key));
|
|
454
|
+
this.logger.debug(
|
|
455
|
+
"ViewportPlugin",
|
|
456
|
+
"GateAdded",
|
|
457
|
+
`Added gate '${key}' for document: ${documentId}. Total gates: ${viewport.gates.size + 1}`
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
releaseGate(key, documentId) {
|
|
462
|
+
const viewport = this.state.documents[documentId];
|
|
463
|
+
if (!viewport) {
|
|
464
|
+
this.logger.warn(
|
|
465
|
+
"ViewportPlugin",
|
|
466
|
+
"ReleaseGate",
|
|
467
|
+
`Cannot release gate for ${documentId}: document not found`
|
|
468
|
+
);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (viewport.gates.has(key)) {
|
|
472
|
+
this.dispatch(removeViewportGate(documentId, key));
|
|
473
|
+
this.logger.debug(
|
|
474
|
+
"ViewportPlugin",
|
|
475
|
+
"GateReleased",
|
|
476
|
+
`Released gate '${key}' for document: ${documentId}. Remaining gates: ${viewport.gates.size - 1}`
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
// ─────────────────────────────────────────────────────────
|
|
481
|
+
// Helper Methods
|
|
482
|
+
// ─────────────────────────────────────────────────────────
|
|
483
|
+
getViewportState(documentId) {
|
|
484
|
+
const id = documentId ?? this.getActiveDocumentId();
|
|
485
|
+
const viewport = this.state.documents[id];
|
|
486
|
+
if (!viewport) {
|
|
487
|
+
throw new Error(`Viewport state not found for document: ${id}`);
|
|
488
|
+
}
|
|
489
|
+
return viewport;
|
|
490
|
+
}
|
|
491
|
+
getMetrics(documentId) {
|
|
492
|
+
return this.getViewportState(documentId).viewportMetrics;
|
|
493
|
+
}
|
|
494
|
+
isScrolling(documentId) {
|
|
495
|
+
return this.getViewportState(documentId).isScrolling;
|
|
496
|
+
}
|
|
497
|
+
isSmoothScrolling(documentId) {
|
|
498
|
+
return this.getViewportState(documentId).isSmoothScrolling;
|
|
160
499
|
}
|
|
161
|
-
|
|
162
|
-
this.
|
|
163
|
-
|
|
500
|
+
isGated(documentId) {
|
|
501
|
+
const viewport = this.getViewportState(documentId);
|
|
502
|
+
return viewport.gates.size > 0;
|
|
164
503
|
}
|
|
165
|
-
|
|
504
|
+
hasGate(key, documentId) {
|
|
505
|
+
const viewport = this.getViewportState(documentId);
|
|
506
|
+
return viewport.gates.has(key);
|
|
507
|
+
}
|
|
508
|
+
getGates(documentId) {
|
|
509
|
+
const viewport = this.getViewportState(documentId);
|
|
510
|
+
return Array.from(viewport.gates);
|
|
511
|
+
}
|
|
512
|
+
getBoundingRect(documentId) {
|
|
513
|
+
const id = documentId ?? this.getActiveDocumentId();
|
|
514
|
+
const provider = this.rectProviders.get(id);
|
|
515
|
+
return (provider == null ? void 0 : provider()) ?? {
|
|
516
|
+
origin: { x: 0, y: 0 },
|
|
517
|
+
size: { width: 0, height: 0 }
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
scrollTo(pos, documentId) {
|
|
521
|
+
const id = documentId ?? this.getActiveDocumentId();
|
|
522
|
+
const viewport = this.getViewportState(id);
|
|
166
523
|
const { x, y, center, behavior = "auto" } = pos;
|
|
167
524
|
if (behavior === "smooth") {
|
|
168
|
-
this.dispatch(setSmoothScrollActivity(true));
|
|
525
|
+
this.dispatch(setSmoothScrollActivity(id, true));
|
|
169
526
|
}
|
|
527
|
+
let finalX = x;
|
|
528
|
+
let finalY = y;
|
|
170
529
|
if (center) {
|
|
171
|
-
const metrics =
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
});
|
|
179
|
-
} else {
|
|
180
|
-
this.scrollReq$.emit({
|
|
181
|
-
x,
|
|
182
|
-
y,
|
|
183
|
-
behavior
|
|
184
|
-
});
|
|
530
|
+
const metrics = viewport.viewportMetrics;
|
|
531
|
+
finalX = x - metrics.clientWidth / 2;
|
|
532
|
+
finalY = y - metrics.clientHeight / 2;
|
|
533
|
+
}
|
|
534
|
+
const emitter = this.scrollRequests$.get(id);
|
|
535
|
+
if (emitter) {
|
|
536
|
+
emitter.emit({ x: finalX, y: finalY, behavior });
|
|
185
537
|
}
|
|
186
538
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
isScrolling: this.state.isScrolling
|
|
191
|
-
};
|
|
192
|
-
this.scrollActivity$.emit(scrollActivity);
|
|
539
|
+
bumpScrollActivity(documentId) {
|
|
540
|
+
this.debouncedDispatch(setScrollActivity(documentId, false), this.scrollEndDelay);
|
|
541
|
+
this.debouncedDispatch(setSmoothScrollActivity(documentId, false), this.scrollEndDelay);
|
|
193
542
|
}
|
|
194
|
-
//
|
|
543
|
+
// ─────────────────────────────────────────────────────────
|
|
544
|
+
// State Change Handling
|
|
545
|
+
// ─────────────────────────────────────────────────────────
|
|
195
546
|
onStoreUpdated(prevState, newState) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
547
|
+
for (const documentId in newState.documents) {
|
|
548
|
+
const prevViewport = prevState.documents[documentId];
|
|
549
|
+
const newViewport = newState.documents[documentId];
|
|
550
|
+
if (prevViewport !== newViewport) {
|
|
551
|
+
this.viewportMetrics$.emit({
|
|
552
|
+
documentId,
|
|
553
|
+
metrics: newViewport.viewportMetrics
|
|
554
|
+
});
|
|
555
|
+
if (prevViewport && (prevViewport.isScrolling !== newViewport.isScrolling || prevViewport.isSmoothScrolling !== newViewport.isSmoothScrolling)) {
|
|
556
|
+
this.scrollActivity$.emit({
|
|
557
|
+
documentId,
|
|
558
|
+
activity: {
|
|
559
|
+
isScrolling: newViewport.isScrolling,
|
|
560
|
+
isSmoothScrolling: newViewport.isSmoothScrolling
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
if (prevViewport && prevViewport.gates !== newViewport.gates) {
|
|
565
|
+
const prevGates = Array.from(prevViewport.gates);
|
|
566
|
+
const newGates = Array.from(newViewport.gates);
|
|
567
|
+
const addedGate = newGates.find((g) => !prevGates.includes(g));
|
|
568
|
+
const removedGate = prevGates.find((g) => !newGates.includes(g));
|
|
569
|
+
this.gateState$.emit({
|
|
570
|
+
documentId,
|
|
571
|
+
isGated: newViewport.gates.size > 0,
|
|
572
|
+
gates: newGates,
|
|
573
|
+
addedGate,
|
|
574
|
+
removedGate
|
|
575
|
+
});
|
|
576
|
+
this.logger.debug(
|
|
577
|
+
"ViewportPlugin",
|
|
578
|
+
"GateStateChanged",
|
|
579
|
+
`Gate state changed for document ${documentId}. Gates: [${newGates.join(", ")}], Gated: ${newViewport.gates.size > 0}`
|
|
580
|
+
);
|
|
581
|
+
}
|
|
200
582
|
}
|
|
201
583
|
}
|
|
202
584
|
}
|
|
585
|
+
// ─────────────────────────────────────────────────────────
|
|
586
|
+
// Lifecycle
|
|
587
|
+
// ─────────────────────────────────────────────────────────
|
|
203
588
|
async initialize(_config) {
|
|
589
|
+
this.logger.info("ViewportPlugin", "Initialize", "Viewport plugin initialized");
|
|
204
590
|
}
|
|
205
591
|
async destroy() {
|
|
206
|
-
super.destroy();
|
|
207
592
|
this.viewportMetrics$.clear();
|
|
208
593
|
this.viewportResize$.clear();
|
|
209
594
|
this.scrollMetrics$.clear();
|
|
210
|
-
this.scrollReq$.clear();
|
|
211
595
|
this.scrollActivity$.clear();
|
|
212
|
-
this.
|
|
596
|
+
this.gateState$.clear();
|
|
597
|
+
this.scrollRequests$.forEach((emitter) => emitter.clear());
|
|
598
|
+
this.scrollRequests$.clear();
|
|
599
|
+
this.rectProviders.clear();
|
|
600
|
+
super.destroy();
|
|
213
601
|
}
|
|
214
602
|
};
|
|
215
603
|
_ViewportPlugin.id = "viewport";
|