@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/index.cjs CHANGED
@@ -1,105 +1,256 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_contract = require("./contract-B3tBU_U9.cjs");
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/capability.ts
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 + handlers + the captured-gesture owner + cursor
7
- * claims live in this closure (runtime registries, not serializable state); the
8
- * reducer state holds only the active tool id and the resolved cursor, so the UI
9
- * can react to them. The router (`dispatch`) is the heart: on down, the first
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 CLAIM (hover feedback
14
- * — 'move' over an annotation, 'text' over text) → over a page, the tool's
15
- * declared cursor → over a gap, the tool's `gapCursor` (default the neutral
16
- * arrow). The winning KEYWORD (claim or base, never gap) is then restyled
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 createInteractionCapability(ctx, builtinTools) {
89
+ function createInteractionController(ctx, builtinTools) {
90
+ const settings = ctx.settings();
22
91
  const tools = /* @__PURE__ */ new Map();
23
- for (const t of builtinTools) tools.set(t.id, t);
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 cursorSkins = /* @__PURE__ */ new Map();
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: /* @__PURE__ */ new Set()
117
+ enables: NO_TAGS
34
118
  };
35
- const active = () => toolOf(ctx.getState().activeToolId);
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 c of claims.values()) if (!top || c.priority > top.priority) top = c;
123
+ for (const claim of claims.values()) if (!top || claim.priority > top.priority) top = claim;
39
124
  const tool = active();
40
- const skin = cursorSkins.get(tool.id);
41
- if (top) return skin?.[top.cursor] ?? top.cursor;
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 skin?.[tool.cursor] ?? tool.cursor;
128
+ return cursors?.[tool.cursor] ?? tool.cursor;
44
129
  };
45
- const syncCursor = () => {
46
- const next = resolveCursor();
47
- if (next !== ctx.getState().cursor) ctx.dispatch({
48
- type: "SET_CURSOR",
49
- cursor: next
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 handlerSources = /* @__PURE__ */ new Map();
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((h) => {
56
- const hs = handlerSources.get(h);
57
- return hs === void 0 || source === void 0 || hs === source;
58
- }).filter((h) => h.enabledFor(tool)).sort((a, b) => b.priority - a.priority);
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
- return {
61
- activeTool: active,
62
- activeToolId: () => ctx.getState().activeToolId,
63
- cursor: () => ctx.getState().cursor,
64
- tools: () => [...tools.values()],
65
- activateTool: (id) => {
66
- if (!tools.has(id)) throw new Error(`[interaction] unknown tool '${id}'`);
67
- owner = null;
68
- claims.clear();
69
- ctx.dispatch({
70
- type: "SET_TOOL",
71
- toolId: id
72
- });
73
- syncCursor();
74
- toolCbs.forEach((cb) => cb());
75
- },
76
- onToolChange: (cb) => {
77
- toolCbs.add(cb);
78
- return () => toolCbs.delete(cb);
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
- registerTool: (tool) => {
81
- tools.set(tool.id, tool);
82
- return () => {
83
- tools.delete(tool.id);
84
- cursorSkins.delete(tool.id);
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, skin) => {
88
- if (skin === null) cursorSkins.delete(id);
89
- else cursorSkins.set(id, skin);
237
+ setToolCursor: (id, cursors) => {
238
+ if (cursors === null) toolCursors.delete(id);
239
+ else toolCursors.set(id, cursors);
90
240
  syncCursor();
91
241
  },
92
- registerHandler: (handler, options) => {
93
- handlers.push(handler);
94
- if (options?.source !== void 0) handlerSources.set(handler, options.source);
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
- setCursor: (token, cursor, priority = 0) => {
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 h of eligible(sample.source)) if (h.claimsTouch?.(sample)) return true;
265
+ for (const handler of eligible(sample.source)) if (handler.claimsTouch?.(sample)) return true;
112
266
  return false;
113
267
  },
114
- dispatch: (sample) => {
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
- for (const h of eligible(sample.source)) if (h.onDown(sample)) {
123
- owner = h;
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 h of eligible(sample.source)) h.onHover?.(sample);
128
- else if (sample.phase === "cancel") {
129
- const o = owner;
130
- owner = null;
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 two built-in tools. Features ADD tools via `registerTool` (a draw tool, a
162
- * redact tool…). `enables` is the composition seam:
163
- * pointer → text selection + annotation editing + annotation marquee selection
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 builtinTools = () => [{
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: require_contract.InteractionToken,
368
+ token: require_contract_host.InteractionToken,
203
369
  scope: "document",
204
- initialState: () => initialInteractionState(config),
205
- reduce: interactionReducer,
206
- capability: (ctx) => createInteractionCapability(ctx, builtinTools())
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 OUTPUT half of the interaction seam:
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
- * ONE workspace-scoped provider, MANY consumers: any plugin lists
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; an
217
- * annotation pickup or a snap later). The plugin itself stays DOM-free — the
218
- * PROVIDER is injected from outside (`@embedpdf/web` ships `vibrationFeedback`
219
- * and a WKWebView bridge; native shells bring their own), exactly like the
220
- * stage's Scheduler seam: host dependencies enter through explicit injection,
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 PLATFORM's haptic taxonomy — iOS's three
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 ONE feedback provider. Stateless: the capability IS
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
- initialState: () => ({}),
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.interactionReducer = interactionReducer;
257
- exports.samplePointOn = require_contract.samplePointOn;
438
+ exports.interactionState = interactionState;
258
439
 
259
440
  //# sourceMappingURL=index.cjs.map