@omnipad/core 0.1.1-alpha.0
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.d.mts +1993 -0
- package/dist/index.d.ts +1993 -0
- package/dist/index.js +1176 -0
- package/dist/index.mjs +1102 -0
- package/package.json +33 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1102 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
5
|
+
// src/types/keys.ts
|
|
6
|
+
var STANDARD_KEYS = {
|
|
7
|
+
// --- System Controls (8-27) ---
|
|
8
|
+
Backspace: { key: "Backspace", code: "Backspace", keyCode: 8 },
|
|
9
|
+
Tab: { key: "Tab", code: "Tab", keyCode: 9 },
|
|
10
|
+
Enter: { key: "Enter", code: "Enter", keyCode: 13 },
|
|
11
|
+
ShiftLeft: { key: "Shift", code: "ShiftLeft", keyCode: 16 },
|
|
12
|
+
ControlLeft: { key: "Control", code: "ControlLeft", keyCode: 17 },
|
|
13
|
+
AltLeft: { key: "Alt", code: "AltLeft", keyCode: 18 },
|
|
14
|
+
Pause: { key: "Pause", code: "Pause", keyCode: 19 },
|
|
15
|
+
CapsLock: { key: "CapsLock", code: "CapsLock", keyCode: 20 },
|
|
16
|
+
Escape: { key: "Escape", code: "Escape", keyCode: 27 },
|
|
17
|
+
// --- Navigation & Editing (32-46) ---
|
|
18
|
+
Space: { key: " ", code: "Space", keyCode: 32 },
|
|
19
|
+
PageUp: { key: "PageUp", code: "PageUp", keyCode: 33 },
|
|
20
|
+
PageDown: { key: "PageDown", code: "PageDown", keyCode: 34 },
|
|
21
|
+
End: { key: "End", code: "End", keyCode: 35 },
|
|
22
|
+
Home: { key: "Home", code: "Home", keyCode: 36 },
|
|
23
|
+
ArrowLeft: { key: "ArrowLeft", code: "ArrowLeft", keyCode: 37 },
|
|
24
|
+
ArrowUp: { key: "ArrowUp", code: "ArrowUp", keyCode: 38 },
|
|
25
|
+
ArrowRight: { key: "ArrowRight", code: "ArrowRight", keyCode: 39 },
|
|
26
|
+
ArrowDown: { key: "ArrowDown", code: "ArrowDown", keyCode: 40 },
|
|
27
|
+
PrintScreen: { key: "PrintScreen", code: "PrintScreen", keyCode: 44 },
|
|
28
|
+
Insert: { key: "Insert", code: "Insert", keyCode: 45 },
|
|
29
|
+
Delete: { key: "Delete", code: "Delete", keyCode: 46 },
|
|
30
|
+
// --- Digit Keys 0-9 (48-57) ---
|
|
31
|
+
Digit0: { key: "0", code: "Digit0", keyCode: 48 },
|
|
32
|
+
Digit1: { key: "1", code: "Digit1", keyCode: 49 },
|
|
33
|
+
Digit2: { key: "2", code: "Digit2", keyCode: 50 },
|
|
34
|
+
Digit3: { key: "3", code: "Digit3", keyCode: 51 },
|
|
35
|
+
Digit4: { key: "4", code: "Digit4", keyCode: 52 },
|
|
36
|
+
Digit5: { key: "5", code: "Digit5", keyCode: 53 },
|
|
37
|
+
Digit6: { key: "6", code: "Digit6", keyCode: 54 },
|
|
38
|
+
Digit7: { key: "7", code: "Digit7", keyCode: 55 },
|
|
39
|
+
Digit8: { key: "8", code: "Digit8", keyCode: 56 },
|
|
40
|
+
Digit9: { key: "9", code: "Digit9", keyCode: 57 },
|
|
41
|
+
// --- Alpha Keys A-Z (65-90) ---
|
|
42
|
+
KeyA: { key: "a", code: "KeyA", keyCode: 65 },
|
|
43
|
+
KeyB: { key: "b", code: "KeyB", keyCode: 66 },
|
|
44
|
+
KeyC: { key: "c", code: "KeyC", keyCode: 67 },
|
|
45
|
+
KeyD: { key: "d", code: "KeyD", keyCode: 68 },
|
|
46
|
+
KeyE: { key: "e", code: "KeyE", keyCode: 69 },
|
|
47
|
+
KeyF: { key: "f", code: "KeyF", keyCode: 70 },
|
|
48
|
+
KeyG: { key: "g", code: "KeyG", keyCode: 71 },
|
|
49
|
+
KeyH: { key: "h", code: "KeyH", keyCode: 72 },
|
|
50
|
+
KeyI: { key: "i", code: "KeyI", keyCode: 73 },
|
|
51
|
+
KeyJ: { key: "j", code: "KeyJ", keyCode: 74 },
|
|
52
|
+
KeyK: { key: "k", code: "KeyK", keyCode: 75 },
|
|
53
|
+
KeyL: { key: "l", code: "KeyL", keyCode: 76 },
|
|
54
|
+
KeyM: { key: "m", code: "KeyM", keyCode: 77 },
|
|
55
|
+
KeyN: { key: "n", code: "KeyN", keyCode: 78 },
|
|
56
|
+
KeyO: { key: "o", code: "KeyO", keyCode: 79 },
|
|
57
|
+
KeyP: { key: "p", code: "KeyP", keyCode: 80 },
|
|
58
|
+
KeyQ: { key: "q", code: "KeyQ", keyCode: 81 },
|
|
59
|
+
KeyR: { key: "r", code: "KeyR", keyCode: 82 },
|
|
60
|
+
KeyS: { key: "s", code: "KeyS", keyCode: 83 },
|
|
61
|
+
KeyT: { key: "t", code: "KeyT", keyCode: 84 },
|
|
62
|
+
KeyU: { key: "u", code: "KeyU", keyCode: 85 },
|
|
63
|
+
KeyV: { key: "v", code: "KeyV", keyCode: 86 },
|
|
64
|
+
KeyW: { key: "w", code: "KeyW", keyCode: 87 },
|
|
65
|
+
KeyX: { key: "x", code: "KeyX", keyCode: 88 },
|
|
66
|
+
KeyY: { key: "y", code: "KeyY", keyCode: 89 },
|
|
67
|
+
KeyZ: { key: "z", code: "KeyZ", keyCode: 90 },
|
|
68
|
+
// --- Meta & Menu (91-93) ---
|
|
69
|
+
MetaLeft: { key: "Meta", code: "MetaLeft", keyCode: 91 },
|
|
70
|
+
ContextMenu: { key: "ContextMenu", code: "ContextMenu", keyCode: 93 },
|
|
71
|
+
// --- Numpad Digits (96-105) ---
|
|
72
|
+
Numpad0: { key: "0", code: "Numpad0", keyCode: 96 },
|
|
73
|
+
Numpad1: { key: "1", code: "Numpad1", keyCode: 97 },
|
|
74
|
+
Numpad2: { key: "2", code: "Numpad2", keyCode: 98 },
|
|
75
|
+
Numpad3: { key: "3", code: "Numpad3", keyCode: 99 },
|
|
76
|
+
Numpad4: { key: "4", code: "Numpad4", keyCode: 100 },
|
|
77
|
+
Numpad5: { key: "5", code: "Numpad5", keyCode: 101 },
|
|
78
|
+
Numpad6: { key: "6", code: "Numpad6", keyCode: 102 },
|
|
79
|
+
Numpad7: { key: "7", code: "Numpad7", keyCode: 103 },
|
|
80
|
+
Numpad8: { key: "8", code: "Numpad8", keyCode: 104 },
|
|
81
|
+
Numpad9: { key: "9", code: "Numpad9", keyCode: 105 },
|
|
82
|
+
// --- Numpad Symbols (106-111) ---
|
|
83
|
+
NumpadMultiply: { key: "*", code: "NumpadMultiply", keyCode: 106 },
|
|
84
|
+
NumpadAdd: { key: "+", code: "NumpadAdd", keyCode: 107 },
|
|
85
|
+
NumpadSubtract: { key: "-", code: "NumpadSubtract", keyCode: 109 },
|
|
86
|
+
NumpadDecimal: { key: ".", code: "NumpadDecimal", keyCode: 110 },
|
|
87
|
+
NumpadDivide: { key: "/", code: "NumpadDivide", keyCode: 111 },
|
|
88
|
+
// --- Function Keys (112-123) ---
|
|
89
|
+
F1: { key: "F1", code: "F1", keyCode: 112 },
|
|
90
|
+
F2: { key: "F2", code: "F2", keyCode: 113 },
|
|
91
|
+
F3: { key: "F3", code: "F3", keyCode: 114 },
|
|
92
|
+
F4: { key: "F4", code: "F4", keyCode: 115 },
|
|
93
|
+
F5: { key: "F5", code: "F5", keyCode: 116 },
|
|
94
|
+
F6: { key: "F6", code: "F6", keyCode: 117 },
|
|
95
|
+
F7: { key: "F7", code: "F7", keyCode: 118 },
|
|
96
|
+
F8: { key: "F8", code: "F8", keyCode: 119 },
|
|
97
|
+
F9: { key: "F9", code: "F9", keyCode: 120 },
|
|
98
|
+
F10: { key: "F10", code: "F10", keyCode: 121 },
|
|
99
|
+
F11: { key: "F11", code: "F11", keyCode: 122 },
|
|
100
|
+
F12: { key: "F12", code: "F12", keyCode: 123 },
|
|
101
|
+
// --- State Locks (144-145) ---
|
|
102
|
+
NumLock: { key: "NumLock", code: "NumLock", keyCode: 144 },
|
|
103
|
+
ScrollLock: { key: "ScrollLock", code: "ScrollLock", keyCode: 145 },
|
|
104
|
+
// --- Punctuation (186-222) ---
|
|
105
|
+
Semicolon: { key: ";", code: "Semicolon", keyCode: 186 },
|
|
106
|
+
Equal: { key: "=", code: "Equal", keyCode: 187 },
|
|
107
|
+
Comma: { key: ",", code: "Comma", keyCode: 188 },
|
|
108
|
+
Minus: { key: "-", code: "Minus", keyCode: 189 },
|
|
109
|
+
Period: { key: ".", code: "Period", keyCode: 190 },
|
|
110
|
+
Slash: { key: "/", code: "Slash", keyCode: 191 },
|
|
111
|
+
Backquote: { key: "`", code: "Backquote", keyCode: 192 },
|
|
112
|
+
BracketLeft: { key: "[", code: "BracketLeft", keyCode: 219 },
|
|
113
|
+
Backslash: { key: "\\", code: "Backslash", keyCode: 220 },
|
|
114
|
+
BracketRight: { key: "]", code: "BracketRight", keyCode: 221 },
|
|
115
|
+
Quote: { key: "'", code: "Quote", keyCode: 222 }
|
|
116
|
+
};
|
|
117
|
+
var KEYS = STANDARD_KEYS;
|
|
118
|
+
|
|
119
|
+
// src/types/index.ts
|
|
120
|
+
var TYPES = {
|
|
121
|
+
// --- Zones ---
|
|
122
|
+
/** Area responsible for capturing touches and spawning dynamic widgets */
|
|
123
|
+
INPUT_ZONE: "input-zone",
|
|
124
|
+
/** Area responsible for receiving signals and simulating DOM events */
|
|
125
|
+
TARGET_ZONE: "target-zone",
|
|
126
|
+
// --- Widgets ---
|
|
127
|
+
/** Simulates a physical keyboard key press */
|
|
128
|
+
KEYBOARD_BUTTON: "keyboard-button",
|
|
129
|
+
/** Simulates a mouse button click/hold */
|
|
130
|
+
MOUSE_BUTTON: "mouse-button",
|
|
131
|
+
/** A joystick that outputs 360-degree or locked direction vectors */
|
|
132
|
+
ANALOG_STICK: "analog-stick",
|
|
133
|
+
/** Classic 4/8-way directional pad */
|
|
134
|
+
D_PAD: "d-pad",
|
|
135
|
+
/** Trackpad-style relative movement area */
|
|
136
|
+
TRACKPAD: "trackpad",
|
|
137
|
+
// --- Virtual Helpers ---
|
|
138
|
+
/** Logic for the on-screen visual cursor */
|
|
139
|
+
VIRTUAL_CURSOR: "virtual-cursor",
|
|
140
|
+
/** The top-level managed container */
|
|
141
|
+
ROOT_LAYER: "root-layer"
|
|
142
|
+
};
|
|
143
|
+
var ACTION_TYPES = {
|
|
144
|
+
KEYDOWN: "keydown",
|
|
145
|
+
KEYUP: "keyup",
|
|
146
|
+
POINTER: "pointer",
|
|
147
|
+
POINTERMOVE: "pointermove",
|
|
148
|
+
POINTERDOWN: "pointerdown",
|
|
149
|
+
POINTERUP: "pointerup",
|
|
150
|
+
MOUSE: "mouse",
|
|
151
|
+
MOUSEMOVE: "mousemove",
|
|
152
|
+
MOUSEDOWN: "mousedown",
|
|
153
|
+
MOUSEUP: "mouseup",
|
|
154
|
+
CLICK: "click"
|
|
155
|
+
};
|
|
156
|
+
var CONTEXT = {
|
|
157
|
+
/** The key used to propagate Parent IDs through the component tree */
|
|
158
|
+
PARENT_ID_KEY: "omnipad-parent-id-link"
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// src/utils/math.ts
|
|
162
|
+
var subVec = (v1, v2) => {
|
|
163
|
+
return { x: v1.x - v2.x, y: v1.y - v2.y };
|
|
164
|
+
};
|
|
165
|
+
var addVec = (v1, v2) => {
|
|
166
|
+
return { x: v1.x + v2.x, y: v1.y + v2.y };
|
|
167
|
+
};
|
|
168
|
+
var scaleVec = (v, s) => {
|
|
169
|
+
return { x: v.x * s, y: v.y * s };
|
|
170
|
+
};
|
|
171
|
+
var clamp = (val, min, max) => {
|
|
172
|
+
return Math.max(min, Math.min(max, val));
|
|
173
|
+
};
|
|
174
|
+
var lerp = (start, end, t) => {
|
|
175
|
+
return start + (end - start) * t;
|
|
176
|
+
};
|
|
177
|
+
var roundTo = (val, precision = 2) => {
|
|
178
|
+
const m = Math.pow(10, precision);
|
|
179
|
+
return Math.round(val * m) / m;
|
|
180
|
+
};
|
|
181
|
+
var getDistance = (p1, p2) => {
|
|
182
|
+
return Math.hypot(p2.x - p1.x, p2.y - p1.y);
|
|
183
|
+
};
|
|
184
|
+
var getAngle = (p1, p2) => {
|
|
185
|
+
return Math.atan2(p2.y - p1.y, p2.x - p1.x);
|
|
186
|
+
};
|
|
187
|
+
var radToDeg = (rad) => {
|
|
188
|
+
return rad * 180 / Math.PI;
|
|
189
|
+
};
|
|
190
|
+
var degToRad = (deg) => {
|
|
191
|
+
return deg * Math.PI / 180;
|
|
192
|
+
};
|
|
193
|
+
var clampVector = (origin, target, radius) => {
|
|
194
|
+
const dx = target.x - origin.x;
|
|
195
|
+
const dy = target.y - origin.y;
|
|
196
|
+
const dist = Math.hypot(dx, dy);
|
|
197
|
+
if (dist <= radius) return target;
|
|
198
|
+
const angle = Math.atan2(dy, dx);
|
|
199
|
+
return {
|
|
200
|
+
x: origin.x + Math.cos(angle) * radius,
|
|
201
|
+
y: origin.y + Math.sin(angle) * radius
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
var lockTo8Directions = (rad) => {
|
|
205
|
+
const step = Math.PI / 4;
|
|
206
|
+
return Math.round(rad / step) * step;
|
|
207
|
+
};
|
|
208
|
+
var lockTo4Directions = (rad) => {
|
|
209
|
+
const step = Math.PI / 2;
|
|
210
|
+
return Math.round(rad / step) * step;
|
|
211
|
+
};
|
|
212
|
+
var percentToPx = (percent, totalPx) => {
|
|
213
|
+
return roundTo(percent * totalPx / 100);
|
|
214
|
+
};
|
|
215
|
+
var pxToPercent = (px, totalPx) => {
|
|
216
|
+
if (totalPx === 0) return 0;
|
|
217
|
+
return roundTo(px * 100 / totalPx);
|
|
218
|
+
};
|
|
219
|
+
var normalizeVec = (v) => {
|
|
220
|
+
const magnitude = Math.hypot(v.x, v.y);
|
|
221
|
+
return magnitude === 0 ? { x: 0, y: 0 } : { x: v.x / magnitude, y: v.y / magnitude };
|
|
222
|
+
};
|
|
223
|
+
var radToVec = (rad) => ({
|
|
224
|
+
x: Math.cos(rad),
|
|
225
|
+
y: Math.sin(rad)
|
|
226
|
+
});
|
|
227
|
+
var remap = (val, inMin, inMax, outMin, outMax) => {
|
|
228
|
+
const t = (val - inMin) / (inMax - inMin);
|
|
229
|
+
return lerp(outMin, outMax, clamp(t, 0, 1));
|
|
230
|
+
};
|
|
231
|
+
var isVec2Equal = (v1, v2, epsilon = 1e-4) => {
|
|
232
|
+
return Math.abs(v1.x - v2.x) < epsilon && Math.abs(v1.y - v2.y) < epsilon;
|
|
233
|
+
};
|
|
234
|
+
var getDeadzoneScalar = (magnitude, threshold, max) => {
|
|
235
|
+
if (magnitude < threshold) return 0;
|
|
236
|
+
const scalar = (magnitude - threshold) / (max - threshold);
|
|
237
|
+
return clamp(scalar, 0, 1);
|
|
238
|
+
};
|
|
239
|
+
var applyRadialDeadzone = (v, radius, deadzonePercent) => {
|
|
240
|
+
const magnitude = Math.hypot(v.x, v.y);
|
|
241
|
+
const threshold = radius * deadzonePercent;
|
|
242
|
+
const scalar = getDeadzoneScalar(magnitude, threshold, radius);
|
|
243
|
+
if (scalar === 0) return { x: 0, y: 0 };
|
|
244
|
+
const direction = { x: v.x / magnitude, y: v.y / magnitude };
|
|
245
|
+
return scaleVec(direction, scalar);
|
|
246
|
+
};
|
|
247
|
+
var applyAxialDeadzone = (v, threshold, max) => {
|
|
248
|
+
return {
|
|
249
|
+
x: getDeadzoneScalar(Math.abs(v.x), threshold, max) * Math.sign(v.x),
|
|
250
|
+
y: getDeadzoneScalar(Math.abs(v.y), threshold, max) * Math.sign(v.y)
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// src/utils/dom.ts
|
|
255
|
+
var getDeepElement = (x, y) => {
|
|
256
|
+
let el = document.elementFromPoint(x, y);
|
|
257
|
+
while (el && el.shadowRoot) {
|
|
258
|
+
const nested = el.shadowRoot.elementFromPoint(x, y);
|
|
259
|
+
if (!nested || nested === el) break;
|
|
260
|
+
el = nested;
|
|
261
|
+
}
|
|
262
|
+
return el;
|
|
263
|
+
};
|
|
264
|
+
var getDeepActiveElement = () => {
|
|
265
|
+
let el = document.activeElement;
|
|
266
|
+
while (el && el.shadowRoot && el.shadowRoot.activeElement) {
|
|
267
|
+
el = el.shadowRoot.activeElement;
|
|
268
|
+
}
|
|
269
|
+
return el;
|
|
270
|
+
};
|
|
271
|
+
var focusElement = (el) => {
|
|
272
|
+
if (getDeepActiveElement() === el) return;
|
|
273
|
+
if (!el.hasAttribute("tabindex")) {
|
|
274
|
+
el.setAttribute("tabindex", "-1");
|
|
275
|
+
}
|
|
276
|
+
el.focus();
|
|
277
|
+
};
|
|
278
|
+
var dispatchKeyboardEvent = (type, payload) => {
|
|
279
|
+
const ev = new KeyboardEvent(type, {
|
|
280
|
+
...payload,
|
|
281
|
+
which: payload.keyCode,
|
|
282
|
+
// Support for legacy Flash engines
|
|
283
|
+
bubbles: true,
|
|
284
|
+
cancelable: true,
|
|
285
|
+
view: window
|
|
286
|
+
});
|
|
287
|
+
window.dispatchEvent(ev);
|
|
288
|
+
};
|
|
289
|
+
var dispatchPointerEventAtPos = (type, x, y, opts = {}) => {
|
|
290
|
+
const target = getDeepElement(x, y);
|
|
291
|
+
if (!target) return;
|
|
292
|
+
const commonProps = {
|
|
293
|
+
bubbles: true,
|
|
294
|
+
cancelable: true,
|
|
295
|
+
composed: true,
|
|
296
|
+
// Crucial for piercing Shadow DOM boundaries
|
|
297
|
+
clientX: x,
|
|
298
|
+
clientY: y,
|
|
299
|
+
view: window,
|
|
300
|
+
...opts
|
|
301
|
+
};
|
|
302
|
+
if (type.startsWith("pointer")) {
|
|
303
|
+
target.dispatchEvent(
|
|
304
|
+
new PointerEvent(type, {
|
|
305
|
+
isPrimary: true,
|
|
306
|
+
pointerId: 1,
|
|
307
|
+
pointerType: "mouse",
|
|
308
|
+
// Emulate mouse behavior for Flash MouseOver/Down logic
|
|
309
|
+
...commonProps
|
|
310
|
+
})
|
|
311
|
+
);
|
|
312
|
+
const mouseType = type.replace("pointer", "mouse");
|
|
313
|
+
target.dispatchEvent(new MouseEvent(mouseType, commonProps));
|
|
314
|
+
} else {
|
|
315
|
+
target.dispatchEvent(new MouseEvent(type, commonProps));
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
var getAnchorPosition = (rect, anchor) => {
|
|
319
|
+
const { left: l, top: t, width: w, height: h } = rect;
|
|
320
|
+
const map = {
|
|
321
|
+
"top-left": { x: l, y: t },
|
|
322
|
+
"top-center": { x: l + w / 2, y: t },
|
|
323
|
+
"top-right": { x: l + w, y: t },
|
|
324
|
+
"center-left": { x: l, y: t + h / 2 },
|
|
325
|
+
center: { x: l + w / 2, y: t + h / 2 },
|
|
326
|
+
"center-right": { x: l + w, y: t + h / 2 },
|
|
327
|
+
"bottom-left": { x: l, y: t + h },
|
|
328
|
+
"bottom-center": { x: l + w / 2, y: t + h },
|
|
329
|
+
"bottom-right": { x: l + w, y: t + h }
|
|
330
|
+
};
|
|
331
|
+
return map[anchor] || map["center"];
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// src/utils/id.ts
|
|
335
|
+
var generateUID = (prefix = "omnipad") => {
|
|
336
|
+
const stamp = Date.now().toString(36);
|
|
337
|
+
const random = Math.random().toString(36).substring(2, 6);
|
|
338
|
+
return `${prefix}-${stamp}-${random}`;
|
|
339
|
+
};
|
|
340
|
+
function isGlobalID(id) {
|
|
341
|
+
return id.startsWith("$");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/utils/layout.ts
|
|
345
|
+
var resolveLayoutStyle = (layout) => {
|
|
346
|
+
if (!layout) return {};
|
|
347
|
+
const style = {};
|
|
348
|
+
style.position = "absolute";
|
|
349
|
+
const fields = ["left", "top", "right", "bottom", "width", "height"];
|
|
350
|
+
fields.forEach((field) => {
|
|
351
|
+
const val = layout[field];
|
|
352
|
+
if (val !== void 0) {
|
|
353
|
+
style[field] = typeof val === "number" ? `${val}px` : val;
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
if (layout.zIndex !== void 0) style.zIndex = layout.zIndex;
|
|
357
|
+
const anchorMap = {
|
|
358
|
+
"top-left": "translate(0, 0)",
|
|
359
|
+
"top-center": "translate(-50%, 0)",
|
|
360
|
+
"top-right": "translate(-100%, 0)",
|
|
361
|
+
"center-left": "translate(0, -50%)",
|
|
362
|
+
center: "translate(-50%, -50%)",
|
|
363
|
+
"center-right": "translate(-100%, -50%)",
|
|
364
|
+
"bottom-left": "translate(0, -100%)",
|
|
365
|
+
"bottom-center": "translate(-50%, -100%)",
|
|
366
|
+
"bottom-right": "translate(-100%, -100%)"
|
|
367
|
+
};
|
|
368
|
+
if (layout.anchor) {
|
|
369
|
+
style.transform = anchorMap[layout.anchor];
|
|
370
|
+
}
|
|
371
|
+
return style;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// src/utils/emitter.ts
|
|
375
|
+
var SimpleEmitter = class {
|
|
376
|
+
constructor() {
|
|
377
|
+
__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Registers a callback function to be executed whenever data is emitted.
|
|
381
|
+
*
|
|
382
|
+
* @param fn - The callback function.
|
|
383
|
+
* @returns A function that, when called, unsubscribes the listener.
|
|
384
|
+
*/
|
|
385
|
+
subscribe(fn) {
|
|
386
|
+
this.listeners.add(fn);
|
|
387
|
+
return () => this.listeners.delete(fn);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Broadcasts the provided data to all registered listeners.
|
|
391
|
+
* Each listener is executed within a try-catch block to ensure that
|
|
392
|
+
* an error in one subscriber doesn't prevent others from receiving the signal.
|
|
393
|
+
*
|
|
394
|
+
* @param data - The payload to be sent to all subscribers.
|
|
395
|
+
*/
|
|
396
|
+
emit(data) {
|
|
397
|
+
this.listeners.forEach((fn) => {
|
|
398
|
+
try {
|
|
399
|
+
fn(data);
|
|
400
|
+
} catch (error) {
|
|
401
|
+
console.error("[OmniPad-Core] Emitter callback error:", error);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Removes all listeners and clears the subscription set.
|
|
407
|
+
* Essential for preventing memory leaks when an Entity is destroyed.
|
|
408
|
+
*/
|
|
409
|
+
clear() {
|
|
410
|
+
this.listeners.clear();
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// src/entities/BaseEntity.ts
|
|
415
|
+
var BaseEntity = class {
|
|
416
|
+
constructor(uid, type, initialConfig, initialState) {
|
|
417
|
+
__publicField(this, "uid");
|
|
418
|
+
__publicField(this, "type");
|
|
419
|
+
__publicField(this, "config");
|
|
420
|
+
__publicField(this, "state");
|
|
421
|
+
__publicField(this, "rect", null);
|
|
422
|
+
// 内部状态发射器,负责处理状态订阅逻辑 / Internal emitter for state subscription logic
|
|
423
|
+
__publicField(this, "stateEmitter", new SimpleEmitter());
|
|
424
|
+
this.uid = uid;
|
|
425
|
+
this.type = type;
|
|
426
|
+
this.config = initialConfig;
|
|
427
|
+
this.state = initialState;
|
|
428
|
+
}
|
|
429
|
+
// --- IObservable Implementation ---
|
|
430
|
+
subscribe(cb) {
|
|
431
|
+
cb(this.state);
|
|
432
|
+
return this.stateEmitter.subscribe(cb);
|
|
433
|
+
}
|
|
434
|
+
// --- State Management ---
|
|
435
|
+
/**
|
|
436
|
+
* Updates the internal state and notifies all subscribers.
|
|
437
|
+
*
|
|
438
|
+
* @param partialState - Partial object containing updated state values.
|
|
439
|
+
*/
|
|
440
|
+
setState(partialState) {
|
|
441
|
+
this.state = { ...this.state, ...partialState };
|
|
442
|
+
this.stateEmitter.emit(this.state);
|
|
443
|
+
}
|
|
444
|
+
// --- Lifecycle ---
|
|
445
|
+
destroy() {
|
|
446
|
+
this.reset();
|
|
447
|
+
this.stateEmitter.clear();
|
|
448
|
+
Registry.getInstance().unregister(this.uid);
|
|
449
|
+
}
|
|
450
|
+
updateRect(rect) {
|
|
451
|
+
this.rect = rect;
|
|
452
|
+
}
|
|
453
|
+
updateConfig(newConfig) {
|
|
454
|
+
this.config = { ...this.config, ...newConfig };
|
|
455
|
+
this.stateEmitter.emit(this.state);
|
|
456
|
+
}
|
|
457
|
+
getState() {
|
|
458
|
+
return this.state;
|
|
459
|
+
}
|
|
460
|
+
getConfig() {
|
|
461
|
+
return this.config;
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// src/registry/index.ts
|
|
466
|
+
var GLOBAL_REGISTRY_KEY = /* @__PURE__ */ Symbol.for("omnipad.registry.instance");
|
|
467
|
+
var Registry = class _Registry {
|
|
468
|
+
/**
|
|
469
|
+
* Private constructor to enforce singleton pattern.
|
|
470
|
+
*/
|
|
471
|
+
constructor() {
|
|
472
|
+
/** Internal storage: Mapping of Entity UID to Entity Instance */
|
|
473
|
+
__publicField(this, "entities", /* @__PURE__ */ new Map());
|
|
474
|
+
if (import.meta.env?.DEV) {
|
|
475
|
+
console.log("[OmniPad-Core] Registry initialized.");
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Retrieves the global instance of the Registry.
|
|
480
|
+
* Uses globalThis to ensure the instance is unique even if the library is loaded multiple times.
|
|
481
|
+
*/
|
|
482
|
+
static getInstance() {
|
|
483
|
+
const globalObj = globalThis;
|
|
484
|
+
if (!globalObj[GLOBAL_REGISTRY_KEY]) {
|
|
485
|
+
globalObj[GLOBAL_REGISTRY_KEY] = new _Registry();
|
|
486
|
+
}
|
|
487
|
+
return globalObj[GLOBAL_REGISTRY_KEY];
|
|
488
|
+
}
|
|
489
|
+
register(entity) {
|
|
490
|
+
if (!entity.uid) {
|
|
491
|
+
console.warn("[OmniPad-Core] Registry: Attempted to register entity without UID.", entity);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (this.entities.has(entity.uid)) {
|
|
495
|
+
}
|
|
496
|
+
this.entities.set(entity.uid, entity);
|
|
497
|
+
}
|
|
498
|
+
unregister(uid) {
|
|
499
|
+
if (this.entities.has(uid)) {
|
|
500
|
+
this.entities.delete(uid);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
getEntity(uid) {
|
|
504
|
+
return this.entities.get(uid);
|
|
505
|
+
}
|
|
506
|
+
getAllEntities() {
|
|
507
|
+
return Array.from(this.entities.values());
|
|
508
|
+
}
|
|
509
|
+
getEntitiesByRoot(rootUid) {
|
|
510
|
+
const all = this.getAllEntities();
|
|
511
|
+
if (!rootUid) return all;
|
|
512
|
+
const rootEntity = this.entities.get(rootUid);
|
|
513
|
+
if (!rootEntity) {
|
|
514
|
+
console.warn(`[OmniPad-Core] Registry: Root entity ${rootUid} not found.`);
|
|
515
|
+
return [];
|
|
516
|
+
}
|
|
517
|
+
const parentMap = /* @__PURE__ */ new Map();
|
|
518
|
+
all.forEach((entity) => {
|
|
519
|
+
if (entity instanceof BaseEntity) {
|
|
520
|
+
const config = entity.getConfig();
|
|
521
|
+
if (config.parentId) {
|
|
522
|
+
if (!parentMap.has(config.parentId)) {
|
|
523
|
+
parentMap.set(config.parentId, []);
|
|
524
|
+
}
|
|
525
|
+
parentMap.get(config.parentId).push(entity);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
const result = [];
|
|
530
|
+
const queue = [rootUid];
|
|
531
|
+
const visited = /* @__PURE__ */ new Set();
|
|
532
|
+
while (queue.length > 0) {
|
|
533
|
+
const currentId = queue.shift();
|
|
534
|
+
if (visited.has(currentId)) continue;
|
|
535
|
+
visited.add(currentId);
|
|
536
|
+
const entity = this.entities.get(currentId);
|
|
537
|
+
if (entity) {
|
|
538
|
+
result.push(entity);
|
|
539
|
+
const children = parentMap.get(currentId);
|
|
540
|
+
if (children) {
|
|
541
|
+
children.forEach((child) => queue.push(child.uid));
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return result;
|
|
546
|
+
}
|
|
547
|
+
destroyByRoot(rootUid) {
|
|
548
|
+
const entities = this.getEntitiesByRoot(rootUid);
|
|
549
|
+
for (let i = entities.length - 1; i >= 0; i--) {
|
|
550
|
+
const entity = entities[i];
|
|
551
|
+
try {
|
|
552
|
+
entity.destroy();
|
|
553
|
+
} catch (error) {
|
|
554
|
+
console.error(`[OmniPad-Core] Error during destroyByRoot at ${entity.uid}:`, error);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
clear() {
|
|
559
|
+
this.entities.clear();
|
|
560
|
+
}
|
|
561
|
+
resetAll() {
|
|
562
|
+
this.entities.forEach((entity) => {
|
|
563
|
+
if ("reset" in entity) {
|
|
564
|
+
entity.reset();
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
debugGetSnapshot() {
|
|
569
|
+
return new Map(this.entities);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
// src/utils/profile.ts
|
|
574
|
+
function parseProfileJson(raw) {
|
|
575
|
+
if (!raw || typeof raw !== "object") {
|
|
576
|
+
throw new Error("[OmniPad-Validation] Profile must be a valid JSON object.");
|
|
577
|
+
}
|
|
578
|
+
if (!raw.rootId) {
|
|
579
|
+
throw new Error('[OmniPad-Validation] Missing "rootId" in profile.');
|
|
580
|
+
}
|
|
581
|
+
if (!Array.isArray(raw.items)) {
|
|
582
|
+
throw new Error('[OmniPad-Validation] "items" must be an array.');
|
|
583
|
+
}
|
|
584
|
+
const meta = {
|
|
585
|
+
name: raw.meta?.name || "Untitled Profile",
|
|
586
|
+
version: raw.meta?.version || "1.0.0",
|
|
587
|
+
author: raw.meta?.author || "Unknown"
|
|
588
|
+
};
|
|
589
|
+
const items = raw.items.map((item, index) => {
|
|
590
|
+
if (!item.id || !item.type) {
|
|
591
|
+
throw new Error(`[OmniPad-Validation] Item at index ${index} is missing "id" or "type".`);
|
|
592
|
+
}
|
|
593
|
+
return {
|
|
594
|
+
id: String(item.id),
|
|
595
|
+
type: String(item.type),
|
|
596
|
+
parentId: item.parentId ? String(item.parentId) : void 0,
|
|
597
|
+
// 确保 config 存在,业务参数平铺于此
|
|
598
|
+
config: item.config || {}
|
|
599
|
+
};
|
|
600
|
+
});
|
|
601
|
+
return {
|
|
602
|
+
meta,
|
|
603
|
+
rootId: String(raw.rootId),
|
|
604
|
+
items
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
function parseProfileTree(profile) {
|
|
608
|
+
const { items, rootId } = profile;
|
|
609
|
+
const cidToUidMap = /* @__PURE__ */ new Map();
|
|
610
|
+
const getUid = (cid, type = "node") => {
|
|
611
|
+
if (isGlobalID(cid)) return cid;
|
|
612
|
+
if (!cidToUidMap.has(cid)) {
|
|
613
|
+
cidToUidMap.set(cid, generateUID(type));
|
|
614
|
+
}
|
|
615
|
+
return cidToUidMap.get(cid);
|
|
616
|
+
};
|
|
617
|
+
items.forEach((item) => getUid(item.id, item.type));
|
|
618
|
+
const childrenMap = /* @__PURE__ */ new Map();
|
|
619
|
+
items.forEach((item) => {
|
|
620
|
+
if (item.parentId) {
|
|
621
|
+
if (!childrenMap.has(item.parentId)) {
|
|
622
|
+
childrenMap.set(item.parentId, []);
|
|
623
|
+
}
|
|
624
|
+
childrenMap.get(item.parentId).push(item);
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
const buildNode = (item) => {
|
|
628
|
+
const runtimeConfig = { ...item.config };
|
|
629
|
+
if (runtimeConfig?.targetStageId) {
|
|
630
|
+
runtimeConfig.targetStageId = getUid(runtimeConfig.targetStageId);
|
|
631
|
+
}
|
|
632
|
+
if (runtimeConfig?.dynamicWidgetId) {
|
|
633
|
+
runtimeConfig.dynamicWidgetId = getUid(runtimeConfig.dynamicWidgetId);
|
|
634
|
+
}
|
|
635
|
+
const rawChildren = childrenMap.get(item.id) || [];
|
|
636
|
+
const children = rawChildren.map((child) => buildNode(child));
|
|
637
|
+
return {
|
|
638
|
+
uid: getUid(item.id),
|
|
639
|
+
type: item.type,
|
|
640
|
+
config: runtimeConfig,
|
|
641
|
+
children
|
|
642
|
+
};
|
|
643
|
+
};
|
|
644
|
+
const rootItem = items.find((i) => i.id === rootId);
|
|
645
|
+
if (!rootItem) {
|
|
646
|
+
throw new Error(`[OmniPad-Core] Root item with ID "${rootId}" not found in profile.`);
|
|
647
|
+
}
|
|
648
|
+
return buildNode(rootItem);
|
|
649
|
+
}
|
|
650
|
+
function exportProfile(meta, rootUid) {
|
|
651
|
+
const registry = Registry.getInstance();
|
|
652
|
+
const allEntities = registry.getEntitiesByRoot(rootUid);
|
|
653
|
+
const eidToCidMap = /* @__PURE__ */ new Map();
|
|
654
|
+
let cidCounter = 0;
|
|
655
|
+
const getNewCid = (eid) => {
|
|
656
|
+
if (isGlobalID(eid)) return eid;
|
|
657
|
+
if (!eidToCidMap.has(eid)) {
|
|
658
|
+
eidToCidMap.set(eid, `node_${++cidCounter}`);
|
|
659
|
+
}
|
|
660
|
+
return eidToCidMap.get(eid);
|
|
661
|
+
};
|
|
662
|
+
const items = allEntities.map((entity) => {
|
|
663
|
+
const config = entity.getConfig();
|
|
664
|
+
const currentEid = entity.uid;
|
|
665
|
+
const processedConfig = { ...config };
|
|
666
|
+
if (processedConfig.targetStageId) {
|
|
667
|
+
processedConfig.targetStageId = getNewCid(processedConfig.targetStageId);
|
|
668
|
+
}
|
|
669
|
+
if (processedConfig.dynamicWidgetId) {
|
|
670
|
+
processedConfig.dynamicWidgetId = getNewCid(processedConfig.dynamicWidgetId);
|
|
671
|
+
}
|
|
672
|
+
const { id, type, parentId, ...cleanConfig } = processedConfig;
|
|
673
|
+
return {
|
|
674
|
+
id: getNewCid(currentEid),
|
|
675
|
+
type: entity.type,
|
|
676
|
+
// 如果存在父级,将其 UID 转换回本次导出的新 CID
|
|
677
|
+
parentId: config.parentId ? getNewCid(config.parentId) : void 0,
|
|
678
|
+
config: cleanConfig
|
|
679
|
+
};
|
|
680
|
+
});
|
|
681
|
+
return {
|
|
682
|
+
meta,
|
|
683
|
+
rootId: getNewCid(rootUid),
|
|
684
|
+
items
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// src/imputManager/index.ts
|
|
689
|
+
var INPUT_MANAGER_KEY = /* @__PURE__ */ Symbol.for("omnipad.input_manager.instance");
|
|
690
|
+
var InputManager = class _InputManager {
|
|
691
|
+
constructor() {
|
|
692
|
+
/** Internal flag to prevent multiple event registrations */
|
|
693
|
+
__publicField(this, "_isListening", false);
|
|
694
|
+
/**
|
|
695
|
+
* Manually triggers a system-wide input reset via Registry.
|
|
696
|
+
*/
|
|
697
|
+
__publicField(this, "handleGlobalReset", () => {
|
|
698
|
+
if (import.meta.env?.DEV) {
|
|
699
|
+
console.debug("[OmniPad-Core] Safety reset triggered by environment change.");
|
|
700
|
+
}
|
|
701
|
+
Registry.getInstance().resetAll();
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Retrieves the global instance of the InputManager.
|
|
706
|
+
* Ensures uniqueness across multiple bundles or modules.
|
|
707
|
+
*/
|
|
708
|
+
static getInstance() {
|
|
709
|
+
const globalObj = globalThis;
|
|
710
|
+
if (!globalObj[INPUT_MANAGER_KEY]) {
|
|
711
|
+
globalObj[INPUT_MANAGER_KEY] = new _InputManager();
|
|
712
|
+
}
|
|
713
|
+
return globalObj[INPUT_MANAGER_KEY];
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Initializes global safety listeners.
|
|
717
|
+
* Should be called once at the root component lifecycle (e.g., VirtualLayer).
|
|
718
|
+
*/
|
|
719
|
+
init() {
|
|
720
|
+
if (this._isListening) return;
|
|
721
|
+
window.addEventListener("resize", this.handleGlobalReset);
|
|
722
|
+
window.addEventListener("blur", this.handleGlobalReset);
|
|
723
|
+
this._isListening = true;
|
|
724
|
+
if (import.meta.env?.DEV) {
|
|
725
|
+
console.log("[OmniPad-Core] Global InputManager monitoring started.");
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Toggle full-screen state of the page.
|
|
730
|
+
* @param element Target HTMLElement
|
|
731
|
+
*/
|
|
732
|
+
async toggleFullscreen(element) {
|
|
733
|
+
const target = element || document.documentElement;
|
|
734
|
+
try {
|
|
735
|
+
if (!document.fullscreenElement) {
|
|
736
|
+
Registry.getInstance().resetAll();
|
|
737
|
+
await target.requestFullscreen();
|
|
738
|
+
} else {
|
|
739
|
+
Registry.getInstance().resetAll();
|
|
740
|
+
await document.exitFullscreen();
|
|
741
|
+
}
|
|
742
|
+
} catch (err) {
|
|
743
|
+
console.error(`[OmniPad-Core] Fullscreen toggle failed:`, err);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Full-screen status query provided to the UI layer.
|
|
748
|
+
*/
|
|
749
|
+
isFullscreen() {
|
|
750
|
+
return !!document.fullscreenElement;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Detaches all global listeners.
|
|
754
|
+
*/
|
|
755
|
+
destroy() {
|
|
756
|
+
window.removeEventListener("resize", this.handleGlobalReset);
|
|
757
|
+
window.removeEventListener("blur", this.handleGlobalReset);
|
|
758
|
+
this._isListening = false;
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
// src/entities/InputZoneCore.ts
|
|
763
|
+
var INITIAL_STATE = {
|
|
764
|
+
isDynamicActive: false,
|
|
765
|
+
dynamicPointerId: null,
|
|
766
|
+
dynamicPosition: { x: 0, y: 0 }
|
|
767
|
+
};
|
|
768
|
+
var InputZoneCore = class extends BaseEntity {
|
|
769
|
+
constructor(uid, config) {
|
|
770
|
+
super(uid, TYPES.INPUT_ZONE, config, INITIAL_STATE);
|
|
771
|
+
}
|
|
772
|
+
onPointerDown(e) {
|
|
773
|
+
if (this.state.isDynamicActive) return;
|
|
774
|
+
if (e.target !== e.currentTarget) return;
|
|
775
|
+
if (e.cancelable) e.preventDefault();
|
|
776
|
+
const pos = this.calculateRelativePosition(e.clientX, e.clientY);
|
|
777
|
+
this.setState({
|
|
778
|
+
isDynamicActive: true,
|
|
779
|
+
dynamicPointerId: e.pointerId,
|
|
780
|
+
dynamicPosition: pos
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
onPointerMove(e) {
|
|
784
|
+
if (!this.state.isDynamicActive || e.pointerId !== this.state.dynamicPointerId) return;
|
|
785
|
+
}
|
|
786
|
+
onPointerUp(e) {
|
|
787
|
+
this.handleRelease(e);
|
|
788
|
+
}
|
|
789
|
+
onPointerCancel(e) {
|
|
790
|
+
this.handleRelease(e);
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Internal helper to handle pointer release and cleanup.
|
|
794
|
+
*/
|
|
795
|
+
handleRelease(e) {
|
|
796
|
+
if (e.pointerId === this.state.dynamicPointerId) {
|
|
797
|
+
try {
|
|
798
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
799
|
+
} catch (err) {
|
|
800
|
+
}
|
|
801
|
+
this.setState({
|
|
802
|
+
isDynamicActive: false,
|
|
803
|
+
dynamicPointerId: null
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
// --- Helper Calculations ---
|
|
808
|
+
/**
|
|
809
|
+
* Converts viewport pixels to percentage coordinates relative to the zone.
|
|
810
|
+
*/
|
|
811
|
+
calculateRelativePosition(clientX, clientY) {
|
|
812
|
+
if (!this.rect) return { x: 0, y: 0 };
|
|
813
|
+
return {
|
|
814
|
+
x: pxToPercent(clientX - this.rect.left, this.rect.width),
|
|
815
|
+
y: pxToPercent(clientY - this.rect.top, this.rect.height)
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Whether the interceptor layer should be enabled.
|
|
820
|
+
*/
|
|
821
|
+
get isInterceptorRequired() {
|
|
822
|
+
return !!(this.config.dynamicWidgetId || this.config.preventFocusLoss);
|
|
823
|
+
}
|
|
824
|
+
reset() {
|
|
825
|
+
this.setState({
|
|
826
|
+
isDynamicActive: false,
|
|
827
|
+
dynamicPointerId: null
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
// src/entities/KeyboardButtonCore.ts
|
|
833
|
+
var INITIAL_STATE2 = {
|
|
834
|
+
isActive: false,
|
|
835
|
+
isPressed: false,
|
|
836
|
+
pointerId: null,
|
|
837
|
+
value: 0
|
|
838
|
+
};
|
|
839
|
+
var KeyboardButtonCore = class extends BaseEntity {
|
|
840
|
+
/**
|
|
841
|
+
* Creates an instance of KeyboardButtonCore.
|
|
842
|
+
*
|
|
843
|
+
* @param uid - The unique entity ID.
|
|
844
|
+
* @param config - The flat configuration object for the button.
|
|
845
|
+
*/
|
|
846
|
+
constructor(uid, config) {
|
|
847
|
+
super(uid, TYPES.KEYBOARD_BUTTON, config, INITIAL_STATE2);
|
|
848
|
+
}
|
|
849
|
+
// --- IPointerHandler Implementation ---
|
|
850
|
+
onPointerDown(e) {
|
|
851
|
+
if (e.cancelable) e.preventDefault();
|
|
852
|
+
e.target.setPointerCapture(e.pointerId);
|
|
853
|
+
this.setState({
|
|
854
|
+
isActive: true,
|
|
855
|
+
isPressed: true,
|
|
856
|
+
pointerId: e.pointerId
|
|
857
|
+
});
|
|
858
|
+
this.sendInputSignal(ACTION_TYPES.KEYDOWN);
|
|
859
|
+
}
|
|
860
|
+
onPointerUp(e) {
|
|
861
|
+
if (e.cancelable) e.preventDefault();
|
|
862
|
+
this.handleRelease(e);
|
|
863
|
+
}
|
|
864
|
+
onPointerCancel(e) {
|
|
865
|
+
this.handleRelease(e);
|
|
866
|
+
}
|
|
867
|
+
onPointerMove(e) {
|
|
868
|
+
if (e.cancelable) e.preventDefault();
|
|
869
|
+
}
|
|
870
|
+
// --- Internal Logic ---
|
|
871
|
+
/**
|
|
872
|
+
* Common logic for releasing the button state.
|
|
873
|
+
*
|
|
874
|
+
* @param e - The pointer event that triggered the release.
|
|
875
|
+
*/
|
|
876
|
+
handleRelease(e) {
|
|
877
|
+
if (this.state.pointerId !== e.pointerId) return;
|
|
878
|
+
if (e.target.hasPointerCapture(e.pointerId)) {
|
|
879
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
880
|
+
}
|
|
881
|
+
this.setState(INITIAL_STATE2);
|
|
882
|
+
this.sendInputSignal(ACTION_TYPES.KEYUP);
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Dispatches input signals to the registered target stage.
|
|
886
|
+
*
|
|
887
|
+
* @param type - The action type (keydown or keyup).
|
|
888
|
+
*/
|
|
889
|
+
sendInputSignal(type) {
|
|
890
|
+
const targetId = this.config.targetStageId;
|
|
891
|
+
if (!targetId) return;
|
|
892
|
+
const target = Registry.getInstance().getEntity(targetId);
|
|
893
|
+
if (target && typeof target.handleSignal === "function") {
|
|
894
|
+
const signal = {
|
|
895
|
+
targetStageId: targetId,
|
|
896
|
+
type,
|
|
897
|
+
payload: {
|
|
898
|
+
key: this.config.mapping.key,
|
|
899
|
+
code: this.config.mapping.code,
|
|
900
|
+
keyCode: this.config.mapping.keyCode
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
target.handleSignal(signal);
|
|
904
|
+
} else {
|
|
905
|
+
if (import.meta.env?.DEV) {
|
|
906
|
+
console.warn(`[OmniPad-Core] Button ${this.uid} target not found: ${targetId}`);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
// --- IResettable Implementation ---
|
|
911
|
+
reset() {
|
|
912
|
+
if (this.state.isPressed) {
|
|
913
|
+
this.sendInputSignal(ACTION_TYPES.KEYUP);
|
|
914
|
+
}
|
|
915
|
+
this.setState(INITIAL_STATE2);
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
// src/entities/RootLayerCore.ts
|
|
920
|
+
var INITIAL_STATE3 = {
|
|
921
|
+
isHighlighted: false
|
|
922
|
+
};
|
|
923
|
+
var RootLayerCore = class extends BaseEntity {
|
|
924
|
+
constructor(uid, config) {
|
|
925
|
+
super(uid, TYPES.ROOT_LAYER, config, INITIAL_STATE3);
|
|
926
|
+
}
|
|
927
|
+
reset() {
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
// src/entities/TargetZoneCore.ts
|
|
932
|
+
var INITIAL_STATE4 = {
|
|
933
|
+
position: { x: 50, y: 50 },
|
|
934
|
+
isVisible: false,
|
|
935
|
+
isPointerDown: false,
|
|
936
|
+
isFocusReturning: false
|
|
937
|
+
};
|
|
938
|
+
var TargetZoneCore = class extends BaseEntity {
|
|
939
|
+
constructor(uid, config) {
|
|
940
|
+
super(uid, TYPES.TARGET_ZONE, config, INITIAL_STATE4);
|
|
941
|
+
__publicField(this, "hideTimer", null);
|
|
942
|
+
__publicField(this, "focusFeedbackTimer", null);
|
|
943
|
+
}
|
|
944
|
+
// --- ISignalReceiver Implementation ---
|
|
945
|
+
handleSignal(signal) {
|
|
946
|
+
const { type, payload } = signal;
|
|
947
|
+
this.ensureFocus();
|
|
948
|
+
switch (type) {
|
|
949
|
+
case ACTION_TYPES.KEYDOWN:
|
|
950
|
+
case ACTION_TYPES.KEYUP:
|
|
951
|
+
dispatchKeyboardEvent(type, payload);
|
|
952
|
+
break;
|
|
953
|
+
case ACTION_TYPES.MOUSEMOVE:
|
|
954
|
+
if (payload.point) {
|
|
955
|
+
this.updateCursorPosition(payload.point);
|
|
956
|
+
if (this.config.cursorEnabled) this.showCursor();
|
|
957
|
+
this.executeMouseAction(ACTION_TYPES.POINTERMOVE, payload);
|
|
958
|
+
}
|
|
959
|
+
break;
|
|
960
|
+
case ACTION_TYPES.MOUSEDOWN:
|
|
961
|
+
case ACTION_TYPES.MOUSEUP:
|
|
962
|
+
case ACTION_TYPES.CLICK:
|
|
963
|
+
if (this.config.cursorEnabled) this.showCursor();
|
|
964
|
+
this.executeMouseAction(
|
|
965
|
+
type.startsWith(ACTION_TYPES.MOUSE) ? type.replace(ACTION_TYPES.MOUSE, ACTION_TYPES.POINTER) : type,
|
|
966
|
+
payload
|
|
967
|
+
);
|
|
968
|
+
break;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
// --- Mouse & Pointer Simulation ---
|
|
972
|
+
/**
|
|
973
|
+
* Calculates pixel coordinates and dispatches simulated pointer events to the deepest element.
|
|
974
|
+
*
|
|
975
|
+
* @param pointerType - The specific pointer event type (e.g., pointermove, pointerdown).
|
|
976
|
+
* @param payload - Data containing point coordinates or button info.
|
|
977
|
+
*/
|
|
978
|
+
executeMouseAction(pointerType, payload) {
|
|
979
|
+
if (!this.rect) return;
|
|
980
|
+
if (pointerType === ACTION_TYPES.POINTERDOWN) this.setState({ isPointerDown: true });
|
|
981
|
+
if (pointerType === ACTION_TYPES.POINTERUP) this.setState({ isPointerDown: false });
|
|
982
|
+
const target = payload.point || this.state.position;
|
|
983
|
+
const px = this.rect.left + percentToPx(target.x, this.rect.width);
|
|
984
|
+
const py = this.rect.top + percentToPx(target.y, this.rect.height);
|
|
985
|
+
dispatchPointerEventAtPos(pointerType, px, py, {
|
|
986
|
+
button: payload.button ?? 0,
|
|
987
|
+
buttons: this.state.isPointerDown ? 1 : 0
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
// --- Focus Management ---
|
|
991
|
+
/**
|
|
992
|
+
* Checks if the target element under the virtual cursor has focus, and reclaims it if lost.
|
|
993
|
+
*/
|
|
994
|
+
ensureFocus() {
|
|
995
|
+
if (!this.rect) return;
|
|
996
|
+
const px = this.rect.left + percentToPx(this.state.position.x, this.rect.width);
|
|
997
|
+
const py = this.rect.top + percentToPx(this.state.position.y, this.rect.height);
|
|
998
|
+
const target = getDeepElement(px, py);
|
|
999
|
+
if (!target) return;
|
|
1000
|
+
if (getDeepActiveElement() !== target) {
|
|
1001
|
+
focusElement(target);
|
|
1002
|
+
this.triggerFocusFeedback();
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Activates a temporary visual feedback state to indicate a focus-reclaim event.
|
|
1007
|
+
*/
|
|
1008
|
+
triggerFocusFeedback() {
|
|
1009
|
+
this.setState({ isFocusReturning: true });
|
|
1010
|
+
if (this.focusFeedbackTimer) clearTimeout(this.focusFeedbackTimer);
|
|
1011
|
+
this.focusFeedbackTimer = setTimeout(() => this.setState({ isFocusReturning: false }), 500);
|
|
1012
|
+
}
|
|
1013
|
+
// --- Cursor State Helpers ---
|
|
1014
|
+
/**
|
|
1015
|
+
* Updates the internal virtual cursor coordinates.
|
|
1016
|
+
*/
|
|
1017
|
+
updateCursorPosition(point) {
|
|
1018
|
+
this.setState({ position: { ...point } });
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Makes the virtual cursor visible and sets a timeout for auto-hiding.
|
|
1022
|
+
*/
|
|
1023
|
+
showCursor() {
|
|
1024
|
+
this.setState({ isVisible: true });
|
|
1025
|
+
if (this.hideTimer) clearTimeout(this.hideTimer);
|
|
1026
|
+
if (this.config.cursorAutoDelay && this.config.cursorAutoDelay > 0) {
|
|
1027
|
+
this.hideTimer = setTimeout(
|
|
1028
|
+
() => this.setState({ isVisible: false }),
|
|
1029
|
+
this.config.cursorAutoDelay
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
// --- IResettable Implementation ---
|
|
1034
|
+
reset() {
|
|
1035
|
+
if (this.state.isPointerDown) {
|
|
1036
|
+
this.executeMouseAction(ACTION_TYPES.POINTERUP, {});
|
|
1037
|
+
}
|
|
1038
|
+
if (this.hideTimer) clearTimeout(this.hideTimer);
|
|
1039
|
+
if (this.focusFeedbackTimer) clearTimeout(this.focusFeedbackTimer);
|
|
1040
|
+
this.setState({
|
|
1041
|
+
isVisible: false,
|
|
1042
|
+
isPointerDown: false,
|
|
1043
|
+
isFocusReturning: false
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
|
|
1048
|
+
// src/index.ts
|
|
1049
|
+
var OmniPad = {
|
|
1050
|
+
Context: CONTEXT,
|
|
1051
|
+
Keys: KEYS,
|
|
1052
|
+
Types: TYPES
|
|
1053
|
+
};
|
|
1054
|
+
export {
|
|
1055
|
+
ACTION_TYPES,
|
|
1056
|
+
BaseEntity,
|
|
1057
|
+
CONTEXT,
|
|
1058
|
+
InputManager,
|
|
1059
|
+
InputZoneCore,
|
|
1060
|
+
KEYS,
|
|
1061
|
+
KeyboardButtonCore,
|
|
1062
|
+
OmniPad,
|
|
1063
|
+
Registry,
|
|
1064
|
+
RootLayerCore,
|
|
1065
|
+
SimpleEmitter,
|
|
1066
|
+
TYPES,
|
|
1067
|
+
TargetZoneCore,
|
|
1068
|
+
addVec,
|
|
1069
|
+
applyAxialDeadzone,
|
|
1070
|
+
applyRadialDeadzone,
|
|
1071
|
+
clamp,
|
|
1072
|
+
clampVector,
|
|
1073
|
+
degToRad,
|
|
1074
|
+
dispatchKeyboardEvent,
|
|
1075
|
+
dispatchPointerEventAtPos,
|
|
1076
|
+
exportProfile,
|
|
1077
|
+
focusElement,
|
|
1078
|
+
generateUID,
|
|
1079
|
+
getAnchorPosition,
|
|
1080
|
+
getAngle,
|
|
1081
|
+
getDeadzoneScalar,
|
|
1082
|
+
getDeepActiveElement,
|
|
1083
|
+
getDeepElement,
|
|
1084
|
+
getDistance,
|
|
1085
|
+
isGlobalID,
|
|
1086
|
+
isVec2Equal,
|
|
1087
|
+
lerp,
|
|
1088
|
+
lockTo4Directions,
|
|
1089
|
+
lockTo8Directions,
|
|
1090
|
+
normalizeVec,
|
|
1091
|
+
parseProfileJson,
|
|
1092
|
+
parseProfileTree,
|
|
1093
|
+
percentToPx,
|
|
1094
|
+
pxToPercent,
|
|
1095
|
+
radToDeg,
|
|
1096
|
+
radToVec,
|
|
1097
|
+
remap,
|
|
1098
|
+
resolveLayoutStyle,
|
|
1099
|
+
roundTo,
|
|
1100
|
+
scaleVec,
|
|
1101
|
+
subVec
|
|
1102
|
+
};
|