@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.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 TYPES = {
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
- let el = document.elementFromPoint(x, y);
327
- while (el && el.shadowRoot) {
328
- const nested = el.shadowRoot.elementFromPoint(x, y);
329
- if (!nested || nested === el) break;
330
- el = nested;
331
- }
332
- return el;
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: 1,
381
+ pointerId: 9999,
377
382
  pointerType: "mouse",
378
383
  // Emulate mouse behavior for Flash MouseOver/Down logic
379
384
  ...commonProps
@@ -706,6 +711,24 @@ var SimpleEmitter = class {
706
711
  }
707
712
  };
708
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
+
709
732
  // src/imputManager/index.ts
710
733
  var import_meta2 = {};
711
734
  var INPUT_MANAGER_KEY = /* @__PURE__ */ Symbol.for("omnipad.input_manager.instance");
@@ -713,6 +736,8 @@ var InputManager = class _InputManager {
713
736
  constructor() {
714
737
  /** Internal flag to prevent multiple event registrations */
715
738
  __publicField(this, "_isListening", false);
739
+ /** A throttled version of the reset logic */
740
+ __publicField(this, "throttledReset");
716
741
  /**
717
742
  * Manually triggers a system-wide input reset via Registry.
718
743
  */
@@ -722,6 +747,20 @@ var InputManager = class _InputManager {
722
747
  }
723
748
  Registry.getInstance().resetAll();
724
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
+ });
725
764
  }
726
765
  /**
727
766
  * Retrieves the global instance of the InputManager.
@@ -740,8 +779,9 @@ var InputManager = class _InputManager {
740
779
  */
741
780
  init() {
742
781
  if (this._isListening) return;
743
- window.addEventListener("resize", this.handleGlobalReset);
744
- window.addEventListener("blur", this.handleGlobalReset);
782
+ window.addEventListener("resize", this.handleResizeReset);
783
+ window.addEventListener("blur", this.handleBlurReset);
784
+ document.addEventListener("visibilitychange", this.handleVisibilityChangeReset);
745
785
  this._isListening = true;
746
786
  if (import_meta2.env?.DEV) {
747
787
  console.log("[OmniPad-Core] Global InputManager monitoring started.");
@@ -775,8 +815,9 @@ var InputManager = class _InputManager {
775
815
  * Detaches all global listeners.
776
816
  */
777
817
  destroy() {
778
- window.removeEventListener("resize", this.handleGlobalReset);
779
- window.removeEventListener("blur", this.handleGlobalReset);
818
+ window.removeEventListener("resize", this.handleResizeReset);
819
+ window.removeEventListener("blur", this.handleBlurReset);
820
+ window.removeEventListener("visibilitychange", this.handleVisibilityChangeReset);
780
821
  this._isListening = false;
781
822
  }
782
823
  };
@@ -788,7 +829,7 @@ var BaseEntity = class {
788
829
  __publicField(this, "type");
789
830
  __publicField(this, "config");
790
831
  __publicField(this, "state");
791
- __publicField(this, "rect", null);
832
+ __publicField(this, "rectProvider", null);
792
833
  // 内部状态发射器,负责处理状态订阅逻辑 / Internal emitter for state subscription logic
793
834
  __publicField(this, "stateEmitter", new SimpleEmitter());
794
835
  this.uid = uid;
@@ -817,8 +858,14 @@ var BaseEntity = class {
817
858
  this.stateEmitter.clear();
818
859
  Registry.getInstance().unregister(this.uid);
819
860
  }
820
- updateRect(rect) {
821
- this.rect = rect;
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;
822
869
  }
823
870
  updateConfig(newConfig) {
824
871
  this.config = { ...this.config, ...newConfig };
@@ -840,12 +887,13 @@ var INITIAL_STATE = {
840
887
  };
841
888
  var InputZoneCore = class extends BaseEntity {
842
889
  constructor(uid, config) {
843
- super(uid, TYPES.INPUT_ZONE, config, INITIAL_STATE);
890
+ super(uid, CMP_TYPES.INPUT_ZONE, config, INITIAL_STATE);
844
891
  }
845
892
  onPointerDown(e) {
846
893
  if (this.state.isDynamicActive) return;
847
894
  if (e.target !== e.currentTarget) return;
848
895
  if (e.cancelable) e.preventDefault();
896
+ e.stopPropagation();
849
897
  const pos = this.calculateRelativePosition(e.clientX, e.clientY);
850
898
  this.setState({
851
899
  isDynamicActive: true,
@@ -857,6 +905,7 @@ var InputZoneCore = class extends BaseEntity {
857
905
  if (!this.state.isDynamicActive || e.pointerId !== this.state.dynamicPointerId) return;
858
906
  }
859
907
  onPointerUp(e) {
908
+ if (e.cancelable) e.preventDefault();
860
909
  this.handleRelease(e);
861
910
  }
862
911
  onPointerCancel(e) {
@@ -882,10 +931,11 @@ var InputZoneCore = class extends BaseEntity {
882
931
  * Converts viewport pixels to percentage coordinates relative to the zone.
883
932
  */
884
933
  calculateRelativePosition(clientX, clientY) {
885
- if (!this.rect) return { x: 0, y: 0 };
934
+ const rect = this.getRect();
935
+ if (!rect) return { x: 0, y: 0 };
886
936
  return {
887
- x: pxToPercent(clientX - this.rect.left, this.rect.width),
888
- y: pxToPercent(clientY - this.rect.top, this.rect.height)
937
+ x: pxToPercent(clientX - rect.left, rect.width),
938
+ y: pxToPercent(clientY - rect.top, rect.height)
889
939
  };
890
940
  }
891
941
  /**
@@ -918,11 +968,12 @@ var KeyboardButtonCore = class extends BaseEntity {
918
968
  * @param config - The flat configuration object for the button.
919
969
  */
920
970
  constructor(uid, config) {
921
- super(uid, TYPES.KEYBOARD_BUTTON, config, INITIAL_STATE2);
971
+ super(uid, CMP_TYPES.KEYBOARD_BUTTON, config, INITIAL_STATE2);
922
972
  }
923
973
  // --- IPointerHandler Implementation ---
924
974
  onPointerDown(e) {
925
975
  if (e.cancelable) e.preventDefault();
976
+ e.stopPropagation();
926
977
  e.target.setPointerCapture(e.pointerId);
927
978
  this.setState({
928
979
  isActive: true,
@@ -977,7 +1028,7 @@ var KeyboardButtonCore = class extends BaseEntity {
977
1028
  target.handleSignal(signal);
978
1029
  } else {
979
1030
  if (import_meta3.env?.DEV) {
980
- console.warn(`[OmniPad-Core] Button ${this.uid} target not found: ${targetId}`);
1031
+ console.warn(`[OmniPad-Core] KeyboardButton ${this.uid} target not found: ${targetId}`);
981
1032
  }
982
1033
  }
983
1034
  }
@@ -990,20 +1041,109 @@ var KeyboardButtonCore = class extends BaseEntity {
990
1041
  }
991
1042
  };
992
1043
 
993
- // src/entities/RootLayerCore.ts
1044
+ // src/entities/MouseButtonCore.ts
1045
+ var import_meta4 = {};
994
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 = {
995
1135
  isHighlighted: false
996
1136
  };
997
1137
  var RootLayerCore = class extends BaseEntity {
998
1138
  constructor(uid, config) {
999
- super(uid, TYPES.ROOT_LAYER, config, INITIAL_STATE3);
1139
+ super(uid, CMP_TYPES.ROOT_LAYER, config, INITIAL_STATE4);
1000
1140
  }
1001
1141
  reset() {
1002
1142
  }
1003
1143
  };
1004
1144
 
1005
1145
  // src/entities/TargetZoneCore.ts
1006
- var INITIAL_STATE4 = {
1146
+ var INITIAL_STATE5 = {
1007
1147
  position: { x: 50, y: 50 },
1008
1148
  isVisible: false,
1009
1149
  isPointerDown: false,
@@ -1011,9 +1151,48 @@ var INITIAL_STATE4 = {
1011
1151
  };
1012
1152
  var TargetZoneCore = class extends BaseEntity {
1013
1153
  constructor(uid, config) {
1014
- super(uid, TYPES.TARGET_ZONE, config, INITIAL_STATE4);
1154
+ super(uid, CMP_TYPES.TARGET_ZONE, config, INITIAL_STATE5);
1015
1155
  __publicField(this, "hideTimer", null);
1016
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
+ });
1017
1196
  }
1018
1197
  // --- ISignalReceiver Implementation ---
1019
1198
  handleSignal(signal) {
@@ -1027,13 +1206,18 @@ var TargetZoneCore = class extends BaseEntity {
1027
1206
  case ACTION_TYPES.MOUSEMOVE:
1028
1207
  if (payload.point) {
1029
1208
  this.updateCursorPosition(payload.point);
1030
- if (this.config.cursorEnabled) this.showCursor();
1031
- this.executeMouseAction(ACTION_TYPES.POINTERMOVE, payload);
1209
+ } else if (payload.delta) {
1210
+ this.updateCursorPositionByDelta(payload.delta);
1032
1211
  }
1212
+ if (this.config.cursorEnabled) this.showCursor();
1213
+ this.executeMouseAction(ACTION_TYPES.POINTERMOVE, payload);
1033
1214
  break;
1034
1215
  case ACTION_TYPES.MOUSEDOWN:
1035
1216
  case ACTION_TYPES.MOUSEUP:
1036
1217
  case ACTION_TYPES.CLICK:
1218
+ if (payload.point) {
1219
+ this.updateCursorPosition(payload.point);
1220
+ }
1037
1221
  if (this.config.cursorEnabled) this.showCursor();
1038
1222
  this.executeMouseAction(
1039
1223
  type.startsWith(ACTION_TYPES.MOUSE) ? type.replace(ACTION_TYPES.MOUSE, ACTION_TYPES.POINTER) : type,
@@ -1050,12 +1234,13 @@ var TargetZoneCore = class extends BaseEntity {
1050
1234
  * @param payload - Data containing point coordinates or button info.
1051
1235
  */
1052
1236
  executeMouseAction(pointerType, payload) {
1053
- if (!this.rect) return;
1237
+ const rect = this.getRect();
1238
+ if (!rect) return;
1054
1239
  if (pointerType === ACTION_TYPES.POINTERDOWN) this.setState({ isPointerDown: true });
1055
1240
  if (pointerType === ACTION_TYPES.POINTERUP) this.setState({ isPointerDown: false });
1056
1241
  const target = payload.point || this.state.position;
1057
- const px = this.rect.left + percentToPx(target.x, this.rect.width);
1058
- const py = this.rect.top + percentToPx(target.y, this.rect.height);
1242
+ const px = rect.left + percentToPx(target.x, rect.width);
1243
+ const py = rect.top + percentToPx(target.y, rect.height);
1059
1244
  dispatchPointerEventAtPos(pointerType, px, py, {
1060
1245
  button: payload.button ?? 0,
1061
1246
  buttons: this.state.isPointerDown ? 1 : 0
@@ -1066,9 +1251,10 @@ var TargetZoneCore = class extends BaseEntity {
1066
1251
  * Checks if the target element under the virtual cursor has focus, and reclaims it if lost.
1067
1252
  */
1068
1253
  ensureFocus() {
1069
- if (!this.rect) return;
1070
- const px = this.rect.left + percentToPx(this.state.position.x, this.rect.width);
1071
- const py = this.rect.top + percentToPx(this.state.position.y, this.rect.height);
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);
1072
1258
  const target = getDeepElement(px, py);
1073
1259
  if (!target) return;
1074
1260
  if (getDeepActiveElement() !== target) {
@@ -1089,8 +1275,23 @@ var TargetZoneCore = class extends BaseEntity {
1089
1275
  * Updates the internal virtual cursor coordinates.
1090
1276
  */
1091
1277
  updateCursorPosition(point) {
1278
+ if (isVec2Equal(point, this.state.position)) return;
1092
1279
  this.setState({ position: { ...point } });
1093
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
+ }
1094
1295
  /**
1095
1296
  * Makes the virtual cursor visible and sets a timeout for auto-hiding.
1096
1297
  */
@@ -1119,27 +1320,165 @@ var TargetZoneCore = class extends BaseEntity {
1119
1320
  }
1120
1321
  };
1121
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
+
1122
1458
  // src/index.ts
1123
1459
  var OmniPad = {
1460
+ ActionTypes: ACTION_TYPES,
1124
1461
  Context: CONTEXT,
1125
1462
  Keys: KEYS,
1126
- Types: TYPES
1463
+ Types: CMP_TYPES
1127
1464
  };
1128
1465
  // Annotate the CommonJS export names for ESM import in node:
1129
1466
  0 && (module.exports = {
1130
1467
  ACTION_TYPES,
1131
1468
  BaseEntity,
1469
+ CMP_TYPES,
1132
1470
  CONTEXT,
1133
1471
  InputManager,
1134
1472
  InputZoneCore,
1135
1473
  KEYS,
1136
1474
  KeyboardButtonCore,
1475
+ MouseButtonCore,
1137
1476
  OmniPad,
1138
1477
  Registry,
1139
1478
  RootLayerCore,
1140
1479
  SimpleEmitter,
1141
- TYPES,
1142
1480
  TargetZoneCore,
1481
+ TrackpadCore,
1143
1482
  addVec,
1144
1483
  applyAxialDeadzone,
1145
1484
  applyRadialDeadzone,