@floegence/redevplugin-ui 3.0.22 → 3.0.24
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 +30 -0
- package/dist/surface.js +185 -5
- 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, PluginCanvasWheelEvent, 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
|
@@ -149,6 +149,34 @@ export type PluginCanvasWheelEvent = {
|
|
|
149
149
|
shiftKey: boolean;
|
|
150
150
|
};
|
|
151
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
|
+
};
|
|
152
180
|
export type PluginMethodResult<T = unknown> = {
|
|
153
181
|
data: T;
|
|
154
182
|
execution_id?: string;
|
|
@@ -305,6 +333,8 @@ export declare class PluginBridgeClient {
|
|
|
305
333
|
render(tree: PluginUIVNode): Promise<void>;
|
|
306
334
|
openCanvas(canvasId: string): Promise<PluginCanvasSurface>;
|
|
307
335
|
updateCanvasAccessibility(canvasId: string, state: PluginCanvasAccessibilityState): Promise<void>;
|
|
336
|
+
setKeyboardBindings(bindings: readonly PluginSurfaceKeyboardBinding[]): Promise<void>;
|
|
337
|
+
onKeyboardInput(handler: (event: PluginSurfaceKeyboardEvent) => void): () => void;
|
|
308
338
|
loadImageAsset(assetId: string): Promise<ImageBitmap>;
|
|
309
339
|
onAction(action: string, handler: (event: PluginUIActionEvent) => void): () => void;
|
|
310
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
|
};
|
|
@@ -977,6 +1067,8 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
977
1067
|
workerHeartbeatTimeout = undefined;
|
|
978
1068
|
workerHeartbeatPendingID = undefined;
|
|
979
1069
|
workerReady = false;
|
|
1070
|
+
keyboardBindings = [];
|
|
1071
|
+
for (const eventType of ["keydown", "keyup"]) document.removeEventListener(eventType, handleSurfaceKeyboard, true);
|
|
980
1072
|
for (const runtime of canvasRuntimes.values()) runtime.dispose();
|
|
981
1073
|
canvasRuntimes.clear();
|
|
982
1074
|
if (workerControlPort) {
|
|
@@ -1526,8 +1618,8 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
1526
1618
|
const root = built.node;
|
|
1527
1619
|
if (!(root instanceof Element)) throw new Error("plugin UI root must be an element");
|
|
1528
1620
|
validateCanvasIdentifiers(root);
|
|
1529
|
-
const renderState = captureRenderState();
|
|
1530
1621
|
const committed = await commitOnRenderOpportunity(() => {
|
|
1622
|
+
const renderState = captureRenderState();
|
|
1531
1623
|
document.body.replaceChildren(root);
|
|
1532
1624
|
restoreRenderState(renderState);
|
|
1533
1625
|
});
|
|
@@ -1541,8 +1633,8 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
1541
1633
|
const patchWorkerTree = async (baseRevision, revision, operations) => {
|
|
1542
1634
|
if (uiRevision < 1 || baseRevision !== uiRevision || revision !== baseRevision + 1) throw new Error("plugin UI patch revision is invalid");
|
|
1543
1635
|
const validated = validatePatch(operations);
|
|
1544
|
-
const renderState = captureRenderState();
|
|
1545
1636
|
const committed = await commitOnRenderOpportunity(() => {
|
|
1637
|
+
const renderState = captureRenderState();
|
|
1546
1638
|
for (const operation of validated.plan) applyPatchOperation(operation);
|
|
1547
1639
|
validated.graph.commit();
|
|
1548
1640
|
restoreRenderState(renderState);
|
|
@@ -1988,7 +2080,7 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
1988
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));
|
|
1989
2081
|
const requestID = (value, expectedKind) => {
|
|
1990
2082
|
if (typeof value !== "string") return undefined;
|
|
1991
|
-
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);
|
|
1992
2084
|
if (!match || match[1] !== expectedKind) return undefined;
|
|
1993
2085
|
const sequence = Number(match[2]);
|
|
1994
2086
|
return Number.isSafeInteger(sequence) ? { kind: match[1], sequence } : undefined;
|
|
@@ -2001,6 +2093,26 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
2001
2093
|
return true;
|
|
2002
2094
|
};
|
|
2003
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
|
+
};
|
|
2004
2116
|
const completeExecutionQueryRequest = (id) => {
|
|
2005
2117
|
const executionID = executionQueryRequests.get(id);
|
|
2006
2118
|
executionQueryRequests.delete(id);
|
|
@@ -2307,6 +2419,17 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
2307
2419
|
updateCanvasAccessibility(message.id, message.canvas_id, message.label, message.description);
|
|
2308
2420
|
return;
|
|
2309
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
|
+
}
|
|
2310
2433
|
if (exactKeys(message, ["type", "id", "asset_id"]) && message.type === "redevplugin.ui.asset.image.open" && typeof message.id === "string" && validResourceIdentifier(message.asset_id)) {
|
|
2311
2434
|
if (!acceptWorkerRequest(message.id, "asset")) return rejectWorkerRequest(message.id, "duplicate, replayed, or excessive plugin request");
|
|
2312
2435
|
const asset = assetByLogicalID.get(message.asset_id);
|
|
@@ -4243,6 +4366,35 @@ function publicActionEvent(value) {
|
|
|
4243
4366
|
form_data: value.form_data,
|
|
4244
4367
|
});
|
|
4245
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
|
+
}
|
|
4246
4398
|
function isCanvasReadyCandidate(value) {
|
|
4247
4399
|
return isRecord(value) && value.type === "redevplugin.ui.canvas.ready" && typeof value.id === "string" && typeof value.canvas_id === "string";
|
|
4248
4400
|
}
|
|
@@ -4401,7 +4553,7 @@ const pluginMethodPattern = new RegExp("^[-A-Za-z0-9._:]{1,256}$");
|
|
|
4401
4553
|
const pluginActionPattern = new RegExp("^[-A-Za-z0-9._:]{1,128}$");
|
|
4402
4554
|
const pluginUIIdentifierPattern = new RegExp("^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$");
|
|
4403
4555
|
const opaqueHandlePattern = new RegExp("^[-A-Za-z0-9_]{8,160}$");
|
|
4404
|
-
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})$/;
|
|
4405
4557
|
function validBridgeRequestID(value, expectedKind) {
|
|
4406
4558
|
if (typeof value !== "string")
|
|
4407
4559
|
return false;
|
|
@@ -4410,6 +4562,34 @@ function validBridgeRequestID(value, expectedKind) {
|
|
|
4410
4562
|
return false;
|
|
4411
4563
|
return Number.isSafeInteger(Number(match[2]));
|
|
4412
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
|
+
}
|
|
4413
4593
|
function validMethod(value) {
|
|
4414
4594
|
return typeof value === "string" && pluginMethodPattern.test(value);
|
|
4415
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.24",
|
|
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.24"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|