@omnipad/core 0.1.1-alpha.1 → 0.2.0-alpha.3

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.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 TYPES = {
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
- 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;
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: 1,
309
+ pointerId: 9999,
307
310
  pointerType: "mouse",
308
311
  // Emulate mouse behavior for Flash MouseOver/Down logic
309
312
  ...commonProps
@@ -635,12 +638,32 @@ var SimpleEmitter = class {
635
638
  }
636
639
  };
637
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
+
638
659
  // src/imputManager/index.ts
639
660
  var INPUT_MANAGER_KEY = /* @__PURE__ */ Symbol.for("omnipad.input_manager.instance");
640
661
  var InputManager = class _InputManager {
641
662
  constructor() {
642
663
  /** Internal flag to prevent multiple event registrations */
643
664
  __publicField(this, "_isListening", false);
665
+ /** A throttled version of the reset logic */
666
+ __publicField(this, "throttledReset");
644
667
  /**
645
668
  * Manually triggers a system-wide input reset via Registry.
646
669
  */
@@ -650,6 +673,20 @@ var InputManager = class _InputManager {
650
673
  }
651
674
  Registry.getInstance().resetAll();
652
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
+ });
653
690
  }
654
691
  /**
655
692
  * Retrieves the global instance of the InputManager.
@@ -668,8 +705,9 @@ var InputManager = class _InputManager {
668
705
  */
669
706
  init() {
670
707
  if (this._isListening) return;
671
- window.addEventListener("resize", this.handleGlobalReset);
672
- window.addEventListener("blur", this.handleGlobalReset);
708
+ window.addEventListener("resize", this.handleResizeReset);
709
+ window.addEventListener("blur", this.handleBlurReset);
710
+ document.addEventListener("visibilitychange", this.handleVisibilityChangeReset);
673
711
  this._isListening = true;
674
712
  if (import.meta.env?.DEV) {
675
713
  console.log("[OmniPad-Core] Global InputManager monitoring started.");
@@ -703,8 +741,9 @@ var InputManager = class _InputManager {
703
741
  * Detaches all global listeners.
704
742
  */
705
743
  destroy() {
706
- window.removeEventListener("resize", this.handleGlobalReset);
707
- window.removeEventListener("blur", this.handleGlobalReset);
744
+ window.removeEventListener("resize", this.handleResizeReset);
745
+ window.removeEventListener("blur", this.handleBlurReset);
746
+ window.removeEventListener("visibilitychange", this.handleVisibilityChangeReset);
708
747
  this._isListening = false;
709
748
  }
710
749
  };
@@ -716,7 +755,7 @@ var BaseEntity = class {
716
755
  __publicField(this, "type");
717
756
  __publicField(this, "config");
718
757
  __publicField(this, "state");
719
- __publicField(this, "rect", null);
758
+ __publicField(this, "rectProvider", null);
720
759
  // 内部状态发射器,负责处理状态订阅逻辑 / Internal emitter for state subscription logic
721
760
  __publicField(this, "stateEmitter", new SimpleEmitter());
722
761
  this.uid = uid;
@@ -745,8 +784,14 @@ var BaseEntity = class {
745
784
  this.stateEmitter.clear();
746
785
  Registry.getInstance().unregister(this.uid);
747
786
  }
748
- updateRect(rect) {
749
- this.rect = rect;
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;
750
795
  }
751
796
  updateConfig(newConfig) {
752
797
  this.config = { ...this.config, ...newConfig };
@@ -768,12 +813,13 @@ var INITIAL_STATE = {
768
813
  };
769
814
  var InputZoneCore = class extends BaseEntity {
770
815
  constructor(uid, config) {
771
- super(uid, TYPES.INPUT_ZONE, config, INITIAL_STATE);
816
+ super(uid, CMP_TYPES.INPUT_ZONE, config, INITIAL_STATE);
772
817
  }
773
818
  onPointerDown(e) {
774
819
  if (this.state.isDynamicActive) return;
775
820
  if (e.target !== e.currentTarget) return;
776
821
  if (e.cancelable) e.preventDefault();
822
+ e.stopPropagation();
777
823
  const pos = this.calculateRelativePosition(e.clientX, e.clientY);
778
824
  this.setState({
779
825
  isDynamicActive: true,
@@ -785,6 +831,7 @@ var InputZoneCore = class extends BaseEntity {
785
831
  if (!this.state.isDynamicActive || e.pointerId !== this.state.dynamicPointerId) return;
786
832
  }
787
833
  onPointerUp(e) {
834
+ if (e.cancelable) e.preventDefault();
788
835
  this.handleRelease(e);
789
836
  }
790
837
  onPointerCancel(e) {
@@ -810,10 +857,11 @@ var InputZoneCore = class extends BaseEntity {
810
857
  * Converts viewport pixels to percentage coordinates relative to the zone.
811
858
  */
812
859
  calculateRelativePosition(clientX, clientY) {
813
- if (!this.rect) return { x: 0, y: 0 };
860
+ const rect = this.getRect();
861
+ if (!rect) return { x: 0, y: 0 };
814
862
  return {
815
- x: pxToPercent(clientX - this.rect.left, this.rect.width),
816
- y: pxToPercent(clientY - this.rect.top, this.rect.height)
863
+ x: pxToPercent(clientX - rect.left, rect.width),
864
+ y: pxToPercent(clientY - rect.top, rect.height)
817
865
  };
818
866
  }
819
867
  /**
@@ -845,11 +893,12 @@ var KeyboardButtonCore = class extends BaseEntity {
845
893
  * @param config - The flat configuration object for the button.
846
894
  */
847
895
  constructor(uid, config) {
848
- super(uid, TYPES.KEYBOARD_BUTTON, config, INITIAL_STATE2);
896
+ super(uid, CMP_TYPES.KEYBOARD_BUTTON, config, INITIAL_STATE2);
849
897
  }
850
898
  // --- IPointerHandler Implementation ---
851
899
  onPointerDown(e) {
852
900
  if (e.cancelable) e.preventDefault();
901
+ e.stopPropagation();
853
902
  e.target.setPointerCapture(e.pointerId);
854
903
  this.setState({
855
904
  isActive: true,
@@ -904,7 +953,7 @@ var KeyboardButtonCore = class extends BaseEntity {
904
953
  target.handleSignal(signal);
905
954
  } else {
906
955
  if (import.meta.env?.DEV) {
907
- console.warn(`[OmniPad-Core] Button ${this.uid} target not found: ${targetId}`);
956
+ console.warn(`[OmniPad-Core] KeyboardButton ${this.uid} target not found: ${targetId}`);
908
957
  }
909
958
  }
910
959
  }
@@ -917,20 +966,108 @@ var KeyboardButtonCore = class extends BaseEntity {
917
966
  }
918
967
  };
919
968
 
920
- // src/entities/RootLayerCore.ts
969
+ // src/entities/MouseButtonCore.ts
921
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 = {
922
1059
  isHighlighted: false
923
1060
  };
924
1061
  var RootLayerCore = class extends BaseEntity {
925
1062
  constructor(uid, config) {
926
- super(uid, TYPES.ROOT_LAYER, config, INITIAL_STATE3);
1063
+ super(uid, CMP_TYPES.ROOT_LAYER, config, INITIAL_STATE4);
927
1064
  }
928
1065
  reset() {
929
1066
  }
930
1067
  };
931
1068
 
932
1069
  // src/entities/TargetZoneCore.ts
933
- var INITIAL_STATE4 = {
1070
+ var INITIAL_STATE5 = {
934
1071
  position: { x: 50, y: 50 },
935
1072
  isVisible: false,
936
1073
  isPointerDown: false,
@@ -938,9 +1075,48 @@ var INITIAL_STATE4 = {
938
1075
  };
939
1076
  var TargetZoneCore = class extends BaseEntity {
940
1077
  constructor(uid, config) {
941
- super(uid, TYPES.TARGET_ZONE, config, INITIAL_STATE4);
1078
+ super(uid, CMP_TYPES.TARGET_ZONE, config, INITIAL_STATE5);
942
1079
  __publicField(this, "hideTimer", null);
943
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
+ });
944
1120
  }
945
1121
  // --- ISignalReceiver Implementation ---
946
1122
  handleSignal(signal) {
@@ -954,13 +1130,18 @@ var TargetZoneCore = class extends BaseEntity {
954
1130
  case ACTION_TYPES.MOUSEMOVE:
955
1131
  if (payload.point) {
956
1132
  this.updateCursorPosition(payload.point);
957
- if (this.config.cursorEnabled) this.showCursor();
958
- this.executeMouseAction(ACTION_TYPES.POINTERMOVE, payload);
1133
+ } else if (payload.delta) {
1134
+ this.updateCursorPositionByDelta(payload.delta);
959
1135
  }
1136
+ if (this.config.cursorEnabled) this.showCursor();
1137
+ this.executeMouseAction(ACTION_TYPES.POINTERMOVE, payload);
960
1138
  break;
961
1139
  case ACTION_TYPES.MOUSEDOWN:
962
1140
  case ACTION_TYPES.MOUSEUP:
963
1141
  case ACTION_TYPES.CLICK:
1142
+ if (payload.point) {
1143
+ this.updateCursorPosition(payload.point);
1144
+ }
964
1145
  if (this.config.cursorEnabled) this.showCursor();
965
1146
  this.executeMouseAction(
966
1147
  type.startsWith(ACTION_TYPES.MOUSE) ? type.replace(ACTION_TYPES.MOUSE, ACTION_TYPES.POINTER) : type,
@@ -977,12 +1158,13 @@ var TargetZoneCore = class extends BaseEntity {
977
1158
  * @param payload - Data containing point coordinates or button info.
978
1159
  */
979
1160
  executeMouseAction(pointerType, payload) {
980
- if (!this.rect) return;
1161
+ const rect = this.getRect();
1162
+ if (!rect) return;
981
1163
  if (pointerType === ACTION_TYPES.POINTERDOWN) this.setState({ isPointerDown: true });
982
1164
  if (pointerType === ACTION_TYPES.POINTERUP) this.setState({ isPointerDown: false });
983
1165
  const target = payload.point || this.state.position;
984
- const px = this.rect.left + percentToPx(target.x, this.rect.width);
985
- const py = this.rect.top + percentToPx(target.y, this.rect.height);
1166
+ const px = rect.left + percentToPx(target.x, rect.width);
1167
+ const py = rect.top + percentToPx(target.y, rect.height);
986
1168
  dispatchPointerEventAtPos(pointerType, px, py, {
987
1169
  button: payload.button ?? 0,
988
1170
  buttons: this.state.isPointerDown ? 1 : 0
@@ -993,9 +1175,10 @@ var TargetZoneCore = class extends BaseEntity {
993
1175
  * Checks if the target element under the virtual cursor has focus, and reclaims it if lost.
994
1176
  */
995
1177
  ensureFocus() {
996
- if (!this.rect) return;
997
- const px = this.rect.left + percentToPx(this.state.position.x, this.rect.width);
998
- const py = this.rect.top + percentToPx(this.state.position.y, this.rect.height);
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);
999
1182
  const target = getDeepElement(px, py);
1000
1183
  if (!target) return;
1001
1184
  if (getDeepActiveElement() !== target) {
@@ -1016,8 +1199,23 @@ var TargetZoneCore = class extends BaseEntity {
1016
1199
  * Updates the internal virtual cursor coordinates.
1017
1200
  */
1018
1201
  updateCursorPosition(point) {
1202
+ if (isVec2Equal(point, this.state.position)) return;
1019
1203
  this.setState({ position: { ...point } });
1020
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
+ }
1021
1219
  /**
1022
1220
  * Makes the virtual cursor visible and sets a timeout for auto-hiding.
1023
1221
  */
@@ -1046,26 +1244,164 @@ var TargetZoneCore = class extends BaseEntity {
1046
1244
  }
1047
1245
  };
1048
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
+
1049
1382
  // src/index.ts
1050
1383
  var OmniPad = {
1384
+ ActionTypes: ACTION_TYPES,
1051
1385
  Context: CONTEXT,
1052
1386
  Keys: KEYS,
1053
- Types: TYPES
1387
+ Types: CMP_TYPES
1054
1388
  };
1055
1389
  export {
1056
1390
  ACTION_TYPES,
1057
1391
  BaseEntity,
1392
+ CMP_TYPES,
1058
1393
  CONTEXT,
1059
1394
  InputManager,
1060
1395
  InputZoneCore,
1061
1396
  KEYS,
1062
1397
  KeyboardButtonCore,
1398
+ MouseButtonCore,
1063
1399
  OmniPad,
1064
1400
  Registry,
1065
1401
  RootLayerCore,
1066
1402
  SimpleEmitter,
1067
- TYPES,
1068
1403
  TargetZoneCore,
1404
+ TrackpadCore,
1069
1405
  addVec,
1070
1406
  applyAxialDeadzone,
1071
1407
  applyRadialDeadzone,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnipad/core",
3
- "version": "0.1.1-alpha.1",
3
+ "version": "0.2.0-alpha.3",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",