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