@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.js
CHANGED
|
@@ -24,17 +24,19 @@ var index_exports = {};
|
|
|
24
24
|
__export(index_exports, {
|
|
25
25
|
ACTION_TYPES: () => ACTION_TYPES,
|
|
26
26
|
BaseEntity: () => BaseEntity,
|
|
27
|
+
CMP_TYPES: () => CMP_TYPES,
|
|
27
28
|
CONTEXT: () => CONTEXT,
|
|
28
29
|
InputManager: () => InputManager,
|
|
29
30
|
InputZoneCore: () => InputZoneCore,
|
|
30
31
|
KEYS: () => KEYS,
|
|
31
32
|
KeyboardButtonCore: () => KeyboardButtonCore,
|
|
33
|
+
MouseButtonCore: () => MouseButtonCore,
|
|
32
34
|
OmniPad: () => OmniPad,
|
|
33
35
|
Registry: () => Registry,
|
|
34
36
|
RootLayerCore: () => RootLayerCore,
|
|
35
37
|
SimpleEmitter: () => SimpleEmitter,
|
|
36
|
-
TYPES: () => TYPES,
|
|
37
38
|
TargetZoneCore: () => TargetZoneCore,
|
|
39
|
+
TrackpadCore: () => TrackpadCore,
|
|
38
40
|
addVec: () => addVec,
|
|
39
41
|
applyAxialDeadzone: () => applyAxialDeadzone,
|
|
40
42
|
applyRadialDeadzone: () => applyRadialDeadzone,
|
|
@@ -187,7 +189,7 @@ var STANDARD_KEYS = {
|
|
|
187
189
|
var KEYS = STANDARD_KEYS;
|
|
188
190
|
|
|
189
191
|
// src/types/index.ts
|
|
190
|
-
var
|
|
192
|
+
var CMP_TYPES = {
|
|
191
193
|
// --- Zones ---
|
|
192
194
|
/** Area responsible for capturing touches and spawning dynamic widgets */
|
|
193
195
|
INPUT_ZONE: "input-zone",
|
|
@@ -322,14 +324,17 @@ var applyAxialDeadzone = (v, threshold, max) => {
|
|
|
322
324
|
};
|
|
323
325
|
|
|
324
326
|
// src/utils/dom.ts
|
|
325
|
-
var getDeepElement = (x, y) => {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
327
|
+
var getDeepElement = (x, y, ignoreClass = "omnipad-target-zone") => {
|
|
328
|
+
const elements = document.elementsFromPoint(x, y);
|
|
329
|
+
let target = elements.find((el) => !el.classList.contains(ignoreClass));
|
|
330
|
+
if (!target) return null;
|
|
331
|
+
while (target && target.shadowRoot) {
|
|
332
|
+
const nestedElements = target.shadowRoot.elementsFromPoint(x, y);
|
|
333
|
+
const nestedTarget = nestedElements.find((el) => !el.classList.contains(ignoreClass));
|
|
334
|
+
if (!nestedTarget || nestedTarget === target) break;
|
|
335
|
+
target = nestedTarget;
|
|
336
|
+
}
|
|
337
|
+
return target;
|
|
333
338
|
};
|
|
334
339
|
var getDeepActiveElement = () => {
|
|
335
340
|
let el = document.activeElement;
|
|
@@ -373,7 +378,7 @@ var dispatchPointerEventAtPos = (type, x, y, opts = {}) => {
|
|
|
373
378
|
target.dispatchEvent(
|
|
374
379
|
new PointerEvent(type, {
|
|
375
380
|
isPrimary: true,
|
|
376
|
-
pointerId:
|
|
381
|
+
pointerId: 9999,
|
|
377
382
|
pointerType: "mouse",
|
|
378
383
|
// Emulate mouse behavior for Flash MouseOver/Down logic
|
|
379
384
|
...commonProps
|
|
@@ -441,97 +446,6 @@ var resolveLayoutStyle = (layout) => {
|
|
|
441
446
|
return style;
|
|
442
447
|
};
|
|
443
448
|
|
|
444
|
-
// src/utils/emitter.ts
|
|
445
|
-
var SimpleEmitter = class {
|
|
446
|
-
constructor() {
|
|
447
|
-
__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Registers a callback function to be executed whenever data is emitted.
|
|
451
|
-
*
|
|
452
|
-
* @param fn - The callback function.
|
|
453
|
-
* @returns A function that, when called, unsubscribes the listener.
|
|
454
|
-
*/
|
|
455
|
-
subscribe(fn) {
|
|
456
|
-
this.listeners.add(fn);
|
|
457
|
-
return () => this.listeners.delete(fn);
|
|
458
|
-
}
|
|
459
|
-
/**
|
|
460
|
-
* Broadcasts the provided data to all registered listeners.
|
|
461
|
-
* Each listener is executed within a try-catch block to ensure that
|
|
462
|
-
* an error in one subscriber doesn't prevent others from receiving the signal.
|
|
463
|
-
*
|
|
464
|
-
* @param data - The payload to be sent to all subscribers.
|
|
465
|
-
*/
|
|
466
|
-
emit(data) {
|
|
467
|
-
this.listeners.forEach((fn) => {
|
|
468
|
-
try {
|
|
469
|
-
fn(data);
|
|
470
|
-
} catch (error) {
|
|
471
|
-
console.error("[OmniPad-Core] Emitter callback error:", error);
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
/**
|
|
476
|
-
* Removes all listeners and clears the subscription set.
|
|
477
|
-
* Essential for preventing memory leaks when an Entity is destroyed.
|
|
478
|
-
*/
|
|
479
|
-
clear() {
|
|
480
|
-
this.listeners.clear();
|
|
481
|
-
}
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
// src/entities/BaseEntity.ts
|
|
485
|
-
var BaseEntity = class {
|
|
486
|
-
constructor(uid, type, initialConfig, initialState) {
|
|
487
|
-
__publicField(this, "uid");
|
|
488
|
-
__publicField(this, "type");
|
|
489
|
-
__publicField(this, "config");
|
|
490
|
-
__publicField(this, "state");
|
|
491
|
-
__publicField(this, "rect", null);
|
|
492
|
-
// 内部状态发射器,负责处理状态订阅逻辑 / Internal emitter for state subscription logic
|
|
493
|
-
__publicField(this, "stateEmitter", new SimpleEmitter());
|
|
494
|
-
this.uid = uid;
|
|
495
|
-
this.type = type;
|
|
496
|
-
this.config = initialConfig;
|
|
497
|
-
this.state = initialState;
|
|
498
|
-
}
|
|
499
|
-
// --- IObservable Implementation ---
|
|
500
|
-
subscribe(cb) {
|
|
501
|
-
cb(this.state);
|
|
502
|
-
return this.stateEmitter.subscribe(cb);
|
|
503
|
-
}
|
|
504
|
-
// --- State Management ---
|
|
505
|
-
/**
|
|
506
|
-
* Updates the internal state and notifies all subscribers.
|
|
507
|
-
*
|
|
508
|
-
* @param partialState - Partial object containing updated state values.
|
|
509
|
-
*/
|
|
510
|
-
setState(partialState) {
|
|
511
|
-
this.state = { ...this.state, ...partialState };
|
|
512
|
-
this.stateEmitter.emit(this.state);
|
|
513
|
-
}
|
|
514
|
-
// --- Lifecycle ---
|
|
515
|
-
destroy() {
|
|
516
|
-
this.reset();
|
|
517
|
-
this.stateEmitter.clear();
|
|
518
|
-
Registry.getInstance().unregister(this.uid);
|
|
519
|
-
}
|
|
520
|
-
updateRect(rect) {
|
|
521
|
-
this.rect = rect;
|
|
522
|
-
}
|
|
523
|
-
updateConfig(newConfig) {
|
|
524
|
-
this.config = { ...this.config, ...newConfig };
|
|
525
|
-
this.stateEmitter.emit(this.state);
|
|
526
|
-
}
|
|
527
|
-
getState() {
|
|
528
|
-
return this.state;
|
|
529
|
-
}
|
|
530
|
-
getConfig() {
|
|
531
|
-
return this.config;
|
|
532
|
-
}
|
|
533
|
-
};
|
|
534
|
-
|
|
535
449
|
// src/registry/index.ts
|
|
536
450
|
var import_meta = {};
|
|
537
451
|
var GLOBAL_REGISTRY_KEY = /* @__PURE__ */ Symbol.for("omnipad.registry.instance");
|
|
@@ -587,8 +501,9 @@ var Registry = class _Registry {
|
|
|
587
501
|
}
|
|
588
502
|
const parentMap = /* @__PURE__ */ new Map();
|
|
589
503
|
all.forEach((entity) => {
|
|
590
|
-
|
|
591
|
-
|
|
504
|
+
const e = entity;
|
|
505
|
+
if (typeof e.getConfig === "function") {
|
|
506
|
+
const config = e.getConfig();
|
|
592
507
|
if (config.parentId) {
|
|
593
508
|
if (!parentMap.has(config.parentId)) {
|
|
594
509
|
parentMap.set(config.parentId, []);
|
|
@@ -756,6 +671,64 @@ function exportProfile(meta, rootUid) {
|
|
|
756
671
|
};
|
|
757
672
|
}
|
|
758
673
|
|
|
674
|
+
// src/utils/emitter.ts
|
|
675
|
+
var SimpleEmitter = class {
|
|
676
|
+
constructor() {
|
|
677
|
+
__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Registers a callback function to be executed whenever data is emitted.
|
|
681
|
+
*
|
|
682
|
+
* @param fn - The callback function.
|
|
683
|
+
* @returns A function that, when called, unsubscribes the listener.
|
|
684
|
+
*/
|
|
685
|
+
subscribe(fn) {
|
|
686
|
+
this.listeners.add(fn);
|
|
687
|
+
return () => this.listeners.delete(fn);
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Broadcasts the provided data to all registered listeners.
|
|
691
|
+
* Each listener is executed within a try-catch block to ensure that
|
|
692
|
+
* an error in one subscriber doesn't prevent others from receiving the signal.
|
|
693
|
+
*
|
|
694
|
+
* @param data - The payload to be sent to all subscribers.
|
|
695
|
+
*/
|
|
696
|
+
emit(data) {
|
|
697
|
+
this.listeners.forEach((fn) => {
|
|
698
|
+
try {
|
|
699
|
+
fn(data);
|
|
700
|
+
} catch (error) {
|
|
701
|
+
console.error("[OmniPad-Core] Emitter callback error:", error);
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Removes all listeners and clears the subscription set.
|
|
707
|
+
* Essential for preventing memory leaks when an Entity is destroyed.
|
|
708
|
+
*/
|
|
709
|
+
clear() {
|
|
710
|
+
this.listeners.clear();
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
// src/utils/performance.ts
|
|
715
|
+
function createRafThrottler(callback) {
|
|
716
|
+
let ticking = false;
|
|
717
|
+
let latestPayload = null;
|
|
718
|
+
return function(payload) {
|
|
719
|
+
latestPayload = payload;
|
|
720
|
+
if (!ticking) {
|
|
721
|
+
ticking = true;
|
|
722
|
+
window.requestAnimationFrame(() => {
|
|
723
|
+
if (latestPayload !== null) {
|
|
724
|
+
callback(latestPayload);
|
|
725
|
+
}
|
|
726
|
+
ticking = false;
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
|
|
759
732
|
// src/imputManager/index.ts
|
|
760
733
|
var import_meta2 = {};
|
|
761
734
|
var INPUT_MANAGER_KEY = /* @__PURE__ */ Symbol.for("omnipad.input_manager.instance");
|
|
@@ -763,6 +736,8 @@ var InputManager = class _InputManager {
|
|
|
763
736
|
constructor() {
|
|
764
737
|
/** Internal flag to prevent multiple event registrations */
|
|
765
738
|
__publicField(this, "_isListening", false);
|
|
739
|
+
/** A throttled version of the reset logic */
|
|
740
|
+
__publicField(this, "throttledReset");
|
|
766
741
|
/**
|
|
767
742
|
* Manually triggers a system-wide input reset via Registry.
|
|
768
743
|
*/
|
|
@@ -772,6 +747,20 @@ var InputManager = class _InputManager {
|
|
|
772
747
|
}
|
|
773
748
|
Registry.getInstance().resetAll();
|
|
774
749
|
});
|
|
750
|
+
__publicField(this, "handleResizeReset", () => {
|
|
751
|
+
this.throttledReset(null);
|
|
752
|
+
});
|
|
753
|
+
__publicField(this, "handleBlurReset", () => {
|
|
754
|
+
this.handleGlobalReset();
|
|
755
|
+
});
|
|
756
|
+
__publicField(this, "handleVisibilityChangeReset", () => {
|
|
757
|
+
if (document.visibilityState === "hidden") {
|
|
758
|
+
this.handleGlobalReset();
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
this.throttledReset = createRafThrottler(() => {
|
|
762
|
+
this.handleGlobalReset();
|
|
763
|
+
});
|
|
775
764
|
}
|
|
776
765
|
/**
|
|
777
766
|
* Retrieves the global instance of the InputManager.
|
|
@@ -790,8 +779,9 @@ var InputManager = class _InputManager {
|
|
|
790
779
|
*/
|
|
791
780
|
init() {
|
|
792
781
|
if (this._isListening) return;
|
|
793
|
-
window.addEventListener("resize", this.
|
|
794
|
-
window.addEventListener("blur", this.
|
|
782
|
+
window.addEventListener("resize", this.handleResizeReset);
|
|
783
|
+
window.addEventListener("blur", this.handleBlurReset);
|
|
784
|
+
document.addEventListener("visibilitychange", this.handleVisibilityChangeReset);
|
|
795
785
|
this._isListening = true;
|
|
796
786
|
if (import_meta2.env?.DEV) {
|
|
797
787
|
console.log("[OmniPad-Core] Global InputManager monitoring started.");
|
|
@@ -825,12 +815,70 @@ var InputManager = class _InputManager {
|
|
|
825
815
|
* Detaches all global listeners.
|
|
826
816
|
*/
|
|
827
817
|
destroy() {
|
|
828
|
-
window.removeEventListener("resize", this.
|
|
829
|
-
window.removeEventListener("blur", this.
|
|
818
|
+
window.removeEventListener("resize", this.handleResizeReset);
|
|
819
|
+
window.removeEventListener("blur", this.handleBlurReset);
|
|
820
|
+
window.removeEventListener("visibilitychange", this.handleVisibilityChangeReset);
|
|
830
821
|
this._isListening = false;
|
|
831
822
|
}
|
|
832
823
|
};
|
|
833
824
|
|
|
825
|
+
// src/entities/BaseEntity.ts
|
|
826
|
+
var BaseEntity = class {
|
|
827
|
+
constructor(uid, type, initialConfig, initialState) {
|
|
828
|
+
__publicField(this, "uid");
|
|
829
|
+
__publicField(this, "type");
|
|
830
|
+
__publicField(this, "config");
|
|
831
|
+
__publicField(this, "state");
|
|
832
|
+
__publicField(this, "rectProvider", null);
|
|
833
|
+
// 内部状态发射器,负责处理状态订阅逻辑 / Internal emitter for state subscription logic
|
|
834
|
+
__publicField(this, "stateEmitter", new SimpleEmitter());
|
|
835
|
+
this.uid = uid;
|
|
836
|
+
this.type = type;
|
|
837
|
+
this.config = initialConfig;
|
|
838
|
+
this.state = initialState;
|
|
839
|
+
}
|
|
840
|
+
// --- IObservable Implementation ---
|
|
841
|
+
subscribe(cb) {
|
|
842
|
+
cb(this.state);
|
|
843
|
+
return this.stateEmitter.subscribe(cb);
|
|
844
|
+
}
|
|
845
|
+
// --- State Management ---
|
|
846
|
+
/**
|
|
847
|
+
* Updates the internal state and notifies all subscribers.
|
|
848
|
+
*
|
|
849
|
+
* @param partialState - Partial object containing updated state values.
|
|
850
|
+
*/
|
|
851
|
+
setState(partialState) {
|
|
852
|
+
this.state = { ...this.state, ...partialState };
|
|
853
|
+
this.stateEmitter.emit(this.state);
|
|
854
|
+
}
|
|
855
|
+
// --- Lifecycle ---
|
|
856
|
+
destroy() {
|
|
857
|
+
this.reset();
|
|
858
|
+
this.stateEmitter.clear();
|
|
859
|
+
Registry.getInstance().unregister(this.uid);
|
|
860
|
+
}
|
|
861
|
+
bindRectProvider(provider) {
|
|
862
|
+
this.rectProvider = provider;
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Called when triggering interactions, this subclass fetches the latest boundaries in real time.
|
|
866
|
+
*/
|
|
867
|
+
getRect() {
|
|
868
|
+
return this.rectProvider ? this.rectProvider() : null;
|
|
869
|
+
}
|
|
870
|
+
updateConfig(newConfig) {
|
|
871
|
+
this.config = { ...this.config, ...newConfig };
|
|
872
|
+
this.stateEmitter.emit(this.state);
|
|
873
|
+
}
|
|
874
|
+
getState() {
|
|
875
|
+
return this.state;
|
|
876
|
+
}
|
|
877
|
+
getConfig() {
|
|
878
|
+
return this.config;
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
|
|
834
882
|
// src/entities/InputZoneCore.ts
|
|
835
883
|
var INITIAL_STATE = {
|
|
836
884
|
isDynamicActive: false,
|
|
@@ -839,12 +887,13 @@ var INITIAL_STATE = {
|
|
|
839
887
|
};
|
|
840
888
|
var InputZoneCore = class extends BaseEntity {
|
|
841
889
|
constructor(uid, config) {
|
|
842
|
-
super(uid,
|
|
890
|
+
super(uid, CMP_TYPES.INPUT_ZONE, config, INITIAL_STATE);
|
|
843
891
|
}
|
|
844
892
|
onPointerDown(e) {
|
|
845
893
|
if (this.state.isDynamicActive) return;
|
|
846
894
|
if (e.target !== e.currentTarget) return;
|
|
847
895
|
if (e.cancelable) e.preventDefault();
|
|
896
|
+
e.stopPropagation();
|
|
848
897
|
const pos = this.calculateRelativePosition(e.clientX, e.clientY);
|
|
849
898
|
this.setState({
|
|
850
899
|
isDynamicActive: true,
|
|
@@ -856,6 +905,7 @@ var InputZoneCore = class extends BaseEntity {
|
|
|
856
905
|
if (!this.state.isDynamicActive || e.pointerId !== this.state.dynamicPointerId) return;
|
|
857
906
|
}
|
|
858
907
|
onPointerUp(e) {
|
|
908
|
+
if (e.cancelable) e.preventDefault();
|
|
859
909
|
this.handleRelease(e);
|
|
860
910
|
}
|
|
861
911
|
onPointerCancel(e) {
|
|
@@ -881,10 +931,11 @@ var InputZoneCore = class extends BaseEntity {
|
|
|
881
931
|
* Converts viewport pixels to percentage coordinates relative to the zone.
|
|
882
932
|
*/
|
|
883
933
|
calculateRelativePosition(clientX, clientY) {
|
|
884
|
-
|
|
934
|
+
const rect = this.getRect();
|
|
935
|
+
if (!rect) return { x: 0, y: 0 };
|
|
885
936
|
return {
|
|
886
|
-
x: pxToPercent(clientX -
|
|
887
|
-
y: pxToPercent(clientY -
|
|
937
|
+
x: pxToPercent(clientX - rect.left, rect.width),
|
|
938
|
+
y: pxToPercent(clientY - rect.top, rect.height)
|
|
888
939
|
};
|
|
889
940
|
}
|
|
890
941
|
/**
|
|
@@ -917,11 +968,12 @@ var KeyboardButtonCore = class extends BaseEntity {
|
|
|
917
968
|
* @param config - The flat configuration object for the button.
|
|
918
969
|
*/
|
|
919
970
|
constructor(uid, config) {
|
|
920
|
-
super(uid,
|
|
971
|
+
super(uid, CMP_TYPES.KEYBOARD_BUTTON, config, INITIAL_STATE2);
|
|
921
972
|
}
|
|
922
973
|
// --- IPointerHandler Implementation ---
|
|
923
974
|
onPointerDown(e) {
|
|
924
975
|
if (e.cancelable) e.preventDefault();
|
|
976
|
+
e.stopPropagation();
|
|
925
977
|
e.target.setPointerCapture(e.pointerId);
|
|
926
978
|
this.setState({
|
|
927
979
|
isActive: true,
|
|
@@ -976,7 +1028,7 @@ var KeyboardButtonCore = class extends BaseEntity {
|
|
|
976
1028
|
target.handleSignal(signal);
|
|
977
1029
|
} else {
|
|
978
1030
|
if (import_meta3.env?.DEV) {
|
|
979
|
-
console.warn(`[OmniPad-Core]
|
|
1031
|
+
console.warn(`[OmniPad-Core] KeyboardButton ${this.uid} target not found: ${targetId}`);
|
|
980
1032
|
}
|
|
981
1033
|
}
|
|
982
1034
|
}
|
|
@@ -989,20 +1041,109 @@ var KeyboardButtonCore = class extends BaseEntity {
|
|
|
989
1041
|
}
|
|
990
1042
|
};
|
|
991
1043
|
|
|
992
|
-
// src/entities/
|
|
1044
|
+
// src/entities/MouseButtonCore.ts
|
|
1045
|
+
var import_meta4 = {};
|
|
993
1046
|
var INITIAL_STATE3 = {
|
|
1047
|
+
isActive: false,
|
|
1048
|
+
isPressed: false,
|
|
1049
|
+
pointerId: null,
|
|
1050
|
+
value: 0
|
|
1051
|
+
};
|
|
1052
|
+
var MouseButtonCore = class extends BaseEntity {
|
|
1053
|
+
constructor(uid, config) {
|
|
1054
|
+
super(uid, CMP_TYPES.MOUSE_BUTTON, config, INITIAL_STATE3);
|
|
1055
|
+
}
|
|
1056
|
+
// --- IPointerHandler Implementation ---
|
|
1057
|
+
onPointerDown(e) {
|
|
1058
|
+
if (e.cancelable) e.preventDefault();
|
|
1059
|
+
e.stopPropagation();
|
|
1060
|
+
e.target.setPointerCapture(e.pointerId);
|
|
1061
|
+
this.setState({
|
|
1062
|
+
isActive: true,
|
|
1063
|
+
isPressed: true,
|
|
1064
|
+
pointerId: e.pointerId
|
|
1065
|
+
});
|
|
1066
|
+
this.sendInputSignal(ACTION_TYPES.MOUSEDOWN);
|
|
1067
|
+
}
|
|
1068
|
+
onPointerUp(e) {
|
|
1069
|
+
if (e.cancelable) e.preventDefault();
|
|
1070
|
+
this.handleRelease(e, true);
|
|
1071
|
+
}
|
|
1072
|
+
onPointerCancel(e) {
|
|
1073
|
+
this.handleRelease(e, false);
|
|
1074
|
+
}
|
|
1075
|
+
onPointerMove(e) {
|
|
1076
|
+
if (e.cancelable) e.preventDefault();
|
|
1077
|
+
}
|
|
1078
|
+
// --- Internal Logic ---
|
|
1079
|
+
/**
|
|
1080
|
+
* Handles the release of the button.
|
|
1081
|
+
*
|
|
1082
|
+
* @param e - The pointer event.
|
|
1083
|
+
* @param isNormalRelease - If true, a 'click' event will also be dispatched.
|
|
1084
|
+
*/
|
|
1085
|
+
handleRelease(e, isNormalRelease) {
|
|
1086
|
+
const isCancelEvent = e.type === "pointercancel" || e.type === "lostpointercapture";
|
|
1087
|
+
if (!isCancelEvent && this.state.pointerId !== e.pointerId) return;
|
|
1088
|
+
if (e.target.hasPointerCapture(e.pointerId)) {
|
|
1089
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
1090
|
+
}
|
|
1091
|
+
this.setState(INITIAL_STATE3);
|
|
1092
|
+
this.sendInputSignal(ACTION_TYPES.MOUSEUP);
|
|
1093
|
+
if (isNormalRelease) {
|
|
1094
|
+
this.sendInputSignal(ACTION_TYPES.CLICK);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Dispatches input signals to the registered target stage.
|
|
1099
|
+
*
|
|
1100
|
+
* @param type - The action type (mousedown, mouseup, or click).
|
|
1101
|
+
*/
|
|
1102
|
+
sendInputSignal(type) {
|
|
1103
|
+
const targetId = this.config.targetStageId;
|
|
1104
|
+
if (!targetId) return;
|
|
1105
|
+
const target = Registry.getInstance().getEntity(targetId);
|
|
1106
|
+
if (target && typeof target.handleSignal === "function") {
|
|
1107
|
+
const signal = {
|
|
1108
|
+
targetStageId: targetId,
|
|
1109
|
+
type,
|
|
1110
|
+
payload: {
|
|
1111
|
+
// 传递配置中的鼠标按键索引 (0:左键, 1:中键, 2:右键)
|
|
1112
|
+
button: this.config.button ?? 0,
|
|
1113
|
+
// 如果配置了固定坐标,一并传递给 TargetZone
|
|
1114
|
+
point: this.config.fixedPoint
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
target.handleSignal(signal);
|
|
1118
|
+
} else {
|
|
1119
|
+
if (import_meta4.env?.DEV) {
|
|
1120
|
+
console.warn(`[OmniPad-Core] MouseButton ${this.uid} target not found: ${targetId}`);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
// --- IResettable Implementation ---
|
|
1125
|
+
reset() {
|
|
1126
|
+
if (this.state.isPressed) {
|
|
1127
|
+
this.sendInputSignal(ACTION_TYPES.MOUSEUP);
|
|
1128
|
+
}
|
|
1129
|
+
this.setState(INITIAL_STATE3);
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1133
|
+
// src/entities/RootLayerCore.ts
|
|
1134
|
+
var INITIAL_STATE4 = {
|
|
994
1135
|
isHighlighted: false
|
|
995
1136
|
};
|
|
996
1137
|
var RootLayerCore = class extends BaseEntity {
|
|
997
1138
|
constructor(uid, config) {
|
|
998
|
-
super(uid,
|
|
1139
|
+
super(uid, CMP_TYPES.ROOT_LAYER, config, INITIAL_STATE4);
|
|
999
1140
|
}
|
|
1000
1141
|
reset() {
|
|
1001
1142
|
}
|
|
1002
1143
|
};
|
|
1003
1144
|
|
|
1004
1145
|
// src/entities/TargetZoneCore.ts
|
|
1005
|
-
var
|
|
1146
|
+
var INITIAL_STATE5 = {
|
|
1006
1147
|
position: { x: 50, y: 50 },
|
|
1007
1148
|
isVisible: false,
|
|
1008
1149
|
isPointerDown: false,
|
|
@@ -1010,9 +1151,48 @@ var INITIAL_STATE4 = {
|
|
|
1010
1151
|
};
|
|
1011
1152
|
var TargetZoneCore = class extends BaseEntity {
|
|
1012
1153
|
constructor(uid, config) {
|
|
1013
|
-
super(uid,
|
|
1154
|
+
super(uid, CMP_TYPES.TARGET_ZONE, config, INITIAL_STATE5);
|
|
1014
1155
|
__publicField(this, "hideTimer", null);
|
|
1015
1156
|
__publicField(this, "focusFeedbackTimer", null);
|
|
1157
|
+
__publicField(this, "throttledPointerMove");
|
|
1158
|
+
this.throttledPointerMove = createRafThrottler((e) => {
|
|
1159
|
+
this.processPhysicalEvent(e, ACTION_TYPES.MOUSEMOVE);
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
// --- IPointerHandler Implementation ---
|
|
1163
|
+
onPointerDown(e) {
|
|
1164
|
+
if (e.cancelable) e.preventDefault();
|
|
1165
|
+
e.stopPropagation();
|
|
1166
|
+
this.processPhysicalEvent(e, ACTION_TYPES.MOUSEDOWN);
|
|
1167
|
+
}
|
|
1168
|
+
onPointerMove(e) {
|
|
1169
|
+
if (e.cancelable) e.preventDefault();
|
|
1170
|
+
e.stopPropagation();
|
|
1171
|
+
this.throttledPointerMove(e);
|
|
1172
|
+
}
|
|
1173
|
+
onPointerUp(e) {
|
|
1174
|
+
if (e.cancelable) e.preventDefault();
|
|
1175
|
+
this.processPhysicalEvent(e, ACTION_TYPES.MOUSEUP);
|
|
1176
|
+
this.processPhysicalEvent(e, ACTION_TYPES.CLICK);
|
|
1177
|
+
}
|
|
1178
|
+
onPointerCancel(e) {
|
|
1179
|
+
this.processPhysicalEvent(e, ACTION_TYPES.MOUSEUP);
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Convert physical DOM events into internal signals
|
|
1183
|
+
*/
|
|
1184
|
+
processPhysicalEvent(e, type) {
|
|
1185
|
+
const rect = this.getRect();
|
|
1186
|
+
if (!rect) return;
|
|
1187
|
+
const point = {
|
|
1188
|
+
x: pxToPercent(e.clientX - rect.left, rect.width),
|
|
1189
|
+
y: pxToPercent(e.clientY - rect.top, rect.height)
|
|
1190
|
+
};
|
|
1191
|
+
this.handleSignal({
|
|
1192
|
+
targetStageId: this.uid,
|
|
1193
|
+
type,
|
|
1194
|
+
payload: { point, button: e.button }
|
|
1195
|
+
});
|
|
1016
1196
|
}
|
|
1017
1197
|
// --- ISignalReceiver Implementation ---
|
|
1018
1198
|
handleSignal(signal) {
|
|
@@ -1026,13 +1206,18 @@ var TargetZoneCore = class extends BaseEntity {
|
|
|
1026
1206
|
case ACTION_TYPES.MOUSEMOVE:
|
|
1027
1207
|
if (payload.point) {
|
|
1028
1208
|
this.updateCursorPosition(payload.point);
|
|
1029
|
-
|
|
1030
|
-
this.
|
|
1209
|
+
} else if (payload.delta) {
|
|
1210
|
+
this.updateCursorPositionByDelta(payload.delta);
|
|
1031
1211
|
}
|
|
1212
|
+
if (this.config.cursorEnabled) this.showCursor();
|
|
1213
|
+
this.executeMouseAction(ACTION_TYPES.POINTERMOVE, payload);
|
|
1032
1214
|
break;
|
|
1033
1215
|
case ACTION_TYPES.MOUSEDOWN:
|
|
1034
1216
|
case ACTION_TYPES.MOUSEUP:
|
|
1035
1217
|
case ACTION_TYPES.CLICK:
|
|
1218
|
+
if (payload.point) {
|
|
1219
|
+
this.updateCursorPosition(payload.point);
|
|
1220
|
+
}
|
|
1036
1221
|
if (this.config.cursorEnabled) this.showCursor();
|
|
1037
1222
|
this.executeMouseAction(
|
|
1038
1223
|
type.startsWith(ACTION_TYPES.MOUSE) ? type.replace(ACTION_TYPES.MOUSE, ACTION_TYPES.POINTER) : type,
|
|
@@ -1049,12 +1234,13 @@ var TargetZoneCore = class extends BaseEntity {
|
|
|
1049
1234
|
* @param payload - Data containing point coordinates or button info.
|
|
1050
1235
|
*/
|
|
1051
1236
|
executeMouseAction(pointerType, payload) {
|
|
1052
|
-
|
|
1237
|
+
const rect = this.getRect();
|
|
1238
|
+
if (!rect) return;
|
|
1053
1239
|
if (pointerType === ACTION_TYPES.POINTERDOWN) this.setState({ isPointerDown: true });
|
|
1054
1240
|
if (pointerType === ACTION_TYPES.POINTERUP) this.setState({ isPointerDown: false });
|
|
1055
1241
|
const target = payload.point || this.state.position;
|
|
1056
|
-
const px =
|
|
1057
|
-
const py =
|
|
1242
|
+
const px = rect.left + percentToPx(target.x, rect.width);
|
|
1243
|
+
const py = rect.top + percentToPx(target.y, rect.height);
|
|
1058
1244
|
dispatchPointerEventAtPos(pointerType, px, py, {
|
|
1059
1245
|
button: payload.button ?? 0,
|
|
1060
1246
|
buttons: this.state.isPointerDown ? 1 : 0
|
|
@@ -1065,9 +1251,10 @@ var TargetZoneCore = class extends BaseEntity {
|
|
|
1065
1251
|
* Checks if the target element under the virtual cursor has focus, and reclaims it if lost.
|
|
1066
1252
|
*/
|
|
1067
1253
|
ensureFocus() {
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
const
|
|
1254
|
+
const rect = this.getRect();
|
|
1255
|
+
if (!rect) return;
|
|
1256
|
+
const px = rect.left + percentToPx(this.state.position.x, rect.width);
|
|
1257
|
+
const py = rect.top + percentToPx(this.state.position.y, rect.height);
|
|
1071
1258
|
const target = getDeepElement(px, py);
|
|
1072
1259
|
if (!target) return;
|
|
1073
1260
|
if (getDeepActiveElement() !== target) {
|
|
@@ -1088,8 +1275,23 @@ var TargetZoneCore = class extends BaseEntity {
|
|
|
1088
1275
|
* Updates the internal virtual cursor coordinates.
|
|
1089
1276
|
*/
|
|
1090
1277
|
updateCursorPosition(point) {
|
|
1278
|
+
if (isVec2Equal(point, this.state.position)) return;
|
|
1091
1279
|
this.setState({ position: { ...point } });
|
|
1092
1280
|
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Updates the internal virtual cursor coordinates by delta.
|
|
1283
|
+
*/
|
|
1284
|
+
updateCursorPositionByDelta(delta) {
|
|
1285
|
+
if (isVec2Equal(delta, { x: 0, y: 0 })) return;
|
|
1286
|
+
const rect = this.getRect();
|
|
1287
|
+
if (!rect) return;
|
|
1288
|
+
const dxPercent = pxToPercent(delta.x, rect.width);
|
|
1289
|
+
const dyPercent = pxToPercent(delta.y, rect.height);
|
|
1290
|
+
this.updateCursorPosition({
|
|
1291
|
+
x: clamp(this.state.position.x + dxPercent, 0, 100),
|
|
1292
|
+
y: clamp(this.state.position.y + dyPercent, 0, 100)
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1093
1295
|
/**
|
|
1094
1296
|
* Makes the virtual cursor visible and sets a timeout for auto-hiding.
|
|
1095
1297
|
*/
|
|
@@ -1118,27 +1320,165 @@ var TargetZoneCore = class extends BaseEntity {
|
|
|
1118
1320
|
}
|
|
1119
1321
|
};
|
|
1120
1322
|
|
|
1323
|
+
// src/entities/TrackpadCore.ts
|
|
1324
|
+
var GESTURE = {
|
|
1325
|
+
TAP_TIME: 200,
|
|
1326
|
+
// 200ms 以内抬起视为轻点 / Release within 200ms counts as a tap
|
|
1327
|
+
TAP_DISTANCE: 10,
|
|
1328
|
+
// 位移小于 10px 视为点击判定 / Movement within 10px counts as a tap
|
|
1329
|
+
DOUBLE_TAP_GAP: 300
|
|
1330
|
+
// 两次点击间隔小于 300ms 视为双击触发 / Interval within 300ms counts as double-tap
|
|
1331
|
+
};
|
|
1332
|
+
var INITIAL_STATE6 = {
|
|
1333
|
+
isActive: false,
|
|
1334
|
+
isPressed: false,
|
|
1335
|
+
pointerId: null,
|
|
1336
|
+
value: 0
|
|
1337
|
+
};
|
|
1338
|
+
var TrackpadCore = class extends BaseEntity {
|
|
1339
|
+
/**
|
|
1340
|
+
* Creates an instance of TrackpadCore.
|
|
1341
|
+
*
|
|
1342
|
+
* @param uid - Unique entity ID.
|
|
1343
|
+
* @param config - Configuration for the trackpad.
|
|
1344
|
+
*/
|
|
1345
|
+
constructor(uid, config) {
|
|
1346
|
+
super(uid, CMP_TYPES.TRACKPAD, config, INITIAL_STATE6);
|
|
1347
|
+
__publicField(this, "lastPointerPos", { x: 0, y: 0 });
|
|
1348
|
+
__publicField(this, "startTime", 0);
|
|
1349
|
+
__publicField(this, "startPos", { x: 0, y: 0 });
|
|
1350
|
+
// 连击状态追踪 / State tracking for consecutive taps
|
|
1351
|
+
__publicField(this, "lastClickTime", 0);
|
|
1352
|
+
__publicField(this, "isDragMode", false);
|
|
1353
|
+
__publicField(this, "throttledPointerMove");
|
|
1354
|
+
this.throttledPointerMove = createRafThrottler((e) => {
|
|
1355
|
+
this.processPointerMove(e);
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
// --- IPointerHandler Implementation ---
|
|
1359
|
+
onPointerDown(e) {
|
|
1360
|
+
if (e.cancelable) e.preventDefault();
|
|
1361
|
+
e.stopPropagation();
|
|
1362
|
+
e.target.setPointerCapture(e.pointerId);
|
|
1363
|
+
const now = Date.now();
|
|
1364
|
+
this.startTime = now;
|
|
1365
|
+
this.startPos = { x: e.clientX, y: e.clientY };
|
|
1366
|
+
this.lastPointerPos = { x: e.clientX, y: e.clientY };
|
|
1367
|
+
if (now - this.lastClickTime < GESTURE.DOUBLE_TAP_GAP) {
|
|
1368
|
+
this.isDragMode = true;
|
|
1369
|
+
this.sendSignal(ACTION_TYPES.MOUSEDOWN);
|
|
1370
|
+
this.setState({ isPressed: true });
|
|
1371
|
+
}
|
|
1372
|
+
this.setState({ isActive: true, pointerId: e.pointerId });
|
|
1373
|
+
}
|
|
1374
|
+
onPointerMove(e) {
|
|
1375
|
+
if (e.cancelable) e.preventDefault();
|
|
1376
|
+
e.stopPropagation();
|
|
1377
|
+
if (this.state.pointerId !== e.pointerId) return;
|
|
1378
|
+
this.throttledPointerMove(e);
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Internal logic for processing pointer movement.
|
|
1382
|
+
* Calculates displacement and emits relative move signals.
|
|
1383
|
+
*
|
|
1384
|
+
* @param e - The pointer event.
|
|
1385
|
+
*/
|
|
1386
|
+
processPointerMove(e) {
|
|
1387
|
+
const dx = e.clientX - this.lastPointerPos.x;
|
|
1388
|
+
const dy = e.clientY - this.lastPointerPos.y;
|
|
1389
|
+
const rect = this.getRect();
|
|
1390
|
+
if (!rect) return;
|
|
1391
|
+
const deltaX = dx / rect.width * 100 * this.config.sensitivity;
|
|
1392
|
+
const deltaY = dy / rect.height * 100 * this.config.sensitivity;
|
|
1393
|
+
if (Math.abs(dx) > 0 || Math.abs(dy) > 0) {
|
|
1394
|
+
this.sendSignal(ACTION_TYPES.MOUSEMOVE, { delta: { x: deltaX, y: deltaY } });
|
|
1395
|
+
}
|
|
1396
|
+
this.lastPointerPos = { x: e.clientX, y: e.clientY };
|
|
1397
|
+
}
|
|
1398
|
+
onPointerUp(e) {
|
|
1399
|
+
if (this.state.pointerId !== e.pointerId) return;
|
|
1400
|
+
if (e.cancelable) e.preventDefault();
|
|
1401
|
+
const duration = Date.now() - this.startTime;
|
|
1402
|
+
const dist = Math.hypot(e.clientX - this.startPos.x, e.clientY - this.startPos.y);
|
|
1403
|
+
if (this.isDragMode) {
|
|
1404
|
+
this.sendSignal(ACTION_TYPES.MOUSEUP);
|
|
1405
|
+
this.isDragMode = false;
|
|
1406
|
+
} else if (duration < GESTURE.TAP_TIME && dist < GESTURE.TAP_DISTANCE) {
|
|
1407
|
+
this.sendSignal(ACTION_TYPES.CLICK);
|
|
1408
|
+
this.lastClickTime = Date.now();
|
|
1409
|
+
}
|
|
1410
|
+
this.handleRelease(e);
|
|
1411
|
+
}
|
|
1412
|
+
onPointerCancel(e) {
|
|
1413
|
+
this.handleRelease(e);
|
|
1414
|
+
}
|
|
1415
|
+
// --- IResettable Implementation ---
|
|
1416
|
+
reset() {
|
|
1417
|
+
if (this.isDragMode) this.sendSignal(ACTION_TYPES.MOUSEUP);
|
|
1418
|
+
this.isDragMode = false;
|
|
1419
|
+
this.setState(INITIAL_STATE6);
|
|
1420
|
+
}
|
|
1421
|
+
// --- Internal Helpers ---
|
|
1422
|
+
/**
|
|
1423
|
+
* Clean up pointer capture and reset interaction state.
|
|
1424
|
+
*/
|
|
1425
|
+
handleRelease(e) {
|
|
1426
|
+
if (e.target.hasPointerCapture(e.pointerId)) {
|
|
1427
|
+
try {
|
|
1428
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
1429
|
+
} catch (err) {
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
this.setState(INITIAL_STATE6);
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Helper to send signals to the target stage via Registry.
|
|
1436
|
+
*
|
|
1437
|
+
* @param type - Signal action type.
|
|
1438
|
+
* @param extraPayload - Additional data like delta or point.
|
|
1439
|
+
*/
|
|
1440
|
+
sendSignal(type, extraPayload = {}) {
|
|
1441
|
+
const targetId = this.config.targetStageId;
|
|
1442
|
+
if (!targetId) return;
|
|
1443
|
+
const target = Registry.getInstance().getEntity(targetId);
|
|
1444
|
+
if (target && typeof target.handleSignal === "function") {
|
|
1445
|
+
target.handleSignal({
|
|
1446
|
+
targetStageId: targetId,
|
|
1447
|
+
type,
|
|
1448
|
+
payload: {
|
|
1449
|
+
button: 0,
|
|
1450
|
+
// 触摸板操作默认模拟左键 / Trackpad defaults to left button
|
|
1451
|
+
...extraPayload
|
|
1452
|
+
}
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1457
|
+
|
|
1121
1458
|
// src/index.ts
|
|
1122
1459
|
var OmniPad = {
|
|
1460
|
+
ActionTypes: ACTION_TYPES,
|
|
1123
1461
|
Context: CONTEXT,
|
|
1124
1462
|
Keys: KEYS,
|
|
1125
|
-
Types:
|
|
1463
|
+
Types: CMP_TYPES
|
|
1126
1464
|
};
|
|
1127
1465
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1128
1466
|
0 && (module.exports = {
|
|
1129
1467
|
ACTION_TYPES,
|
|
1130
1468
|
BaseEntity,
|
|
1469
|
+
CMP_TYPES,
|
|
1131
1470
|
CONTEXT,
|
|
1132
1471
|
InputManager,
|
|
1133
1472
|
InputZoneCore,
|
|
1134
1473
|
KEYS,
|
|
1135
1474
|
KeyboardButtonCore,
|
|
1475
|
+
MouseButtonCore,
|
|
1136
1476
|
OmniPad,
|
|
1137
1477
|
Registry,
|
|
1138
1478
|
RootLayerCore,
|
|
1139
1479
|
SimpleEmitter,
|
|
1140
|
-
TYPES,
|
|
1141
1480
|
TargetZoneCore,
|
|
1481
|
+
TrackpadCore,
|
|
1142
1482
|
addVec,
|
|
1143
1483
|
applyAxialDeadzone,
|
|
1144
1484
|
applyRadialDeadzone,
|