@embedpdf/plugin-interaction 3.0.0-next.13 → 3.0.0-next.15
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/contract/host.cjs +17 -0
- package/dist/contract/host.cjs.map +1 -0
- package/dist/contract/host.d.cts +3 -0
- package/dist/contract/host.d.ts +3 -0
- package/dist/contract/host.js +14 -0
- package/dist/contract/host.js.map +1 -0
- package/dist/contract-C9IFDdDd.d.cts +179 -0
- package/dist/contract-C9IFDdDd.d.ts +179 -0
- package/dist/contract-CmEMPz9L.js +19 -0
- package/dist/contract-CmEMPz9L.js.map +1 -0
- package/dist/contract-yO6QKpba.cjs +36 -0
- package/dist/contract-yO6QKpba.cjs.map +1 -0
- package/dist/contract.cjs +2 -2
- package/dist/contract.d.cts +2 -2
- package/dist/contract.d.ts +2 -2
- package/dist/contract.js +2 -2
- package/dist/host-contract-Crcu-9DW.d.ts +121 -0
- package/dist/host-contract-DCb-QVfd.d.cts +121 -0
- package/dist/index.cjs +347 -166
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -52
- package/dist/index.d.ts +23 -52
- package/dist/index.js +347 -163
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
- package/dist/contract-B3tBU_U9.cjs +0 -36
- package/dist/contract-B3tBU_U9.cjs.map +0 -1
- package/dist/contract-CrM0i9pQ.js +0 -19
- package/dist/contract-CrM0i9pQ.js.map +0 -1
- package/dist/contract-F3gsFtSf.d.cts +0 -239
- package/dist/contract-F3gsFtSf.d.ts +0 -239
package/dist/index.js
CHANGED
|
@@ -1,104 +1,255 @@
|
|
|
1
|
-
import { n as InteractionToken, r as
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { n as InteractionToken, r as FeedbackToken, t as INTERACTION_DEFAULTS } from "./contract-CmEMPz9L.js";
|
|
2
|
+
import { InteractionToken as InteractionToken$1, samplePointOn } from "./contract/host.js";
|
|
3
|
+
import { PluginError, definePlugin, defineState, memo } from "@embedpdf/core";
|
|
4
|
+
//#region src/model.ts
|
|
5
|
+
/** A document starts on `pointer`; the controller then activates the `defaultTool` setting. */
|
|
6
|
+
const initialInteractionState = () => ({
|
|
7
|
+
activeToolId: "pointer",
|
|
8
|
+
toolStack: [],
|
|
9
|
+
cursor: "default"
|
|
10
|
+
});
|
|
11
|
+
/** Activate a tool and forget every pushed one. */
|
|
12
|
+
function activateTool(state, toolId) {
|
|
13
|
+
if (state.activeToolId === toolId && state.toolStack.length === 0) return state;
|
|
14
|
+
return {
|
|
15
|
+
...state,
|
|
16
|
+
activeToolId: toolId,
|
|
17
|
+
toolStack: []
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Activate a tool for a moment, saving the active one for {@link popTool}. */
|
|
21
|
+
function pushTool(state, toolId) {
|
|
22
|
+
return {
|
|
23
|
+
...state,
|
|
24
|
+
toolStack: [...state.toolStack, state.activeToolId],
|
|
25
|
+
activeToolId: toolId
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Activate the tool saved last; an empty stack changes nothing. */
|
|
29
|
+
function popTool(state) {
|
|
30
|
+
if (state.toolStack.length === 0) return state;
|
|
31
|
+
return {
|
|
32
|
+
...state,
|
|
33
|
+
toolStack: state.toolStack.slice(0, -1),
|
|
34
|
+
activeToolId: state.toolStack[state.toolStack.length - 1]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function setCursor(state, cursor) {
|
|
38
|
+
return state.cursor === cursor ? state : {
|
|
39
|
+
...state,
|
|
40
|
+
cursor
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The two built-in tools. Features add tools via `registerTool`. `enables`
|
|
45
|
+
* is the composition seam:
|
|
46
|
+
* pointer → text selection + annotation editing + marquee selection
|
|
47
|
+
* pan → scrolling (contributed by Stage) + annotation editing, no text select
|
|
48
|
+
* Both carry `form-fill` and `link-nav`: filling forms and following links is
|
|
49
|
+
* the resting state of a viewer (Acrobat's hand tool does both).
|
|
50
|
+
*/
|
|
51
|
+
const builtinTools = () => [{
|
|
52
|
+
id: "pointer",
|
|
53
|
+
cursor: "default",
|
|
54
|
+
enables: /* @__PURE__ */ new Set([
|
|
55
|
+
"text-select",
|
|
56
|
+
"annotation-edit",
|
|
57
|
+
"annotation-marquee",
|
|
58
|
+
"form-fill",
|
|
59
|
+
"link-nav"
|
|
60
|
+
])
|
|
61
|
+
}, {
|
|
62
|
+
id: "pan",
|
|
63
|
+
cursor: "grab",
|
|
64
|
+
gapCursor: "grab",
|
|
65
|
+
enables: /* @__PURE__ */ new Set([
|
|
66
|
+
"scroll",
|
|
67
|
+
"annotation-edit",
|
|
68
|
+
"form-fill",
|
|
69
|
+
"link-nav"
|
|
70
|
+
])
|
|
71
|
+
}];
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/controller.ts
|
|
74
|
+
/** A tool's own pointer methods take a press before every handler a plugin registers. */
|
|
75
|
+
const OWN_POINTER_PRIORITY = 1e3;
|
|
76
|
+
const NO_TAGS = /* @__PURE__ */ new Set();
|
|
4
77
|
/**
|
|
5
|
-
* The interaction hub. Tools
|
|
6
|
-
* claims
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* eligible handler (by priority) to return true OWNS the gesture; move/up route
|
|
10
|
-
* to it; with no owner, move is hover (cursor feedback).
|
|
78
|
+
* The interaction hub. Tools, handlers, the captured-gesture owner and cursor
|
|
79
|
+
* claims are registries in this closure; the state holds only the active
|
|
80
|
+
* tool, the tool stack and the resolved cursor, so UI can react. Registering
|
|
81
|
+
* or removing a tool wakes readers with `ctx.notify()`.
|
|
11
82
|
*
|
|
12
|
-
* Cursor arbitration, top to bottom: the highest-priority
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* through the armed tool's skin when it maps that keyword. One resolver, so an
|
|
17
|
-
* image cursor can never shadow hover feedback or outlive the page under the
|
|
18
|
-
* pointer.
|
|
83
|
+
* Cursor arbitration, top to bottom: the highest-priority claim (hover
|
|
84
|
+
* feedback) → over a page, the tool's declared cursor → over a gap, the
|
|
85
|
+
* tool's `gapCursor`. The winning keyword is replaced through the active
|
|
86
|
+
* tool's cursors (`setToolCursor`) when they name that keyword.
|
|
19
87
|
*/
|
|
20
|
-
function
|
|
88
|
+
function createInteractionController(ctx, builtinTools) {
|
|
89
|
+
const settings = ctx.settings();
|
|
21
90
|
const tools = /* @__PURE__ */ new Map();
|
|
22
|
-
|
|
91
|
+
/** The handler each tool with its own pointer methods brings, registered beside the plugins' handlers. */
|
|
92
|
+
const ownHandlers = /* @__PURE__ */ new Map();
|
|
93
|
+
/** Bumped on every tool registry change: the input of the tool list. */
|
|
94
|
+
let toolsVersion = 0;
|
|
23
95
|
const handlers = [];
|
|
96
|
+
const handlerSources = /* @__PURE__ */ new Map();
|
|
24
97
|
const claims = /* @__PURE__ */ new Map();
|
|
25
|
-
const
|
|
26
|
-
const toolCbs = /* @__PURE__ */ new Set();
|
|
98
|
+
const toolCursors = /* @__PURE__ */ new Map();
|
|
27
99
|
let owner = null;
|
|
100
|
+
/** The gesture `owner` took, as its events report it. */
|
|
101
|
+
let gesture = null;
|
|
102
|
+
/** Whether the last dispatched sample hit a page: gaps fall back to `gapCursor`. */
|
|
28
103
|
let overPage = false;
|
|
104
|
+
const toolChanged = ctx.events.source();
|
|
105
|
+
const gestureStarted = ctx.events.source();
|
|
106
|
+
const gestureEnded = ctx.events.source();
|
|
107
|
+
const gestureCancelled = ctx.events.source();
|
|
108
|
+
const cursorChanged = ctx.events.source();
|
|
109
|
+
ctx.state.onChange(({ previous, next }) => {
|
|
110
|
+
if (previous.cursor !== next.cursor) cursorChanged.emit({ cursor: next.cursor });
|
|
111
|
+
});
|
|
112
|
+
const state = () => ctx.state.get();
|
|
29
113
|
const toolOf = (id) => tools.get(id) ?? {
|
|
30
114
|
id,
|
|
31
115
|
cursor: "default",
|
|
32
|
-
enables:
|
|
116
|
+
enables: NO_TAGS
|
|
33
117
|
};
|
|
34
|
-
const active = () => toolOf(
|
|
118
|
+
const active = () => toolOf(state().activeToolId);
|
|
119
|
+
const listTools = memo(() => [toolsVersion], (_version) => [...tools.values()]);
|
|
35
120
|
const resolveCursor = () => {
|
|
36
121
|
let top = null;
|
|
37
|
-
for (const
|
|
122
|
+
for (const claim of claims.values()) if (!top || claim.priority > top.priority) top = claim;
|
|
38
123
|
const tool = active();
|
|
39
|
-
const
|
|
40
|
-
if (top) return
|
|
124
|
+
const cursors = toolCursors.get(tool.id);
|
|
125
|
+
if (top) return cursors?.[top.cursor] ?? top.cursor;
|
|
41
126
|
if (!overPage) return tool.gapCursor ?? "default";
|
|
42
|
-
return
|
|
127
|
+
return cursors?.[tool.cursor] ?? tool.cursor;
|
|
43
128
|
};
|
|
44
|
-
const syncCursor = () =>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
129
|
+
const syncCursor = () => ctx.state.update(setCursor, resolveCursor());
|
|
130
|
+
const removeHandler = (handler) => {
|
|
131
|
+
const index = handlers.indexOf(handler);
|
|
132
|
+
if (index >= 0) handlers.splice(index, 1);
|
|
133
|
+
handlerSources.delete(handler);
|
|
134
|
+
if (owner === handler) {
|
|
135
|
+
owner = null;
|
|
136
|
+
gesture = null;
|
|
137
|
+
}
|
|
50
138
|
};
|
|
51
|
-
const
|
|
139
|
+
const addHandler = (handler, source) => {
|
|
140
|
+
handlers.push(handler);
|
|
141
|
+
if (source !== void 0) handlerSources.set(handler, source);
|
|
142
|
+
return () => removeHandler(handler);
|
|
143
|
+
};
|
|
144
|
+
/** Take a tool out of the registry, with the handler its pointer methods brought. */
|
|
145
|
+
const removeTool = (tool) => {
|
|
146
|
+
tools.delete(tool.id);
|
|
147
|
+
const handler = ownHandlers.get(tool);
|
|
148
|
+
ownHandlers.delete(tool);
|
|
149
|
+
if (handler) removeHandler(handler);
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Put a tool in the registry without waking readers, and return the stored
|
|
153
|
+
* tool (tags filled in). A taken id is refused unless `replace`.
|
|
154
|
+
*/
|
|
155
|
+
const addTool = (input, replace) => {
|
|
156
|
+
const taken = tools.get(input.id);
|
|
157
|
+
if (taken && !replace) throw new PluginError("conflict", "interaction", `tool '${input.id}' is already registered; pass { replace: true } to replace it`);
|
|
158
|
+
if (taken) removeTool(taken);
|
|
159
|
+
const tool = input.enables ? input : {
|
|
160
|
+
...input,
|
|
161
|
+
enables: NO_TAGS
|
|
162
|
+
};
|
|
163
|
+
tools.set(tool.id, tool);
|
|
164
|
+
const handler = ownPointerHandler(tool);
|
|
165
|
+
if (handler) {
|
|
166
|
+
ownHandlers.set(tool, handler);
|
|
167
|
+
addHandler(handler);
|
|
168
|
+
}
|
|
169
|
+
return tool;
|
|
170
|
+
};
|
|
171
|
+
const toolsChanged = () => {
|
|
172
|
+
toolsVersion += 1;
|
|
173
|
+
ctx.notify();
|
|
174
|
+
};
|
|
175
|
+
/** The registration's remover: it removes this registration only, never a later one with its id. */
|
|
176
|
+
const removerOf = (tool) => () => {
|
|
177
|
+
if (tools.get(tool.id) !== tool) return;
|
|
178
|
+
removeTool(tool);
|
|
179
|
+
toolCursors.delete(tool.id);
|
|
180
|
+
toolsChanged();
|
|
181
|
+
};
|
|
182
|
+
for (const tool of builtinTools) addTool(tool, true);
|
|
183
|
+
let removeSettingTools = settings.get().tools.map((tool) => removerOf(addTool(tool, true)));
|
|
184
|
+
settings.api.onSettingsChanged(({ changed }) => {
|
|
185
|
+
if (!changed.includes("tools")) return;
|
|
186
|
+
for (const remove of removeSettingTools) remove();
|
|
187
|
+
removeSettingTools = settings.get().tools.map((tool) => removerOf(addTool(tool, true)));
|
|
188
|
+
toolsChanged();
|
|
189
|
+
});
|
|
190
|
+
ctx.state.update(activateTool, settings.get().defaultTool);
|
|
191
|
+
syncCursor();
|
|
192
|
+
/** Lens scoping: a handler registered with a `source` only sees samples stamped with it. */
|
|
52
193
|
const eligible = (source) => {
|
|
53
194
|
const tool = active();
|
|
54
|
-
return handlers.filter((
|
|
55
|
-
const
|
|
56
|
-
return
|
|
57
|
-
}).filter((
|
|
195
|
+
return handlers.filter((handler) => {
|
|
196
|
+
const handlerSource = handlerSources.get(handler);
|
|
197
|
+
return handlerSource === void 0 || source === void 0 || handlerSource === source;
|
|
198
|
+
}).filter((handler) => handler.enabledFor(tool)).sort((left, right) => right.priority - left.priority);
|
|
58
199
|
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
200
|
+
/**
|
|
201
|
+
* Activate a tool through `transition`. Every activation is announced,
|
|
202
|
+
* including activating the active tool again.
|
|
203
|
+
*/
|
|
204
|
+
function arm(id, transition) {
|
|
205
|
+
if (!tools.has(id)) throw new PluginError("not-found", "interaction", `unknown tool '${id}'`);
|
|
206
|
+
const previousToolId = state().activeToolId;
|
|
207
|
+
owner = null;
|
|
208
|
+
gesture = null;
|
|
209
|
+
claims.clear();
|
|
210
|
+
ctx.state.update(transition);
|
|
211
|
+
syncCursor();
|
|
212
|
+
toolChanged.emit({
|
|
213
|
+
toolId: id,
|
|
214
|
+
previousToolId
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return { api: {
|
|
218
|
+
...settings.api,
|
|
219
|
+
getActiveTool: active,
|
|
220
|
+
getActiveToolId: () => state().activeToolId,
|
|
221
|
+
getDefaultToolId: () => settings.get().defaultTool,
|
|
222
|
+
listTools,
|
|
223
|
+
getTool: (id) => tools.get(id) ?? null,
|
|
224
|
+
hasTool: (id) => tools.has(id),
|
|
225
|
+
activateTool: (id) => arm(id, (current) => activateTool(current, id)),
|
|
226
|
+
activateDefaultTool: () => {
|
|
227
|
+
const id = settings.get().defaultTool;
|
|
228
|
+
arm(id, (current) => activateTool(current, id));
|
|
78
229
|
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
};
|
|
230
|
+
pushTool: (id) => arm(id, (current) => pushTool(current, id)),
|
|
231
|
+
popTool: () => {
|
|
232
|
+
const stack = state().toolStack;
|
|
233
|
+
if (stack.length === 0) return;
|
|
234
|
+
arm(stack[stack.length - 1], popTool);
|
|
85
235
|
},
|
|
86
|
-
setToolCursor: (id,
|
|
87
|
-
if (
|
|
88
|
-
else
|
|
236
|
+
setToolCursor: (id, cursors) => {
|
|
237
|
+
if (cursors === null) toolCursors.delete(id);
|
|
238
|
+
else toolCursors.set(id, cursors);
|
|
89
239
|
syncCursor();
|
|
90
240
|
},
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return ()
|
|
95
|
-
const i = handlers.indexOf(handler);
|
|
96
|
-
if (i >= 0) handlers.splice(i, 1);
|
|
97
|
-
handlerSources.delete(handler);
|
|
98
|
-
if (owner === handler) owner = null;
|
|
99
|
-
};
|
|
241
|
+
registerTool: (tool, options) => {
|
|
242
|
+
const stored = addTool(tool, options?.replace ?? false);
|
|
243
|
+
toolsChanged();
|
|
244
|
+
return removerOf(stored);
|
|
100
245
|
},
|
|
101
|
-
|
|
246
|
+
onToolChanged: toolChanged.on,
|
|
247
|
+
onGestureStarted: gestureStarted.on,
|
|
248
|
+
onGestureEnded: gestureEnded.on,
|
|
249
|
+
onGestureCancelled: gestureCancelled.on,
|
|
250
|
+
activeToolEnables: (behavior) => active().enables.has(behavior),
|
|
251
|
+
registerHandler: (handler, options) => addHandler(handler, options?.source),
|
|
252
|
+
claimCursor: (token, cursor, priority = 0) => {
|
|
102
253
|
if (cursor === null) claims.delete(token);
|
|
103
254
|
else claims.set(token, {
|
|
104
255
|
cursor,
|
|
@@ -106,11 +257,14 @@ function createInteractionCapability(ctx, builtinTools) {
|
|
|
106
257
|
});
|
|
107
258
|
syncCursor();
|
|
108
259
|
},
|
|
260
|
+
getCursor: () => state().cursor,
|
|
261
|
+
hasCursorClaim: () => claims.size > 0,
|
|
262
|
+
onCursorChanged: cursorChanged.on,
|
|
109
263
|
wouldClaimTouch: (sample) => {
|
|
110
|
-
for (const
|
|
264
|
+
for (const handler of eligible(sample.source)) if (handler.claimsTouch?.(sample)) return true;
|
|
111
265
|
return false;
|
|
112
266
|
},
|
|
113
|
-
|
|
267
|
+
dispatchPointer: (sample) => {
|
|
114
268
|
const nowOverPage = sample.page != null;
|
|
115
269
|
if (nowOverPage !== overPage) {
|
|
116
270
|
overPage = nowOverPage;
|
|
@@ -118,108 +272,140 @@ function createInteractionCapability(ctx, builtinTools) {
|
|
|
118
272
|
}
|
|
119
273
|
if (sample.phase === "down") {
|
|
120
274
|
owner = null;
|
|
121
|
-
|
|
122
|
-
|
|
275
|
+
gesture = null;
|
|
276
|
+
for (const handler of eligible(sample.source)) if (handler.onDown(sample)) {
|
|
277
|
+
owner = handler;
|
|
278
|
+
gesture = {
|
|
279
|
+
toolId: state().activeToolId,
|
|
280
|
+
page: sample.page?.ref ?? null,
|
|
281
|
+
pointerType: sample.pointerType ?? "mouse"
|
|
282
|
+
};
|
|
283
|
+
gestureStarted.emit(gesture);
|
|
123
284
|
break;
|
|
124
285
|
}
|
|
125
286
|
} else if (sample.phase === "move") if (owner) owner.onMove?.(sample);
|
|
126
|
-
else for (const
|
|
127
|
-
else
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
if (o) (o.onCancel ?? o.onUp)?.call(o, sample);
|
|
131
|
-
} else {
|
|
132
|
-
owner?.onUp?.(sample);
|
|
287
|
+
else for (const handler of eligible(sample.source)) handler.onHover?.(sample);
|
|
288
|
+
else {
|
|
289
|
+
const captured = owner;
|
|
290
|
+
const taken = gesture;
|
|
133
291
|
owner = null;
|
|
292
|
+
gesture = null;
|
|
293
|
+
if (!captured || !taken) return;
|
|
294
|
+
if (sample.phase === "cancel") {
|
|
295
|
+
(captured.onCancel ?? captured.onUp)?.call(captured, sample);
|
|
296
|
+
gestureCancelled.emit(taken);
|
|
297
|
+
} else {
|
|
298
|
+
captured.onUp?.(sample);
|
|
299
|
+
gestureEnded.emit(taken);
|
|
300
|
+
}
|
|
134
301
|
}
|
|
135
302
|
}
|
|
303
|
+
} };
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* The handler a tool's own pointer methods bring, or null for a tool without
|
|
307
|
+
* them: it is live only while that tool is active, and turns samples into
|
|
308
|
+
* {@link ToolPointerEvent}s in page coordinates. A gesture keeps the page it
|
|
309
|
+
* started on, so a drag that leaves the page goes on in that page's
|
|
310
|
+
* coordinates.
|
|
311
|
+
*/
|
|
312
|
+
function ownPointerHandler(tool) {
|
|
313
|
+
if (!tool.onPointerDown && !tool.onHover) return null;
|
|
314
|
+
let home = null;
|
|
315
|
+
let last = null;
|
|
316
|
+
const eventOf = (page, point, sample) => ({
|
|
317
|
+
page,
|
|
318
|
+
point,
|
|
319
|
+
modifiers: sample.modifiers,
|
|
320
|
+
pointerType: sample.pointerType ?? "mouse"
|
|
321
|
+
});
|
|
322
|
+
/** The sample on the gesture's page, or the last event when the source can't project it there. */
|
|
323
|
+
const follow = (sample) => {
|
|
324
|
+
if (!home) return null;
|
|
325
|
+
const point = samplePointOn(sample, home);
|
|
326
|
+
if (point) last = eventOf(home, point, sample);
|
|
327
|
+
return last;
|
|
328
|
+
};
|
|
329
|
+
const end = (sample) => {
|
|
330
|
+
const event = follow(sample);
|
|
331
|
+
home = null;
|
|
332
|
+
last = null;
|
|
333
|
+
if (event) tool.onPointerUp?.(event);
|
|
334
|
+
};
|
|
335
|
+
return {
|
|
336
|
+
id: `tool:${tool.id}`,
|
|
337
|
+
priority: OWN_POINTER_PRIORITY,
|
|
338
|
+
enabledFor: (active) => active === tool,
|
|
339
|
+
onDown: (sample) => {
|
|
340
|
+
if (!sample.page || !tool.onPointerDown) return false;
|
|
341
|
+
const event = eventOf(sample.page.ref, sample.page.point, sample);
|
|
342
|
+
if (tool.onPointerDown(event) !== true) return false;
|
|
343
|
+
home = sample.page.ref;
|
|
344
|
+
last = event;
|
|
345
|
+
return true;
|
|
346
|
+
},
|
|
347
|
+
onMove: (sample) => {
|
|
348
|
+
const event = follow(sample);
|
|
349
|
+
if (event) tool.onPointerMove?.(event);
|
|
350
|
+
},
|
|
351
|
+
onUp: end,
|
|
352
|
+
onCancel: end,
|
|
353
|
+
onHover: (sample) => {
|
|
354
|
+
if (sample.page) tool.onHover?.(eventOf(sample.page.ref, sample.page.point, sample));
|
|
355
|
+
}
|
|
136
356
|
};
|
|
137
357
|
}
|
|
138
358
|
//#endregion
|
|
139
|
-
//#region src/reducer.ts
|
|
140
|
-
const initialInteractionState = (config) => ({
|
|
141
|
-
activeToolId: config.defaultTool ?? "pointer",
|
|
142
|
-
cursor: "default"
|
|
143
|
-
});
|
|
144
|
-
const interactionReducer = (state, action) => {
|
|
145
|
-
switch (action.type) {
|
|
146
|
-
case "SET_TOOL": return state.activeToolId === action.toolId ? state : {
|
|
147
|
-
...state,
|
|
148
|
-
activeToolId: action.toolId
|
|
149
|
-
};
|
|
150
|
-
case "SET_CURSOR": return state.cursor === action.cursor ? state : {
|
|
151
|
-
...state,
|
|
152
|
-
cursor: action.cursor
|
|
153
|
-
};
|
|
154
|
-
default: return state;
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
//#endregion
|
|
158
359
|
//#region src/interaction.plugin.ts
|
|
159
360
|
/**
|
|
160
|
-
* The
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* pan → scrolling (contributed by Stage) + annotation editing, NO text select
|
|
164
|
-
*
|
|
165
|
-
* Both carry `form-fill`: filling a form is the RESTING state of a viewer
|
|
166
|
-
* (Acrobat's hand tool fills forms too), so widgets are fill-controls under the
|
|
167
|
-
* default tools and only become geometry-editable under a form-design tool.
|
|
168
|
-
* Both carry `link-nav` for the same reason: links navigate under the default
|
|
169
|
-
* tools (Acrobat behaviour) and only become geometry-editable under the link
|
|
170
|
-
* tool. Tags are opaque to the hub — without the form/link plugins they're inert.
|
|
361
|
+
* The interaction hub: document-scoped, depends on nothing. Feature plugins
|
|
362
|
+
* `require` this token; the Stage `optional`-ly contributes a scroll handler.
|
|
363
|
+
* `config` is the settings the app registers, over {@link INTERACTION_DEFAULTS}.
|
|
171
364
|
*/
|
|
172
|
-
const
|
|
173
|
-
id: "pointer",
|
|
174
|
-
cursor: "default",
|
|
175
|
-
enables: /* @__PURE__ */ new Set([
|
|
176
|
-
"text-select",
|
|
177
|
-
"annotation-edit",
|
|
178
|
-
"annotation-marquee",
|
|
179
|
-
"form-fill",
|
|
180
|
-
"link-nav"
|
|
181
|
-
])
|
|
182
|
-
}, {
|
|
183
|
-
id: "pan",
|
|
184
|
-
cursor: "grab",
|
|
185
|
-
gapCursor: "grab",
|
|
186
|
-
enables: /* @__PURE__ */ new Set([
|
|
187
|
-
"scroll",
|
|
188
|
-
"annotation-edit",
|
|
189
|
-
"form-fill",
|
|
190
|
-
"link-nav"
|
|
191
|
-
])
|
|
192
|
-
}];
|
|
193
|
-
/**
|
|
194
|
-
* The interaction hub plugin — document-scoped, depends on nothing. Every feature
|
|
195
|
-
* plugin (selection, annotation, forms, redaction) `requires` this token; Stage
|
|
196
|
-
* `optional`-ly contributes a scroll handler. Works with `<Stage>` or a standalone
|
|
197
|
-
* `<PageView>` — it only routes page-space pointer samples.
|
|
198
|
-
*/
|
|
199
|
-
const interactionPlugin = (config = {}) => definePlugin({
|
|
365
|
+
const interactionPlugin = (config) => definePlugin({
|
|
200
366
|
id: "interaction",
|
|
201
|
-
token: InteractionToken,
|
|
367
|
+
token: InteractionToken$1,
|
|
202
368
|
scope: "document",
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
369
|
+
state: initialInteractionState,
|
|
370
|
+
settings: {
|
|
371
|
+
defaults: INTERACTION_DEFAULTS,
|
|
372
|
+
registered: config
|
|
373
|
+
},
|
|
374
|
+
create: (ctx) => createInteractionController(ctx, builtinTools())
|
|
375
|
+
});
|
|
376
|
+
//#endregion
|
|
377
|
+
//#region src/state.ts
|
|
378
|
+
/**
|
|
379
|
+
* The tools page's State table as code: what `useInteractionState()` returns,
|
|
380
|
+
* and the same fields in every other framework. With no document there is no
|
|
381
|
+
* active tool and no tool to switch to.
|
|
382
|
+
*/
|
|
383
|
+
const interactionState = defineState(InteractionToken, {
|
|
384
|
+
read: (interaction) => ({
|
|
385
|
+
activeToolId: interaction.getActiveToolId(),
|
|
386
|
+
tools: interaction.listTools()
|
|
387
|
+
}),
|
|
388
|
+
empty: {
|
|
389
|
+
activeToolId: null,
|
|
390
|
+
tools: []
|
|
391
|
+
}
|
|
206
392
|
});
|
|
207
393
|
//#endregion
|
|
208
394
|
//#region src/feedback.ts
|
|
209
395
|
/**
|
|
210
|
-
* Platform feedback (haptics) — the
|
|
396
|
+
* Platform feedback (haptics) — the output half of the interaction seam:
|
|
211
397
|
* pointers come in, feedback goes back out to the device.
|
|
212
398
|
*
|
|
213
|
-
*
|
|
399
|
+
* One workspace-scoped provider, many consumers: any plugin lists
|
|
214
400
|
* {@link FeedbackToken} as an optional dependency and calls the capability at
|
|
215
|
-
* its semantic moments (the selection handler on a long-press word-select
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* never a hidden global.
|
|
401
|
+
* its semantic moments (the selection handler on a long-press word-select).
|
|
402
|
+
* The plugin itself stays DOM-free — the provider is injected from outside
|
|
403
|
+
* (`@embedpdf/web` ships `vibrationFeedback` and a WKWebView bridge; native
|
|
404
|
+
* shells bring their own), as the host's clock enters the kernel
|
|
405
|
+
* (`createKernel({ clock })`): host dependencies enter through explicit
|
|
406
|
+
* injection, never a hidden global.
|
|
221
407
|
*
|
|
222
|
-
* The vocabulary is deliberately the
|
|
408
|
+
* The vocabulary is deliberately the platform's haptic taxonomy — iOS's three
|
|
223
409
|
* generator families, which Android mirrors in `HapticFeedbackConstants` and
|
|
224
410
|
* the web approximates with vibration patterns — not app vocabulary. A verb
|
|
225
411
|
* belongs here only if a provider would map it to a physically different
|
|
@@ -233,18 +419,16 @@ const NOOP = {
|
|
|
233
419
|
notify() {}
|
|
234
420
|
};
|
|
235
421
|
/**
|
|
236
|
-
* Register the workspace's
|
|
237
|
-
* the provider (or the no-op).
|
|
422
|
+
* Register the workspace's one feedback provider. Stateless: the capability
|
|
423
|
+
* is the provider (or the no-op).
|
|
238
424
|
*/
|
|
239
425
|
const feedbackPlugin = (options = {}) => definePlugin({
|
|
240
426
|
id: "feedback",
|
|
241
427
|
scope: "workspace",
|
|
242
428
|
token: FeedbackToken,
|
|
243
|
-
|
|
244
|
-
reduce: (state) => state,
|
|
245
|
-
capability: () => options.provider ?? NOOP
|
|
429
|
+
create: () => ({ api: options.provider ?? NOOP })
|
|
246
430
|
});
|
|
247
431
|
//#endregion
|
|
248
|
-
export { FeedbackToken,
|
|
432
|
+
export { FeedbackToken, INTERACTION_DEFAULTS, InteractionToken, feedbackPlugin, interactionPlugin, interactionState };
|
|
249
433
|
|
|
250
434
|
//# sourceMappingURL=index.js.map
|