@floegence/redevplugin-ui 3.0.21 → 3.0.23
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/opaque-surface-policy.gen.d.ts +1 -0
- package/dist/opaque-surface-policy.gen.js +1 -0
- package/dist/plugin.d.ts +1 -1
- package/dist/surface.d.ts +43 -1
- package/dist/surface.js +231 -4
- package/package.json +2 -2
|
@@ -33,6 +33,7 @@ export declare const opaqueSurfaceRenderLimits: {
|
|
|
33
33
|
readonly max_text_length: 65536;
|
|
34
34
|
readonly max_attribute_value_length: 4096;
|
|
35
35
|
readonly max_form_fields: 128;
|
|
36
|
+
readonly max_keyboard_bindings: 128;
|
|
36
37
|
readonly max_canvas_count: 4;
|
|
37
38
|
readonly max_canvas_dimension: 4096;
|
|
38
39
|
readonly max_canvas_total_pixels: 16777216;
|
|
@@ -215,6 +215,7 @@ export const opaqueSurfaceRenderLimits = {
|
|
|
215
215
|
"max_text_length": 65536,
|
|
216
216
|
"max_attribute_value_length": 4096,
|
|
217
217
|
"max_form_fields": 128,
|
|
218
|
+
"max_keyboard_bindings": 128,
|
|
218
219
|
"max_canvas_count": 4,
|
|
219
220
|
"max_canvas_dimension": 4096,
|
|
220
221
|
"max_canvas_total_pixels": 16777216,
|
package/dist/plugin.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export { PluginBridgeClient } from "./surface.js";
|
|
|
2
2
|
export { PluginBridgeError } from "./errors.js";
|
|
3
3
|
export { pluginAPIMajor } from "./contracts.gen.js";
|
|
4
4
|
export { callCapabilityOperation, callCapabilityStream, callCapabilitySync, isCapabilityBusinessError, } from "./capability-client.js";
|
|
5
|
-
export type { BridgeLifecycleEvent, MessagePortLike, PluginBridgeClientOptions, PluginBridgeRequestOptions, PluginCanvasAccessibilityState, PluginCanvasInputEvent, PluginCanvasKeyEvent, PluginCanvasPointerEvent, PluginCanvasResizeEvent, PluginCanvasSurface, PluginJSONObject, PluginJSONValue, PluginMethodResult, PluginExecutionFailureCode, PluginExecutionSnapshot, PluginExecutionProgress, PluginExecutionEvent, PluginExecutionEventList, PluginSurfaceContext, PluginUIActionEvent, PluginUIAttributeValue, PluginUIElementVNode, PluginUIPatchOperation, PluginUIVNode, } from "./surface.js";
|
|
5
|
+
export type { BridgeLifecycleEvent, MessagePortLike, PluginBridgeClientOptions, PluginBridgeRequestOptions, PluginCanvasAccessibilityState, PluginCanvasInputEvent, PluginCanvasKeyEvent, PluginCanvasPointerEvent, PluginCanvasWheelEvent, PluginCanvasResizeEvent, PluginCanvasSurface, PluginSurfaceKeyboardBinding, PluginSurfaceKeyboardEvent, PluginSurfaceKeyboardTargetKind, PluginJSONObject, PluginJSONValue, PluginMethodResult, PluginExecutionFailureCode, PluginExecutionSnapshot, PluginExecutionProgress, PluginExecutionEvent, PluginExecutionEventList, PluginSurfaceContext, PluginUIActionEvent, PluginUIAttributeValue, PluginUIElementVNode, PluginUIPatchOperation, PluginUIVNode, } from "./surface.js";
|
|
6
6
|
export type { PluginCapabilitySchema, PluginCapabilityEffect, PluginCapabilityOperationContract, PluginCapabilityStreamContract, PluginCapabilitySyncContract, PluginCapabilityStreamEvent, PluginCapabilityStreamReadResult, PluginExecution, PluginExecutionTerminalStatus, PluginExecutionWaitOptions, PluginStream, } from "./capability-client.js";
|
package/dist/surface.d.ts
CHANGED
|
@@ -136,7 +136,47 @@ export type PluginCanvasPointerEvent = {
|
|
|
136
136
|
y: number;
|
|
137
137
|
pressure: number;
|
|
138
138
|
};
|
|
139
|
-
export type
|
|
139
|
+
export type PluginCanvasWheelEvent = {
|
|
140
|
+
type: "wheel";
|
|
141
|
+
x: number;
|
|
142
|
+
y: number;
|
|
143
|
+
deltaX: number;
|
|
144
|
+
deltaY: number;
|
|
145
|
+
deltaMode: 0 | 1 | 2;
|
|
146
|
+
altKey: boolean;
|
|
147
|
+
ctrlKey: boolean;
|
|
148
|
+
metaKey: boolean;
|
|
149
|
+
shiftKey: boolean;
|
|
150
|
+
};
|
|
151
|
+
export type PluginCanvasInputEvent = PluginCanvasFocusEvent | PluginCanvasResizeEvent | PluginCanvasKeyEvent | PluginCanvasPointerEvent | PluginCanvasWheelEvent;
|
|
152
|
+
export type PluginSurfaceKeyboardTargetKind = "canvas" | "editable" | "control" | "surface";
|
|
153
|
+
export type PluginSurfaceKeyboardBinding = {
|
|
154
|
+
id: string;
|
|
155
|
+
event: "keydown" | "keyup";
|
|
156
|
+
code: string;
|
|
157
|
+
repeat: boolean;
|
|
158
|
+
altKey: boolean;
|
|
159
|
+
ctrlKey: boolean;
|
|
160
|
+
metaKey: boolean;
|
|
161
|
+
shiftKey: boolean;
|
|
162
|
+
targetKey: string | null;
|
|
163
|
+
targetKind: PluginSurfaceKeyboardTargetKind;
|
|
164
|
+
};
|
|
165
|
+
export type PluginSurfaceKeyboardEvent = {
|
|
166
|
+
event: "keydown" | "keyup";
|
|
167
|
+
code: string;
|
|
168
|
+
key: string;
|
|
169
|
+
repeat: boolean;
|
|
170
|
+
altKey: boolean;
|
|
171
|
+
ctrlKey: boolean;
|
|
172
|
+
metaKey: boolean;
|
|
173
|
+
shiftKey: boolean;
|
|
174
|
+
isComposing: boolean;
|
|
175
|
+
targetKey: string | null;
|
|
176
|
+
targetKind: PluginSurfaceKeyboardTargetKind;
|
|
177
|
+
defaultPrevented: boolean;
|
|
178
|
+
bindingId: string | null;
|
|
179
|
+
};
|
|
140
180
|
export type PluginMethodResult<T = unknown> = {
|
|
141
181
|
data: T;
|
|
142
182
|
execution_id?: string;
|
|
@@ -293,6 +333,8 @@ export declare class PluginBridgeClient {
|
|
|
293
333
|
render(tree: PluginUIVNode): Promise<void>;
|
|
294
334
|
openCanvas(canvasId: string): Promise<PluginCanvasSurface>;
|
|
295
335
|
updateCanvasAccessibility(canvasId: string, state: PluginCanvasAccessibilityState): Promise<void>;
|
|
336
|
+
setKeyboardBindings(bindings: readonly PluginSurfaceKeyboardBinding[]): Promise<void>;
|
|
337
|
+
onKeyboardInput(handler: (event: PluginSurfaceKeyboardEvent) => void): () => void;
|
|
296
338
|
loadImageAsset(assetId: string): Promise<ImageBitmap>;
|
|
297
339
|
onAction(action: string, handler: (event: PluginUIActionEvent) => void): () => void;
|
|
298
340
|
onCanvasInput(canvasId: string, handler: (event: PluginCanvasInputEvent) => void): () => void;
|
package/dist/surface.js
CHANGED
|
@@ -28,6 +28,7 @@ export class PluginBridgeClient {
|
|
|
28
28
|
#pending = new Map();
|
|
29
29
|
#actionHandlers = new Map();
|
|
30
30
|
#canvasInputHandlers = new Map();
|
|
31
|
+
#keyboardInputHandlers = new Set();
|
|
31
32
|
#lifecycleHandlers = new Set();
|
|
32
33
|
#contextHandlers = new Set();
|
|
33
34
|
#context;
|
|
@@ -194,6 +195,37 @@ export class PluginBridgeClient {
|
|
|
194
195
|
description: state.description,
|
|
195
196
|
});
|
|
196
197
|
}
|
|
198
|
+
setKeyboardBindings(bindings) {
|
|
199
|
+
this.#assertActive();
|
|
200
|
+
if (!validKeyboardBindings(bindings)) {
|
|
201
|
+
throw new PluginBridgeError("PLUGIN_INVALID_REQUEST", "Plugin surface keyboard bindings are invalid");
|
|
202
|
+
}
|
|
203
|
+
const id = this.#requestID("keyboard");
|
|
204
|
+
return this.#request(id, {
|
|
205
|
+
type: "redevplugin.ui.keyboard.bindings",
|
|
206
|
+
id,
|
|
207
|
+
bindings: bindings.map((binding) => ({
|
|
208
|
+
binding_id: binding.id,
|
|
209
|
+
event: binding.event,
|
|
210
|
+
code: binding.code,
|
|
211
|
+
repeat: binding.repeat,
|
|
212
|
+
alt_key: binding.altKey,
|
|
213
|
+
ctrl_key: binding.ctrlKey,
|
|
214
|
+
meta_key: binding.metaKey,
|
|
215
|
+
shift_key: binding.shiftKey,
|
|
216
|
+
target_key: binding.targetKey,
|
|
217
|
+
target_kind: binding.targetKind,
|
|
218
|
+
})),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
onKeyboardInput(handler) {
|
|
222
|
+
this.#assertActive();
|
|
223
|
+
if (typeof handler !== "function") {
|
|
224
|
+
throw new PluginBridgeError("PLUGIN_INVALID_REQUEST", "Plugin surface keyboard input subscription is invalid");
|
|
225
|
+
}
|
|
226
|
+
this.#keyboardInputHandlers.add(handler);
|
|
227
|
+
return () => this.#keyboardInputHandlers.delete(handler);
|
|
228
|
+
}
|
|
197
229
|
loadImageAsset(assetId) {
|
|
198
230
|
this.#assertActive();
|
|
199
231
|
if (!validUIIdentifier(assetId)) {
|
|
@@ -257,6 +289,7 @@ export class PluginBridgeClient {
|
|
|
257
289
|
this.#pending.clear();
|
|
258
290
|
this.#actionHandlers.clear();
|
|
259
291
|
this.#canvasInputHandlers.clear();
|
|
292
|
+
this.#keyboardInputHandlers.clear();
|
|
260
293
|
this.#lifecycleHandlers.clear();
|
|
261
294
|
this.#contextHandlers.clear();
|
|
262
295
|
this.#context = undefined;
|
|
@@ -517,6 +550,12 @@ export class PluginBridgeClient {
|
|
|
517
550
|
if (canvasInput) {
|
|
518
551
|
for (const handler of this.#canvasInputHandlers.get(canvasInput.canvasId) ?? [])
|
|
519
552
|
handler(canvasInput.event);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const keyboardInput = publicKeyboardInputMessage(data);
|
|
556
|
+
if (keyboardInput) {
|
|
557
|
+
for (const handler of this.#keyboardInputHandlers)
|
|
558
|
+
handler(keyboardInput);
|
|
520
559
|
}
|
|
521
560
|
}
|
|
522
561
|
#requestID(prefix) {
|
|
@@ -742,6 +781,7 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
742
781
|
const maxTextLength = ${opaqueSurfaceRenderLimits.max_text_length};
|
|
743
782
|
const maxAttributeValueLength = ${opaqueSurfaceRenderLimits.max_attribute_value_length};
|
|
744
783
|
const maxFormFields = ${opaqueSurfaceRenderLimits.max_form_fields};
|
|
784
|
+
const maxKeyboardBindings = ${opaqueSurfaceRenderLimits.max_keyboard_bindings};
|
|
745
785
|
const maxCanvasCount = ${opaqueSurfaceRenderLimits.max_canvas_count};
|
|
746
786
|
const maxCanvasDimension = ${opaqueSurfaceRenderLimits.max_canvas_dimension};
|
|
747
787
|
const maxCanvasTotalPixels = ${opaqueSurfaceRenderLimits.max_canvas_total_pixels};
|
|
@@ -867,7 +907,7 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
867
907
|
let runtimeDisposed = false;
|
|
868
908
|
const pendingWorkerRequests = new Set();
|
|
869
909
|
const pendingRendererCommits = new Set();
|
|
870
|
-
const requestSequence = { rpc: 0, execution: 0, render: 0, canvas: 0, asset: 0 };
|
|
910
|
+
const requestSequence = { rpc: 0, execution: 0, render: 0, canvas: 0, asset: 0, keyboard: 0 };
|
|
871
911
|
let executionQuerySurfaceTokens = 8;
|
|
872
912
|
let executionQuerySurfaceRefillAt = performance.now();
|
|
873
913
|
const executionQueryStateIdleMS = 120000;
|
|
@@ -881,6 +921,7 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
881
921
|
let uiGraph = new Map();
|
|
882
922
|
let uiNodes = new Map();
|
|
883
923
|
const controlEdits = new Map();
|
|
924
|
+
let keyboardBindings = [];
|
|
884
925
|
let lastAutofocusIdentity = "";
|
|
885
926
|
const pendingAssets = new Map();
|
|
886
927
|
const queuedAssets = [];
|
|
@@ -898,6 +939,55 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
898
939
|
let retainedImagePixels = 0;
|
|
899
940
|
const canvasRuntimes = new Map();
|
|
900
941
|
|
|
942
|
+
const keyboardTarget = (target) => {
|
|
943
|
+
const origin = target instanceof Element ? target : document.activeElement instanceof Element ? document.activeElement : null;
|
|
944
|
+
const keyed = origin?.closest("[data-redevplugin-key]");
|
|
945
|
+
const key = keyed?.getAttribute("data-redevplugin-key");
|
|
946
|
+
const targetKey = validResourceIdentifier(key) ? key : null;
|
|
947
|
+
const control = origin?.closest("canvas, input, textarea, select, button");
|
|
948
|
+
if (!control) return { targetKey, targetKind: "surface" };
|
|
949
|
+
const tag = control.tagName.toLowerCase();
|
|
950
|
+
if (tag === "canvas") return { targetKey, targetKind: "canvas" };
|
|
951
|
+
if (tag === "textarea") return { targetKey, targetKind: "editable" };
|
|
952
|
+
if (tag === "input" && ["text", "search", "number"].includes((control.getAttribute("type") || "text").toLowerCase())) {
|
|
953
|
+
return { targetKey, targetKind: "editable" };
|
|
954
|
+
}
|
|
955
|
+
return { targetKey, targetKind: "control" };
|
|
956
|
+
};
|
|
957
|
+
const handleSurfaceKeyboard = (event) => {
|
|
958
|
+
if (!initialized || runtimeDisposed || pendingQuiesceID || !workerPort || !["keydown", "keyup"].includes(event.type)) return;
|
|
959
|
+
const target = keyboardTarget(event.target);
|
|
960
|
+
const edit = target.targetKey === null ? undefined : controlEdits.get(target.targetKey);
|
|
961
|
+
const isComposing = Boolean(event.isComposing || edit?.isComposing);
|
|
962
|
+
const binding = isComposing ? undefined : keyboardBindings.find((candidate) =>
|
|
963
|
+
candidate.event === event.type && candidate.code === String(event.code || "") &&
|
|
964
|
+
candidate.repeat === Boolean(event.repeat) && candidate.alt_key === Boolean(event.altKey) &&
|
|
965
|
+
candidate.ctrl_key === Boolean(event.ctrlKey) && candidate.meta_key === Boolean(event.metaKey) &&
|
|
966
|
+
candidate.shift_key === Boolean(event.shiftKey) && candidate.target_key === target.targetKey &&
|
|
967
|
+
candidate.target_kind === target.targetKind
|
|
968
|
+
);
|
|
969
|
+
if (binding) event.preventDefault();
|
|
970
|
+
sendWorker({
|
|
971
|
+
type: "redevplugin.ui.keyboard.input",
|
|
972
|
+
event: {
|
|
973
|
+
event: event.type,
|
|
974
|
+
code: String(event.code || "").slice(0, 64),
|
|
975
|
+
key: String(event.key || "").slice(0, 64),
|
|
976
|
+
repeat: Boolean(event.repeat),
|
|
977
|
+
alt_key: Boolean(event.altKey),
|
|
978
|
+
ctrl_key: Boolean(event.ctrlKey),
|
|
979
|
+
meta_key: Boolean(event.metaKey),
|
|
980
|
+
shift_key: Boolean(event.shiftKey),
|
|
981
|
+
is_composing: isComposing,
|
|
982
|
+
target_key: target.targetKey,
|
|
983
|
+
target_kind: target.targetKind,
|
|
984
|
+
default_prevented: Boolean(event.defaultPrevented),
|
|
985
|
+
binding_id: binding?.binding_id || null,
|
|
986
|
+
},
|
|
987
|
+
});
|
|
988
|
+
};
|
|
989
|
+
for (const eventType of ["keydown", "keyup"]) document.addEventListener(eventType, handleSurfaceKeyboard, true);
|
|
990
|
+
|
|
901
991
|
const sendParent = (message) => {
|
|
902
992
|
if (parentPort && withinLimit(message)) parentPort.postMessage(message);
|
|
903
993
|
};
|
|
@@ -937,7 +1027,12 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
937
1027
|
};
|
|
938
1028
|
document.addEventListener("pointerdown", (event) => sendInteraction("activation", event.target), true);
|
|
939
1029
|
document.addEventListener("focusin", (event) => sendInteraction("focus", event.target), true);
|
|
940
|
-
|
|
1030
|
+
const ownsCanvasWheel = (target) => {
|
|
1031
|
+
const element = target instanceof Element ? target.closest("canvas[data-redevplugin-canvas]") : null;
|
|
1032
|
+
const canvasID = element?.getAttribute("data-redevplugin-canvas");
|
|
1033
|
+
return validResourceIdentifier(canvasID) && canvasRuntimes.has(canvasID);
|
|
1034
|
+
};
|
|
1035
|
+
document.addEventListener("wheel", (event) => sendInteraction("wheel", event.target, { localScroll: ownsCanvasWheel(event.target) || ownsLocalScroll(event) }), { capture: true, passive: true });
|
|
941
1036
|
document.addEventListener("selectionchange", () => {
|
|
942
1037
|
const selection = document.getSelection();
|
|
943
1038
|
const target = selection?.anchorNode instanceof Element ? selection.anchorNode : selection?.anchorNode?.parentElement;
|
|
@@ -972,6 +1067,8 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
972
1067
|
workerHeartbeatTimeout = undefined;
|
|
973
1068
|
workerHeartbeatPendingID = undefined;
|
|
974
1069
|
workerReady = false;
|
|
1070
|
+
keyboardBindings = [];
|
|
1071
|
+
for (const eventType of ["keydown", "keyup"]) document.removeEventListener(eventType, handleSurfaceKeyboard, true);
|
|
975
1072
|
for (const runtime of canvasRuntimes.values()) runtime.dispose();
|
|
976
1073
|
canvasRuntimes.clear();
|
|
977
1074
|
if (workerControlPort) {
|
|
@@ -1983,7 +2080,7 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
1983
2080
|
const validCall = (value) => exactKeys(value, ["type", "request"]) && value.type === "redevplugin.bridge.call" && isRecord(value.request) && Object.keys(value.request).every((key) => ["id", "method", "params"].includes(key)) && typeof value.request.id === "string" && value.request.id.length <= 128 && typeof value.request.method === "string" && /^[A-Za-z0-9._:-]{1,256}$/.test(value.request.method) && (value.request.params === undefined || isRecord(value.request.params));
|
|
1984
2081
|
const requestID = (value, expectedKind) => {
|
|
1985
2082
|
if (typeof value !== "string") return undefined;
|
|
1986
|
-
const match = /^(rpc|execution|render|canvas|asset)_([1-9][0-9]{0,15})$/.exec(value);
|
|
2083
|
+
const match = /^(rpc|execution|render|canvas|asset|keyboard)_([1-9][0-9]{0,15})$/.exec(value);
|
|
1987
2084
|
if (!match || match[1] !== expectedKind) return undefined;
|
|
1988
2085
|
const sequence = Number(match[2]);
|
|
1989
2086
|
return Number.isSafeInteger(sequence) ? { kind: match[1], sequence } : undefined;
|
|
@@ -1996,6 +2093,26 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
1996
2093
|
return true;
|
|
1997
2094
|
};
|
|
1998
2095
|
const completeWorkerRequest = (id) => pendingWorkerRequests.delete(id);
|
|
2096
|
+
const validKeyboardBinding = (value) => exactKeys(value, ["binding_id", "event", "code", "repeat", "alt_key", "ctrl_key", "meta_key", "shift_key", "target_key", "target_kind"]) &&
|
|
2097
|
+
validIdentifier(value.binding_id) && ["keydown", "keyup"].includes(value.event) &&
|
|
2098
|
+
typeof value.code === "string" && value.code.length > 0 && value.code.length <= 64 &&
|
|
2099
|
+
typeof value.repeat === "boolean" && typeof value.alt_key === "boolean" && typeof value.ctrl_key === "boolean" &&
|
|
2100
|
+
typeof value.meta_key === "boolean" && typeof value.shift_key === "boolean" &&
|
|
2101
|
+
(value.target_key === null || validResourceIdentifier(value.target_key)) &&
|
|
2102
|
+
["canvas", "editable", "control", "surface"].includes(value.target_kind);
|
|
2103
|
+
const validKeyboardBindings = (value) => {
|
|
2104
|
+
if (!Array.isArray(value) || value.length > maxKeyboardBindings || !value.every(validKeyboardBinding)) return false;
|
|
2105
|
+
const ids = new Set();
|
|
2106
|
+
const matchers = new Set();
|
|
2107
|
+
for (const binding of value) {
|
|
2108
|
+
if (ids.has(binding.binding_id)) return false;
|
|
2109
|
+
ids.add(binding.binding_id);
|
|
2110
|
+
const matcher = JSON.stringify([binding.event, binding.code, binding.repeat, binding.alt_key, binding.ctrl_key, binding.meta_key, binding.shift_key, binding.target_key, binding.target_kind]);
|
|
2111
|
+
if (matchers.has(matcher)) return false;
|
|
2112
|
+
matchers.add(matcher);
|
|
2113
|
+
}
|
|
2114
|
+
return true;
|
|
2115
|
+
};
|
|
1999
2116
|
const completeExecutionQueryRequest = (id) => {
|
|
2000
2117
|
const executionID = executionQueryRequests.get(id);
|
|
2001
2118
|
executionQueryRequests.delete(id);
|
|
@@ -2159,8 +2276,25 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
2159
2276
|
shift_key: Boolean(event.shiftKey),
|
|
2160
2277
|
});
|
|
2161
2278
|
};
|
|
2279
|
+
const handleWheel = (event) => {
|
|
2280
|
+
event.preventDefault();
|
|
2281
|
+
const rect = canvas.getBoundingClientRect();
|
|
2282
|
+
sendInput({
|
|
2283
|
+
type: "wheel",
|
|
2284
|
+
x: Math.min(32768, Math.max(-16384, event.clientX - rect.left)),
|
|
2285
|
+
y: Math.min(32768, Math.max(-16384, event.clientY - rect.top)),
|
|
2286
|
+
delta_x: Math.min(32768, Math.max(-32768, Number.isFinite(event.deltaX) ? event.deltaX : 0)),
|
|
2287
|
+
delta_y: Math.min(32768, Math.max(-32768, Number.isFinite(event.deltaY) ? event.deltaY : 0)),
|
|
2288
|
+
delta_mode: [0, 1, 2].includes(event.deltaMode) ? event.deltaMode : 0,
|
|
2289
|
+
alt_key: Boolean(event.altKey),
|
|
2290
|
+
ctrl_key: Boolean(event.ctrlKey),
|
|
2291
|
+
meta_key: Boolean(event.metaKey),
|
|
2292
|
+
shift_key: Boolean(event.shiftKey),
|
|
2293
|
+
});
|
|
2294
|
+
};
|
|
2162
2295
|
for (const type of ["pointerdown", "pointermove", "pointerup", "pointercancel"]) listen(type, handlePointer);
|
|
2163
2296
|
for (const type of ["keydown", "keyup"]) listen(type, handleKey);
|
|
2297
|
+
listen("wheel", handleWheel);
|
|
2164
2298
|
listen("focus", () => sendInput({ type: "focus" }));
|
|
2165
2299
|
listen("blur", () => sendInput({ type: "blur" }));
|
|
2166
2300
|
const observer = new ResizeObserver(() => {
|
|
@@ -2285,6 +2419,17 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
2285
2419
|
updateCanvasAccessibility(message.id, message.canvas_id, message.label, message.description);
|
|
2286
2420
|
return;
|
|
2287
2421
|
}
|
|
2422
|
+
if (exactKeys(message, ["type", "id", "bindings"]) && message.type === "redevplugin.ui.keyboard.bindings" && typeof message.id === "string") {
|
|
2423
|
+
if (!acceptWorkerRequest(message.id, "keyboard")) return rejectWorkerRequest(message.id, "duplicate, replayed, or excessive plugin request");
|
|
2424
|
+
if (!validKeyboardBindings(message.bindings)) {
|
|
2425
|
+
completeWorkerRequest(message.id);
|
|
2426
|
+
return rejectWorkerRequest(message.id, "plugin surface keyboard bindings are invalid");
|
|
2427
|
+
}
|
|
2428
|
+
keyboardBindings = message.bindings.map((binding) => ({ ...binding }));
|
|
2429
|
+
completeWorkerRequest(message.id);
|
|
2430
|
+
sendWorker({ type: "redevplugin.bridge.response", id: message.id, ok: true });
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2288
2433
|
if (exactKeys(message, ["type", "id", "asset_id"]) && message.type === "redevplugin.ui.asset.image.open" && typeof message.id === "string" && validResourceIdentifier(message.asset_id)) {
|
|
2289
2434
|
if (!acceptWorkerRequest(message.id, "asset")) return rejectWorkerRequest(message.id, "duplicate, replayed, or excessive plugin request");
|
|
2290
2435
|
const asset = assetByLogicalID.get(message.asset_id);
|
|
@@ -4221,6 +4366,35 @@ function publicActionEvent(value) {
|
|
|
4221
4366
|
form_data: value.form_data,
|
|
4222
4367
|
});
|
|
4223
4368
|
}
|
|
4369
|
+
function publicKeyboardInputMessage(value) {
|
|
4370
|
+
if (!isRecord(value) || !hasExactKeys(value, ["type", "event"]) || value.type !== "redevplugin.ui.keyboard.input" || !isRecord(value.event))
|
|
4371
|
+
return undefined;
|
|
4372
|
+
const event = value.event;
|
|
4373
|
+
if (!hasExactKeys(event, ["event", "code", "key", "repeat", "alt_key", "ctrl_key", "meta_key", "shift_key", "is_composing", "target_key", "target_kind", "default_prevented", "binding_id"]) ||
|
|
4374
|
+
(event.event !== "keydown" && event.event !== "keyup") || typeof event.code !== "string" || event.code.length > 64 ||
|
|
4375
|
+
typeof event.key !== "string" || event.key.length > 64 || typeof event.repeat !== "boolean" ||
|
|
4376
|
+
typeof event.alt_key !== "boolean" || typeof event.ctrl_key !== "boolean" || typeof event.meta_key !== "boolean" ||
|
|
4377
|
+
typeof event.shift_key !== "boolean" || typeof event.is_composing !== "boolean" ||
|
|
4378
|
+
(event.target_key !== null && !validUIIdentifier(event.target_key)) ||
|
|
4379
|
+
(event.target_kind !== "canvas" && event.target_kind !== "editable" && event.target_kind !== "control" && event.target_kind !== "surface") ||
|
|
4380
|
+
typeof event.default_prevented !== "boolean" || (event.binding_id !== null && !validActionID(event.binding_id)))
|
|
4381
|
+
return undefined;
|
|
4382
|
+
return {
|
|
4383
|
+
event: event.event,
|
|
4384
|
+
code: event.code,
|
|
4385
|
+
key: event.key,
|
|
4386
|
+
repeat: event.repeat,
|
|
4387
|
+
altKey: event.alt_key,
|
|
4388
|
+
ctrlKey: event.ctrl_key,
|
|
4389
|
+
metaKey: event.meta_key,
|
|
4390
|
+
shiftKey: event.shift_key,
|
|
4391
|
+
isComposing: event.is_composing,
|
|
4392
|
+
targetKey: event.target_key,
|
|
4393
|
+
targetKind: event.target_kind,
|
|
4394
|
+
defaultPrevented: event.default_prevented,
|
|
4395
|
+
bindingId: event.binding_id,
|
|
4396
|
+
};
|
|
4397
|
+
}
|
|
4224
4398
|
function isCanvasReadyCandidate(value) {
|
|
4225
4399
|
return isRecord(value) && value.type === "redevplugin.ui.canvas.ready" && typeof value.id === "string" && typeof value.canvas_id === "string";
|
|
4226
4400
|
}
|
|
@@ -4298,6 +4472,28 @@ function publicCanvasInputMessage(value) {
|
|
|
4298
4472
|
},
|
|
4299
4473
|
};
|
|
4300
4474
|
}
|
|
4475
|
+
if (hasExactKeys(event, ["type", "x", "y", "delta_x", "delta_y", "delta_mode", "alt_key", "ctrl_key", "meta_key", "shift_key"]) &&
|
|
4476
|
+
event.type === "wheel" && validCanvasCoordinate(event.x) && validCanvasCoordinate(event.y) &&
|
|
4477
|
+
validCanvasWheelDelta(event.delta_x) && validCanvasWheelDelta(event.delta_y) &&
|
|
4478
|
+
(event.delta_mode === 0 || event.delta_mode === 1 || event.delta_mode === 2) &&
|
|
4479
|
+
typeof event.alt_key === "boolean" && typeof event.ctrl_key === "boolean" &&
|
|
4480
|
+
typeof event.meta_key === "boolean" && typeof event.shift_key === "boolean") {
|
|
4481
|
+
return {
|
|
4482
|
+
canvasId: value.canvas_id,
|
|
4483
|
+
event: {
|
|
4484
|
+
type: "wheel",
|
|
4485
|
+
x: event.x,
|
|
4486
|
+
y: event.y,
|
|
4487
|
+
deltaX: event.delta_x,
|
|
4488
|
+
deltaY: event.delta_y,
|
|
4489
|
+
deltaMode: event.delta_mode,
|
|
4490
|
+
altKey: event.alt_key,
|
|
4491
|
+
ctrlKey: event.ctrl_key,
|
|
4492
|
+
metaKey: event.meta_key,
|
|
4493
|
+
shiftKey: event.shift_key,
|
|
4494
|
+
},
|
|
4495
|
+
};
|
|
4496
|
+
}
|
|
4301
4497
|
return undefined;
|
|
4302
4498
|
}
|
|
4303
4499
|
function isOffscreenCanvasLike(value) {
|
|
@@ -4317,6 +4513,9 @@ function validDevicePixelRatio(value) {
|
|
|
4317
4513
|
function validCanvasCoordinate(value) {
|
|
4318
4514
|
return typeof value === "number" && Number.isFinite(value) && value >= -16384 && value <= 32768;
|
|
4319
4515
|
}
|
|
4516
|
+
function validCanvasWheelDelta(value) {
|
|
4517
|
+
return typeof value === "number" && Number.isFinite(value) && value >= -32768 && value <= 32768;
|
|
4518
|
+
}
|
|
4320
4519
|
function isMessagePortLike(value) {
|
|
4321
4520
|
return isRecord(value) &&
|
|
4322
4521
|
typeof value.postMessage === "function" &&
|
|
@@ -4354,7 +4553,7 @@ const pluginMethodPattern = new RegExp("^[-A-Za-z0-9._:]{1,256}$");
|
|
|
4354
4553
|
const pluginActionPattern = new RegExp("^[-A-Za-z0-9._:]{1,128}$");
|
|
4355
4554
|
const pluginUIIdentifierPattern = new RegExp("^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$");
|
|
4356
4555
|
const opaqueHandlePattern = new RegExp("^[-A-Za-z0-9_]{8,160}$");
|
|
4357
|
-
const bridgeRequestIDPattern = /^(rpc|execution|render|canvas|asset)_([1-9][0-9]{0,15})$/;
|
|
4556
|
+
const bridgeRequestIDPattern = /^(rpc|execution|render|canvas|asset|keyboard)_([1-9][0-9]{0,15})$/;
|
|
4358
4557
|
function validBridgeRequestID(value, expectedKind) {
|
|
4359
4558
|
if (typeof value !== "string")
|
|
4360
4559
|
return false;
|
|
@@ -4363,6 +4562,34 @@ function validBridgeRequestID(value, expectedKind) {
|
|
|
4363
4562
|
return false;
|
|
4364
4563
|
return Number.isSafeInteger(Number(match[2]));
|
|
4365
4564
|
}
|
|
4565
|
+
function validKeyboardBinding(value) {
|
|
4566
|
+
return isRecord(value) && hasExactKeys(value, ["id", "event", "code", "repeat", "altKey", "ctrlKey", "metaKey", "shiftKey", "targetKey", "targetKind"]) &&
|
|
4567
|
+
validActionID(value.id) && (value.event === "keydown" || value.event === "keyup") &&
|
|
4568
|
+
typeof value.code === "string" && value.code.length > 0 && value.code.length <= 64 &&
|
|
4569
|
+
typeof value.repeat === "boolean" && typeof value.altKey === "boolean" && typeof value.ctrlKey === "boolean" &&
|
|
4570
|
+
typeof value.metaKey === "boolean" && typeof value.shiftKey === "boolean" &&
|
|
4571
|
+
(value.targetKey === null || validUIIdentifier(value.targetKey)) &&
|
|
4572
|
+
(value.targetKind === "canvas" || value.targetKind === "editable" || value.targetKind === "control" || value.targetKind === "surface");
|
|
4573
|
+
}
|
|
4574
|
+
function validKeyboardBindings(value) {
|
|
4575
|
+
if (!Array.isArray(value) || value.length > 128 || !value.every(validKeyboardBinding))
|
|
4576
|
+
return false;
|
|
4577
|
+
const ids = new Set();
|
|
4578
|
+
const matchers = new Set();
|
|
4579
|
+
for (const binding of value) {
|
|
4580
|
+
if (ids.has(binding.id))
|
|
4581
|
+
return false;
|
|
4582
|
+
ids.add(binding.id);
|
|
4583
|
+
const matcher = JSON.stringify([
|
|
4584
|
+
binding.event, binding.code, binding.repeat, binding.altKey, binding.ctrlKey,
|
|
4585
|
+
binding.metaKey, binding.shiftKey, binding.targetKey, binding.targetKind,
|
|
4586
|
+
]);
|
|
4587
|
+
if (matchers.has(matcher))
|
|
4588
|
+
return false;
|
|
4589
|
+
matchers.add(matcher);
|
|
4590
|
+
}
|
|
4591
|
+
return true;
|
|
4592
|
+
}
|
|
4366
4593
|
function validMethod(value) {
|
|
4367
4594
|
return typeof value === "string" && pluginMethodPattern.test(value);
|
|
4368
4595
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@floegence/redevplugin-ui",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.23",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"dist"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@floegence/redevplugin-contracts": "3.0.
|
|
43
|
+
"@floegence/redevplugin-contracts": "3.0.23"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|