@omnipad/core 0.1.1-alpha.0 → 0.2.0-alpha.2
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 +301 -155
- package/dist/index.d.ts +301 -155
- package/dist/index.js +468 -128
- package/dist/index.mjs +464 -127
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -117,7 +117,7 @@ var STANDARD_KEYS = {
|
|
|
117
117
|
var KEYS = STANDARD_KEYS;
|
|
118
118
|
|
|
119
119
|
// src/types/index.ts
|
|
120
|
-
var
|
|
120
|
+
var CMP_TYPES = {
|
|
121
121
|
// --- Zones ---
|
|
122
122
|
/** Area responsible for capturing touches and spawning dynamic widgets */
|
|
123
123
|
INPUT_ZONE: "input-zone",
|
|
@@ -252,14 +252,17 @@ var applyAxialDeadzone = (v, threshold, max) => {
|
|
|
252
252
|
};
|
|
253
253
|
|
|
254
254
|
// src/utils/dom.ts
|
|
255
|
-
var getDeepElement = (x, y) => {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
255
|
+
var getDeepElement = (x, y, ignoreClass = "omnipad-target-zone") => {
|
|
256
|
+
const elements = document.elementsFromPoint(x, y);
|
|
257
|
+
let target = elements.find((el) => !el.classList.contains(ignoreClass));
|
|
258
|
+
if (!target) return null;
|
|
259
|
+
while (target && target.shadowRoot) {
|
|
260
|
+
const nestedElements = target.shadowRoot.elementsFromPoint(x, y);
|
|
261
|
+
const nestedTarget = nestedElements.find((el) => !el.classList.contains(ignoreClass));
|
|
262
|
+
if (!nestedTarget || nestedTarget === target) break;
|
|
263
|
+
target = nestedTarget;
|
|
264
|
+
}
|
|
265
|
+
return target;
|
|
263
266
|
};
|
|
264
267
|
var getDeepActiveElement = () => {
|
|
265
268
|
let el = document.activeElement;
|
|
@@ -303,7 +306,7 @@ var dispatchPointerEventAtPos = (type, x, y, opts = {}) => {
|
|
|
303
306
|
target.dispatchEvent(
|
|
304
307
|
new PointerEvent(type, {
|
|
305
308
|
isPrimary: true,
|
|
306
|
-
pointerId:
|
|
309
|
+
pointerId: 9999,
|
|
307
310
|
pointerType: "mouse",
|
|
308
311
|
// Emulate mouse behavior for Flash MouseOver/Down logic
|
|
309
312
|
...commonProps
|
|
@@ -371,97 +374,6 @@ var resolveLayoutStyle = (layout) => {
|
|
|
371
374
|
return style;
|
|
372
375
|
};
|
|
373
376
|
|
|
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
377
|
// src/registry/index.ts
|
|
466
378
|
var GLOBAL_REGISTRY_KEY = /* @__PURE__ */ Symbol.for("omnipad.registry.instance");
|
|
467
379
|
var Registry = class _Registry {
|
|
@@ -516,8 +428,9 @@ var Registry = class _Registry {
|
|
|
516
428
|
}
|
|
517
429
|
const parentMap = /* @__PURE__ */ new Map();
|
|
518
430
|
all.forEach((entity) => {
|
|
519
|
-
|
|
520
|
-
|
|
431
|
+
const e = entity;
|
|
432
|
+
if (typeof e.getConfig === "function") {
|
|
433
|
+
const config = e.getConfig();
|
|
521
434
|
if (config.parentId) {
|
|
522
435
|
if (!parentMap.has(config.parentId)) {
|
|
523
436
|
parentMap.set(config.parentId, []);
|
|
@@ -685,12 +598,72 @@ function exportProfile(meta, rootUid) {
|
|
|
685
598
|
};
|
|
686
599
|
}
|
|
687
600
|
|
|
601
|
+
// src/utils/emitter.ts
|
|
602
|
+
var SimpleEmitter = class {
|
|
603
|
+
constructor() {
|
|
604
|
+
__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Registers a callback function to be executed whenever data is emitted.
|
|
608
|
+
*
|
|
609
|
+
* @param fn - The callback function.
|
|
610
|
+
* @returns A function that, when called, unsubscribes the listener.
|
|
611
|
+
*/
|
|
612
|
+
subscribe(fn) {
|
|
613
|
+
this.listeners.add(fn);
|
|
614
|
+
return () => this.listeners.delete(fn);
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Broadcasts the provided data to all registered listeners.
|
|
618
|
+
* Each listener is executed within a try-catch block to ensure that
|
|
619
|
+
* an error in one subscriber doesn't prevent others from receiving the signal.
|
|
620
|
+
*
|
|
621
|
+
* @param data - The payload to be sent to all subscribers.
|
|
622
|
+
*/
|
|
623
|
+
emit(data) {
|
|
624
|
+
this.listeners.forEach((fn) => {
|
|
625
|
+
try {
|
|
626
|
+
fn(data);
|
|
627
|
+
} catch (error) {
|
|
628
|
+
console.error("[OmniPad-Core] Emitter callback error:", error);
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Removes all listeners and clears the subscription set.
|
|
634
|
+
* Essential for preventing memory leaks when an Entity is destroyed.
|
|
635
|
+
*/
|
|
636
|
+
clear() {
|
|
637
|
+
this.listeners.clear();
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
// src/utils/performance.ts
|
|
642
|
+
function createRafThrottler(callback) {
|
|
643
|
+
let ticking = false;
|
|
644
|
+
let latestPayload = null;
|
|
645
|
+
return function(payload) {
|
|
646
|
+
latestPayload = payload;
|
|
647
|
+
if (!ticking) {
|
|
648
|
+
ticking = true;
|
|
649
|
+
window.requestAnimationFrame(() => {
|
|
650
|
+
if (latestPayload !== null) {
|
|
651
|
+
callback(latestPayload);
|
|
652
|
+
}
|
|
653
|
+
ticking = false;
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
688
659
|
// src/imputManager/index.ts
|
|
689
660
|
var INPUT_MANAGER_KEY = /* @__PURE__ */ Symbol.for("omnipad.input_manager.instance");
|
|
690
661
|
var InputManager = class _InputManager {
|
|
691
662
|
constructor() {
|
|
692
663
|
/** Internal flag to prevent multiple event registrations */
|
|
693
664
|
__publicField(this, "_isListening", false);
|
|
665
|
+
/** A throttled version of the reset logic */
|
|
666
|
+
__publicField(this, "throttledReset");
|
|
694
667
|
/**
|
|
695
668
|
* Manually triggers a system-wide input reset via Registry.
|
|
696
669
|
*/
|
|
@@ -700,6 +673,20 @@ var InputManager = class _InputManager {
|
|
|
700
673
|
}
|
|
701
674
|
Registry.getInstance().resetAll();
|
|
702
675
|
});
|
|
676
|
+
__publicField(this, "handleResizeReset", () => {
|
|
677
|
+
this.throttledReset(null);
|
|
678
|
+
});
|
|
679
|
+
__publicField(this, "handleBlurReset", () => {
|
|
680
|
+
this.handleGlobalReset();
|
|
681
|
+
});
|
|
682
|
+
__publicField(this, "handleVisibilityChangeReset", () => {
|
|
683
|
+
if (document.visibilityState === "hidden") {
|
|
684
|
+
this.handleGlobalReset();
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
this.throttledReset = createRafThrottler(() => {
|
|
688
|
+
this.handleGlobalReset();
|
|
689
|
+
});
|
|
703
690
|
}
|
|
704
691
|
/**
|
|
705
692
|
* Retrieves the global instance of the InputManager.
|
|
@@ -718,8 +705,9 @@ var InputManager = class _InputManager {
|
|
|
718
705
|
*/
|
|
719
706
|
init() {
|
|
720
707
|
if (this._isListening) return;
|
|
721
|
-
window.addEventListener("resize", this.
|
|
722
|
-
window.addEventListener("blur", this.
|
|
708
|
+
window.addEventListener("resize", this.handleResizeReset);
|
|
709
|
+
window.addEventListener("blur", this.handleBlurReset);
|
|
710
|
+
document.addEventListener("visibilitychange", this.handleVisibilityChangeReset);
|
|
723
711
|
this._isListening = true;
|
|
724
712
|
if (import.meta.env?.DEV) {
|
|
725
713
|
console.log("[OmniPad-Core] Global InputManager monitoring started.");
|
|
@@ -753,12 +741,70 @@ var InputManager = class _InputManager {
|
|
|
753
741
|
* Detaches all global listeners.
|
|
754
742
|
*/
|
|
755
743
|
destroy() {
|
|
756
|
-
window.removeEventListener("resize", this.
|
|
757
|
-
window.removeEventListener("blur", this.
|
|
744
|
+
window.removeEventListener("resize", this.handleResizeReset);
|
|
745
|
+
window.removeEventListener("blur", this.handleBlurReset);
|
|
746
|
+
window.removeEventListener("visibilitychange", this.handleVisibilityChangeReset);
|
|
758
747
|
this._isListening = false;
|
|
759
748
|
}
|
|
760
749
|
};
|
|
761
750
|
|
|
751
|
+
// src/entities/BaseEntity.ts
|
|
752
|
+
var BaseEntity = class {
|
|
753
|
+
constructor(uid, type, initialConfig, initialState) {
|
|
754
|
+
__publicField(this, "uid");
|
|
755
|
+
__publicField(this, "type");
|
|
756
|
+
__publicField(this, "config");
|
|
757
|
+
__publicField(this, "state");
|
|
758
|
+
__publicField(this, "rectProvider", null);
|
|
759
|
+
// 内部状态发射器,负责处理状态订阅逻辑 / Internal emitter for state subscription logic
|
|
760
|
+
__publicField(this, "stateEmitter", new SimpleEmitter());
|
|
761
|
+
this.uid = uid;
|
|
762
|
+
this.type = type;
|
|
763
|
+
this.config = initialConfig;
|
|
764
|
+
this.state = initialState;
|
|
765
|
+
}
|
|
766
|
+
// --- IObservable Implementation ---
|
|
767
|
+
subscribe(cb) {
|
|
768
|
+
cb(this.state);
|
|
769
|
+
return this.stateEmitter.subscribe(cb);
|
|
770
|
+
}
|
|
771
|
+
// --- State Management ---
|
|
772
|
+
/**
|
|
773
|
+
* Updates the internal state and notifies all subscribers.
|
|
774
|
+
*
|
|
775
|
+
* @param partialState - Partial object containing updated state values.
|
|
776
|
+
*/
|
|
777
|
+
setState(partialState) {
|
|
778
|
+
this.state = { ...this.state, ...partialState };
|
|
779
|
+
this.stateEmitter.emit(this.state);
|
|
780
|
+
}
|
|
781
|
+
// --- Lifecycle ---
|
|
782
|
+
destroy() {
|
|
783
|
+
this.reset();
|
|
784
|
+
this.stateEmitter.clear();
|
|
785
|
+
Registry.getInstance().unregister(this.uid);
|
|
786
|
+
}
|
|
787
|
+
bindRectProvider(provider) {
|
|
788
|
+
this.rectProvider = provider;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Called when triggering interactions, this subclass fetches the latest boundaries in real time.
|
|
792
|
+
*/
|
|
793
|
+
getRect() {
|
|
794
|
+
return this.rectProvider ? this.rectProvider() : null;
|
|
795
|
+
}
|
|
796
|
+
updateConfig(newConfig) {
|
|
797
|
+
this.config = { ...this.config, ...newConfig };
|
|
798
|
+
this.stateEmitter.emit(this.state);
|
|
799
|
+
}
|
|
800
|
+
getState() {
|
|
801
|
+
return this.state;
|
|
802
|
+
}
|
|
803
|
+
getConfig() {
|
|
804
|
+
return this.config;
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
|
|
762
808
|
// src/entities/InputZoneCore.ts
|
|
763
809
|
var INITIAL_STATE = {
|
|
764
810
|
isDynamicActive: false,
|
|
@@ -767,12 +813,13 @@ var INITIAL_STATE = {
|
|
|
767
813
|
};
|
|
768
814
|
var InputZoneCore = class extends BaseEntity {
|
|
769
815
|
constructor(uid, config) {
|
|
770
|
-
super(uid,
|
|
816
|
+
super(uid, CMP_TYPES.INPUT_ZONE, config, INITIAL_STATE);
|
|
771
817
|
}
|
|
772
818
|
onPointerDown(e) {
|
|
773
819
|
if (this.state.isDynamicActive) return;
|
|
774
820
|
if (e.target !== e.currentTarget) return;
|
|
775
821
|
if (e.cancelable) e.preventDefault();
|
|
822
|
+
e.stopPropagation();
|
|
776
823
|
const pos = this.calculateRelativePosition(e.clientX, e.clientY);
|
|
777
824
|
this.setState({
|
|
778
825
|
isDynamicActive: true,
|
|
@@ -784,6 +831,7 @@ var InputZoneCore = class extends BaseEntity {
|
|
|
784
831
|
if (!this.state.isDynamicActive || e.pointerId !== this.state.dynamicPointerId) return;
|
|
785
832
|
}
|
|
786
833
|
onPointerUp(e) {
|
|
834
|
+
if (e.cancelable) e.preventDefault();
|
|
787
835
|
this.handleRelease(e);
|
|
788
836
|
}
|
|
789
837
|
onPointerCancel(e) {
|
|
@@ -809,10 +857,11 @@ var InputZoneCore = class extends BaseEntity {
|
|
|
809
857
|
* Converts viewport pixels to percentage coordinates relative to the zone.
|
|
810
858
|
*/
|
|
811
859
|
calculateRelativePosition(clientX, clientY) {
|
|
812
|
-
|
|
860
|
+
const rect = this.getRect();
|
|
861
|
+
if (!rect) return { x: 0, y: 0 };
|
|
813
862
|
return {
|
|
814
|
-
x: pxToPercent(clientX -
|
|
815
|
-
y: pxToPercent(clientY -
|
|
863
|
+
x: pxToPercent(clientX - rect.left, rect.width),
|
|
864
|
+
y: pxToPercent(clientY - rect.top, rect.height)
|
|
816
865
|
};
|
|
817
866
|
}
|
|
818
867
|
/**
|
|
@@ -844,11 +893,12 @@ var KeyboardButtonCore = class extends BaseEntity {
|
|
|
844
893
|
* @param config - The flat configuration object for the button.
|
|
845
894
|
*/
|
|
846
895
|
constructor(uid, config) {
|
|
847
|
-
super(uid,
|
|
896
|
+
super(uid, CMP_TYPES.KEYBOARD_BUTTON, config, INITIAL_STATE2);
|
|
848
897
|
}
|
|
849
898
|
// --- IPointerHandler Implementation ---
|
|
850
899
|
onPointerDown(e) {
|
|
851
900
|
if (e.cancelable) e.preventDefault();
|
|
901
|
+
e.stopPropagation();
|
|
852
902
|
e.target.setPointerCapture(e.pointerId);
|
|
853
903
|
this.setState({
|
|
854
904
|
isActive: true,
|
|
@@ -903,7 +953,7 @@ var KeyboardButtonCore = class extends BaseEntity {
|
|
|
903
953
|
target.handleSignal(signal);
|
|
904
954
|
} else {
|
|
905
955
|
if (import.meta.env?.DEV) {
|
|
906
|
-
console.warn(`[OmniPad-Core]
|
|
956
|
+
console.warn(`[OmniPad-Core] KeyboardButton ${this.uid} target not found: ${targetId}`);
|
|
907
957
|
}
|
|
908
958
|
}
|
|
909
959
|
}
|
|
@@ -916,20 +966,108 @@ var KeyboardButtonCore = class extends BaseEntity {
|
|
|
916
966
|
}
|
|
917
967
|
};
|
|
918
968
|
|
|
919
|
-
// src/entities/
|
|
969
|
+
// src/entities/MouseButtonCore.ts
|
|
920
970
|
var INITIAL_STATE3 = {
|
|
971
|
+
isActive: false,
|
|
972
|
+
isPressed: false,
|
|
973
|
+
pointerId: null,
|
|
974
|
+
value: 0
|
|
975
|
+
};
|
|
976
|
+
var MouseButtonCore = class extends BaseEntity {
|
|
977
|
+
constructor(uid, config) {
|
|
978
|
+
super(uid, CMP_TYPES.MOUSE_BUTTON, config, INITIAL_STATE3);
|
|
979
|
+
}
|
|
980
|
+
// --- IPointerHandler Implementation ---
|
|
981
|
+
onPointerDown(e) {
|
|
982
|
+
if (e.cancelable) e.preventDefault();
|
|
983
|
+
e.stopPropagation();
|
|
984
|
+
e.target.setPointerCapture(e.pointerId);
|
|
985
|
+
this.setState({
|
|
986
|
+
isActive: true,
|
|
987
|
+
isPressed: true,
|
|
988
|
+
pointerId: e.pointerId
|
|
989
|
+
});
|
|
990
|
+
this.sendInputSignal(ACTION_TYPES.MOUSEDOWN);
|
|
991
|
+
}
|
|
992
|
+
onPointerUp(e) {
|
|
993
|
+
if (e.cancelable) e.preventDefault();
|
|
994
|
+
this.handleRelease(e, true);
|
|
995
|
+
}
|
|
996
|
+
onPointerCancel(e) {
|
|
997
|
+
this.handleRelease(e, false);
|
|
998
|
+
}
|
|
999
|
+
onPointerMove(e) {
|
|
1000
|
+
if (e.cancelable) e.preventDefault();
|
|
1001
|
+
}
|
|
1002
|
+
// --- Internal Logic ---
|
|
1003
|
+
/**
|
|
1004
|
+
* Handles the release of the button.
|
|
1005
|
+
*
|
|
1006
|
+
* @param e - The pointer event.
|
|
1007
|
+
* @param isNormalRelease - If true, a 'click' event will also be dispatched.
|
|
1008
|
+
*/
|
|
1009
|
+
handleRelease(e, isNormalRelease) {
|
|
1010
|
+
const isCancelEvent = e.type === "pointercancel" || e.type === "lostpointercapture";
|
|
1011
|
+
if (!isCancelEvent && this.state.pointerId !== e.pointerId) return;
|
|
1012
|
+
if (e.target.hasPointerCapture(e.pointerId)) {
|
|
1013
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
1014
|
+
}
|
|
1015
|
+
this.setState(INITIAL_STATE3);
|
|
1016
|
+
this.sendInputSignal(ACTION_TYPES.MOUSEUP);
|
|
1017
|
+
if (isNormalRelease) {
|
|
1018
|
+
this.sendInputSignal(ACTION_TYPES.CLICK);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Dispatches input signals to the registered target stage.
|
|
1023
|
+
*
|
|
1024
|
+
* @param type - The action type (mousedown, mouseup, or click).
|
|
1025
|
+
*/
|
|
1026
|
+
sendInputSignal(type) {
|
|
1027
|
+
const targetId = this.config.targetStageId;
|
|
1028
|
+
if (!targetId) return;
|
|
1029
|
+
const target = Registry.getInstance().getEntity(targetId);
|
|
1030
|
+
if (target && typeof target.handleSignal === "function") {
|
|
1031
|
+
const signal = {
|
|
1032
|
+
targetStageId: targetId,
|
|
1033
|
+
type,
|
|
1034
|
+
payload: {
|
|
1035
|
+
// 传递配置中的鼠标按键索引 (0:左键, 1:中键, 2:右键)
|
|
1036
|
+
button: this.config.button ?? 0,
|
|
1037
|
+
// 如果配置了固定坐标,一并传递给 TargetZone
|
|
1038
|
+
point: this.config.fixedPoint
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
target.handleSignal(signal);
|
|
1042
|
+
} else {
|
|
1043
|
+
if (import.meta.env?.DEV) {
|
|
1044
|
+
console.warn(`[OmniPad-Core] MouseButton ${this.uid} target not found: ${targetId}`);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
// --- IResettable Implementation ---
|
|
1049
|
+
reset() {
|
|
1050
|
+
if (this.state.isPressed) {
|
|
1051
|
+
this.sendInputSignal(ACTION_TYPES.MOUSEUP);
|
|
1052
|
+
}
|
|
1053
|
+
this.setState(INITIAL_STATE3);
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
// src/entities/RootLayerCore.ts
|
|
1058
|
+
var INITIAL_STATE4 = {
|
|
921
1059
|
isHighlighted: false
|
|
922
1060
|
};
|
|
923
1061
|
var RootLayerCore = class extends BaseEntity {
|
|
924
1062
|
constructor(uid, config) {
|
|
925
|
-
super(uid,
|
|
1063
|
+
super(uid, CMP_TYPES.ROOT_LAYER, config, INITIAL_STATE4);
|
|
926
1064
|
}
|
|
927
1065
|
reset() {
|
|
928
1066
|
}
|
|
929
1067
|
};
|
|
930
1068
|
|
|
931
1069
|
// src/entities/TargetZoneCore.ts
|
|
932
|
-
var
|
|
1070
|
+
var INITIAL_STATE5 = {
|
|
933
1071
|
position: { x: 50, y: 50 },
|
|
934
1072
|
isVisible: false,
|
|
935
1073
|
isPointerDown: false,
|
|
@@ -937,9 +1075,48 @@ var INITIAL_STATE4 = {
|
|
|
937
1075
|
};
|
|
938
1076
|
var TargetZoneCore = class extends BaseEntity {
|
|
939
1077
|
constructor(uid, config) {
|
|
940
|
-
super(uid,
|
|
1078
|
+
super(uid, CMP_TYPES.TARGET_ZONE, config, INITIAL_STATE5);
|
|
941
1079
|
__publicField(this, "hideTimer", null);
|
|
942
1080
|
__publicField(this, "focusFeedbackTimer", null);
|
|
1081
|
+
__publicField(this, "throttledPointerMove");
|
|
1082
|
+
this.throttledPointerMove = createRafThrottler((e) => {
|
|
1083
|
+
this.processPhysicalEvent(e, ACTION_TYPES.MOUSEMOVE);
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
// --- IPointerHandler Implementation ---
|
|
1087
|
+
onPointerDown(e) {
|
|
1088
|
+
if (e.cancelable) e.preventDefault();
|
|
1089
|
+
e.stopPropagation();
|
|
1090
|
+
this.processPhysicalEvent(e, ACTION_TYPES.MOUSEDOWN);
|
|
1091
|
+
}
|
|
1092
|
+
onPointerMove(e) {
|
|
1093
|
+
if (e.cancelable) e.preventDefault();
|
|
1094
|
+
e.stopPropagation();
|
|
1095
|
+
this.throttledPointerMove(e);
|
|
1096
|
+
}
|
|
1097
|
+
onPointerUp(e) {
|
|
1098
|
+
if (e.cancelable) e.preventDefault();
|
|
1099
|
+
this.processPhysicalEvent(e, ACTION_TYPES.MOUSEUP);
|
|
1100
|
+
this.processPhysicalEvent(e, ACTION_TYPES.CLICK);
|
|
1101
|
+
}
|
|
1102
|
+
onPointerCancel(e) {
|
|
1103
|
+
this.processPhysicalEvent(e, ACTION_TYPES.MOUSEUP);
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Convert physical DOM events into internal signals
|
|
1107
|
+
*/
|
|
1108
|
+
processPhysicalEvent(e, type) {
|
|
1109
|
+
const rect = this.getRect();
|
|
1110
|
+
if (!rect) return;
|
|
1111
|
+
const point = {
|
|
1112
|
+
x: pxToPercent(e.clientX - rect.left, rect.width),
|
|
1113
|
+
y: pxToPercent(e.clientY - rect.top, rect.height)
|
|
1114
|
+
};
|
|
1115
|
+
this.handleSignal({
|
|
1116
|
+
targetStageId: this.uid,
|
|
1117
|
+
type,
|
|
1118
|
+
payload: { point, button: e.button }
|
|
1119
|
+
});
|
|
943
1120
|
}
|
|
944
1121
|
// --- ISignalReceiver Implementation ---
|
|
945
1122
|
handleSignal(signal) {
|
|
@@ -953,13 +1130,18 @@ var TargetZoneCore = class extends BaseEntity {
|
|
|
953
1130
|
case ACTION_TYPES.MOUSEMOVE:
|
|
954
1131
|
if (payload.point) {
|
|
955
1132
|
this.updateCursorPosition(payload.point);
|
|
956
|
-
|
|
957
|
-
this.
|
|
1133
|
+
} else if (payload.delta) {
|
|
1134
|
+
this.updateCursorPositionByDelta(payload.delta);
|
|
958
1135
|
}
|
|
1136
|
+
if (this.config.cursorEnabled) this.showCursor();
|
|
1137
|
+
this.executeMouseAction(ACTION_TYPES.POINTERMOVE, payload);
|
|
959
1138
|
break;
|
|
960
1139
|
case ACTION_TYPES.MOUSEDOWN:
|
|
961
1140
|
case ACTION_TYPES.MOUSEUP:
|
|
962
1141
|
case ACTION_TYPES.CLICK:
|
|
1142
|
+
if (payload.point) {
|
|
1143
|
+
this.updateCursorPosition(payload.point);
|
|
1144
|
+
}
|
|
963
1145
|
if (this.config.cursorEnabled) this.showCursor();
|
|
964
1146
|
this.executeMouseAction(
|
|
965
1147
|
type.startsWith(ACTION_TYPES.MOUSE) ? type.replace(ACTION_TYPES.MOUSE, ACTION_TYPES.POINTER) : type,
|
|
@@ -976,12 +1158,13 @@ var TargetZoneCore = class extends BaseEntity {
|
|
|
976
1158
|
* @param payload - Data containing point coordinates or button info.
|
|
977
1159
|
*/
|
|
978
1160
|
executeMouseAction(pointerType, payload) {
|
|
979
|
-
|
|
1161
|
+
const rect = this.getRect();
|
|
1162
|
+
if (!rect) return;
|
|
980
1163
|
if (pointerType === ACTION_TYPES.POINTERDOWN) this.setState({ isPointerDown: true });
|
|
981
1164
|
if (pointerType === ACTION_TYPES.POINTERUP) this.setState({ isPointerDown: false });
|
|
982
1165
|
const target = payload.point || this.state.position;
|
|
983
|
-
const px =
|
|
984
|
-
const py =
|
|
1166
|
+
const px = rect.left + percentToPx(target.x, rect.width);
|
|
1167
|
+
const py = rect.top + percentToPx(target.y, rect.height);
|
|
985
1168
|
dispatchPointerEventAtPos(pointerType, px, py, {
|
|
986
1169
|
button: payload.button ?? 0,
|
|
987
1170
|
buttons: this.state.isPointerDown ? 1 : 0
|
|
@@ -992,9 +1175,10 @@ var TargetZoneCore = class extends BaseEntity {
|
|
|
992
1175
|
* Checks if the target element under the virtual cursor has focus, and reclaims it if lost.
|
|
993
1176
|
*/
|
|
994
1177
|
ensureFocus() {
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
const
|
|
1178
|
+
const rect = this.getRect();
|
|
1179
|
+
if (!rect) return;
|
|
1180
|
+
const px = rect.left + percentToPx(this.state.position.x, rect.width);
|
|
1181
|
+
const py = rect.top + percentToPx(this.state.position.y, rect.height);
|
|
998
1182
|
const target = getDeepElement(px, py);
|
|
999
1183
|
if (!target) return;
|
|
1000
1184
|
if (getDeepActiveElement() !== target) {
|
|
@@ -1015,8 +1199,23 @@ var TargetZoneCore = class extends BaseEntity {
|
|
|
1015
1199
|
* Updates the internal virtual cursor coordinates.
|
|
1016
1200
|
*/
|
|
1017
1201
|
updateCursorPosition(point) {
|
|
1202
|
+
if (isVec2Equal(point, this.state.position)) return;
|
|
1018
1203
|
this.setState({ position: { ...point } });
|
|
1019
1204
|
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Updates the internal virtual cursor coordinates by delta.
|
|
1207
|
+
*/
|
|
1208
|
+
updateCursorPositionByDelta(delta) {
|
|
1209
|
+
if (isVec2Equal(delta, { x: 0, y: 0 })) return;
|
|
1210
|
+
const rect = this.getRect();
|
|
1211
|
+
if (!rect) return;
|
|
1212
|
+
const dxPercent = pxToPercent(delta.x, rect.width);
|
|
1213
|
+
const dyPercent = pxToPercent(delta.y, rect.height);
|
|
1214
|
+
this.updateCursorPosition({
|
|
1215
|
+
x: clamp(this.state.position.x + dxPercent, 0, 100),
|
|
1216
|
+
y: clamp(this.state.position.y + dyPercent, 0, 100)
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1020
1219
|
/**
|
|
1021
1220
|
* Makes the virtual cursor visible and sets a timeout for auto-hiding.
|
|
1022
1221
|
*/
|
|
@@ -1045,26 +1244,164 @@ var TargetZoneCore = class extends BaseEntity {
|
|
|
1045
1244
|
}
|
|
1046
1245
|
};
|
|
1047
1246
|
|
|
1247
|
+
// src/entities/TrackpadCore.ts
|
|
1248
|
+
var GESTURE = {
|
|
1249
|
+
TAP_TIME: 200,
|
|
1250
|
+
// 200ms 以内抬起视为轻点 / Release within 200ms counts as a tap
|
|
1251
|
+
TAP_DISTANCE: 10,
|
|
1252
|
+
// 位移小于 10px 视为点击判定 / Movement within 10px counts as a tap
|
|
1253
|
+
DOUBLE_TAP_GAP: 300
|
|
1254
|
+
// 两次点击间隔小于 300ms 视为双击触发 / Interval within 300ms counts as double-tap
|
|
1255
|
+
};
|
|
1256
|
+
var INITIAL_STATE6 = {
|
|
1257
|
+
isActive: false,
|
|
1258
|
+
isPressed: false,
|
|
1259
|
+
pointerId: null,
|
|
1260
|
+
value: 0
|
|
1261
|
+
};
|
|
1262
|
+
var TrackpadCore = class extends BaseEntity {
|
|
1263
|
+
/**
|
|
1264
|
+
* Creates an instance of TrackpadCore.
|
|
1265
|
+
*
|
|
1266
|
+
* @param uid - Unique entity ID.
|
|
1267
|
+
* @param config - Configuration for the trackpad.
|
|
1268
|
+
*/
|
|
1269
|
+
constructor(uid, config) {
|
|
1270
|
+
super(uid, CMP_TYPES.TRACKPAD, config, INITIAL_STATE6);
|
|
1271
|
+
__publicField(this, "lastPointerPos", { x: 0, y: 0 });
|
|
1272
|
+
__publicField(this, "startTime", 0);
|
|
1273
|
+
__publicField(this, "startPos", { x: 0, y: 0 });
|
|
1274
|
+
// 连击状态追踪 / State tracking for consecutive taps
|
|
1275
|
+
__publicField(this, "lastClickTime", 0);
|
|
1276
|
+
__publicField(this, "isDragMode", false);
|
|
1277
|
+
__publicField(this, "throttledPointerMove");
|
|
1278
|
+
this.throttledPointerMove = createRafThrottler((e) => {
|
|
1279
|
+
this.processPointerMove(e);
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
// --- IPointerHandler Implementation ---
|
|
1283
|
+
onPointerDown(e) {
|
|
1284
|
+
if (e.cancelable) e.preventDefault();
|
|
1285
|
+
e.stopPropagation();
|
|
1286
|
+
e.target.setPointerCapture(e.pointerId);
|
|
1287
|
+
const now = Date.now();
|
|
1288
|
+
this.startTime = now;
|
|
1289
|
+
this.startPos = { x: e.clientX, y: e.clientY };
|
|
1290
|
+
this.lastPointerPos = { x: e.clientX, y: e.clientY };
|
|
1291
|
+
if (now - this.lastClickTime < GESTURE.DOUBLE_TAP_GAP) {
|
|
1292
|
+
this.isDragMode = true;
|
|
1293
|
+
this.sendSignal(ACTION_TYPES.MOUSEDOWN);
|
|
1294
|
+
this.setState({ isPressed: true });
|
|
1295
|
+
}
|
|
1296
|
+
this.setState({ isActive: true, pointerId: e.pointerId });
|
|
1297
|
+
}
|
|
1298
|
+
onPointerMove(e) {
|
|
1299
|
+
if (e.cancelable) e.preventDefault();
|
|
1300
|
+
e.stopPropagation();
|
|
1301
|
+
if (this.state.pointerId !== e.pointerId) return;
|
|
1302
|
+
this.throttledPointerMove(e);
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Internal logic for processing pointer movement.
|
|
1306
|
+
* Calculates displacement and emits relative move signals.
|
|
1307
|
+
*
|
|
1308
|
+
* @param e - The pointer event.
|
|
1309
|
+
*/
|
|
1310
|
+
processPointerMove(e) {
|
|
1311
|
+
const dx = e.clientX - this.lastPointerPos.x;
|
|
1312
|
+
const dy = e.clientY - this.lastPointerPos.y;
|
|
1313
|
+
const rect = this.getRect();
|
|
1314
|
+
if (!rect) return;
|
|
1315
|
+
const deltaX = dx / rect.width * 100 * this.config.sensitivity;
|
|
1316
|
+
const deltaY = dy / rect.height * 100 * this.config.sensitivity;
|
|
1317
|
+
if (Math.abs(dx) > 0 || Math.abs(dy) > 0) {
|
|
1318
|
+
this.sendSignal(ACTION_TYPES.MOUSEMOVE, { delta: { x: deltaX, y: deltaY } });
|
|
1319
|
+
}
|
|
1320
|
+
this.lastPointerPos = { x: e.clientX, y: e.clientY };
|
|
1321
|
+
}
|
|
1322
|
+
onPointerUp(e) {
|
|
1323
|
+
if (this.state.pointerId !== e.pointerId) return;
|
|
1324
|
+
if (e.cancelable) e.preventDefault();
|
|
1325
|
+
const duration = Date.now() - this.startTime;
|
|
1326
|
+
const dist = Math.hypot(e.clientX - this.startPos.x, e.clientY - this.startPos.y);
|
|
1327
|
+
if (this.isDragMode) {
|
|
1328
|
+
this.sendSignal(ACTION_TYPES.MOUSEUP);
|
|
1329
|
+
this.isDragMode = false;
|
|
1330
|
+
} else if (duration < GESTURE.TAP_TIME && dist < GESTURE.TAP_DISTANCE) {
|
|
1331
|
+
this.sendSignal(ACTION_TYPES.CLICK);
|
|
1332
|
+
this.lastClickTime = Date.now();
|
|
1333
|
+
}
|
|
1334
|
+
this.handleRelease(e);
|
|
1335
|
+
}
|
|
1336
|
+
onPointerCancel(e) {
|
|
1337
|
+
this.handleRelease(e);
|
|
1338
|
+
}
|
|
1339
|
+
// --- IResettable Implementation ---
|
|
1340
|
+
reset() {
|
|
1341
|
+
if (this.isDragMode) this.sendSignal(ACTION_TYPES.MOUSEUP);
|
|
1342
|
+
this.isDragMode = false;
|
|
1343
|
+
this.setState(INITIAL_STATE6);
|
|
1344
|
+
}
|
|
1345
|
+
// --- Internal Helpers ---
|
|
1346
|
+
/**
|
|
1347
|
+
* Clean up pointer capture and reset interaction state.
|
|
1348
|
+
*/
|
|
1349
|
+
handleRelease(e) {
|
|
1350
|
+
if (e.target.hasPointerCapture(e.pointerId)) {
|
|
1351
|
+
try {
|
|
1352
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
1353
|
+
} catch (err) {
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
this.setState(INITIAL_STATE6);
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Helper to send signals to the target stage via Registry.
|
|
1360
|
+
*
|
|
1361
|
+
* @param type - Signal action type.
|
|
1362
|
+
* @param extraPayload - Additional data like delta or point.
|
|
1363
|
+
*/
|
|
1364
|
+
sendSignal(type, extraPayload = {}) {
|
|
1365
|
+
const targetId = this.config.targetStageId;
|
|
1366
|
+
if (!targetId) return;
|
|
1367
|
+
const target = Registry.getInstance().getEntity(targetId);
|
|
1368
|
+
if (target && typeof target.handleSignal === "function") {
|
|
1369
|
+
target.handleSignal({
|
|
1370
|
+
targetStageId: targetId,
|
|
1371
|
+
type,
|
|
1372
|
+
payload: {
|
|
1373
|
+
button: 0,
|
|
1374
|
+
// 触摸板操作默认模拟左键 / Trackpad defaults to left button
|
|
1375
|
+
...extraPayload
|
|
1376
|
+
}
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1048
1382
|
// src/index.ts
|
|
1049
1383
|
var OmniPad = {
|
|
1384
|
+
ActionTypes: ACTION_TYPES,
|
|
1050
1385
|
Context: CONTEXT,
|
|
1051
1386
|
Keys: KEYS,
|
|
1052
|
-
Types:
|
|
1387
|
+
Types: CMP_TYPES
|
|
1053
1388
|
};
|
|
1054
1389
|
export {
|
|
1055
1390
|
ACTION_TYPES,
|
|
1056
1391
|
BaseEntity,
|
|
1392
|
+
CMP_TYPES,
|
|
1057
1393
|
CONTEXT,
|
|
1058
1394
|
InputManager,
|
|
1059
1395
|
InputZoneCore,
|
|
1060
1396
|
KEYS,
|
|
1061
1397
|
KeyboardButtonCore,
|
|
1398
|
+
MouseButtonCore,
|
|
1062
1399
|
OmniPad,
|
|
1063
1400
|
Registry,
|
|
1064
1401
|
RootLayerCore,
|
|
1065
1402
|
SimpleEmitter,
|
|
1066
|
-
TYPES,
|
|
1067
1403
|
TargetZoneCore,
|
|
1404
|
+
TrackpadCore,
|
|
1068
1405
|
addVec,
|
|
1069
1406
|
applyAxialDeadzone,
|
|
1070
1407
|
applyRadialDeadzone,
|