Skip to content

Commit 2252569

Browse files
committed
refactor(core/target-zone): introduce setDispatcherProvider and set default dispatch strategy of TargetZone
1 parent fd392ea commit 2252569

File tree

6 files changed

+108
-19
lines changed

6 files changed

+108
-19
lines changed

packages/core/src/entities/TargetZoneCore.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ import { clamp, isVec2Equal, percentToPx, pxToPercent } from '../utils/math';
66
import { createRafThrottler } from '../runtime/performance';
77
import { BaseEntity } from './BaseEntity';
88
import { ACTION_TYPES, CMP_TYPES } from '../constants';
9+
import { DispatcherProvider, getDispatcher } from '../runtime/dispatch';
10+
11+
let _defaultDispatcher: DispatcherProvider | null = null;
12+
13+
const getDefaultDispatcher = () => {
14+
if (_defaultDispatcher) return _defaultDispatcher;
15+
_defaultDispatcher = getDispatcher() as DispatcherProvider;
16+
return _defaultDispatcher;
17+
};
918

1019
/**
1120
* Interface for delegating DOM operations within a target zone.
@@ -45,9 +54,12 @@ export class TargetZoneCore
4554
private throttledMoveExecution: (payload: any) => void;
4655

4756
private delegates: TargetZoneDelegates = {
48-
dispatchKeyboardEvent: () => {},
49-
dispatchPointerEventAtPos: () => {},
50-
reclaimFocusAtPos: () => {},
57+
dispatchKeyboardEvent: (type: string, payload: any) =>
58+
getDefaultDispatcher()?.dispatchKeyboard(type, payload),
59+
dispatchPointerEventAtPos: (type: string, x: number, y: number, opts: any) =>
60+
getDefaultDispatcher()?.dispatchPointerAtPos(type, x, y, opts),
61+
reclaimFocusAtPos: (x: number, y: number, callback: () => void) =>
62+
getDefaultDispatcher()?.reclaimFocus(x, y, callback),
5163
};
5264

5365
constructor(uid: string, config: TargetZoneConfig, customTypeName?: EntityType) {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Interface defining the required capabilities for an environment-specific event dispatcher.
3+
*/
4+
export interface DispatcherProvider {
5+
/**
6+
* Simulates a keyboard event in the host environment.
7+
*
8+
* @param type - The action type (e.g., 'keydown', 'keyup').
9+
* @param payload - Keyboard event data containing key, code, and keyCode.
10+
*/
11+
dispatchKeyboard: (type: string, payload: any) => void;
12+
13+
/**
14+
* Simulates a pointer or mouse event at a specific viewport coordinate.
15+
*
16+
* @param type - The action type (e.g., 'pointerdown', 'pointermove', 'click').
17+
* @param x - Absolute viewport X coordinate in pixels.
18+
* @param y - Absolute viewport Y coordinate in pixels.
19+
* @param opts - Additional pointer event options like buttons or pressure.
20+
*/
21+
dispatchPointerAtPos: (type: string, x: number, y: number, opts: any) => void;
22+
23+
/**
24+
* Reclaims and forces focus back to the interaction target at the specified position.
25+
*
26+
* @param x - Viewport X coordinate in pixels.
27+
* @param y - Viewport Y coordinate in pixels.
28+
*/
29+
reclaimFocus: (x: number, y: number, callback: () => void) => void;
30+
}
31+
32+
/** Internal storage for the active dispatcher implementation. */
33+
let _dispatcher: DispatcherProvider | null = null;
34+
35+
/**
36+
* Injects a platform-specific dispatcher implementation into the core engine.
37+
* This is the bridge that allows the headless core to "hit" the real world.
38+
*
39+
* @param p - The dispatcher implementation (typically from @omnipad/web).
40+
*/
41+
export function setDispatcherProvider(p: DispatcherProvider) {
42+
_dispatcher = p;
43+
}
44+
45+
/**
46+
* Retrieves the currently active dispatcher provider.
47+
* Returns null if no provider has been injected.
48+
*
49+
* @internal
50+
*/
51+
export const getDispatcher = () => _dispatcher;

packages/core/src/runtime/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './adapter';
2-
export * from './performance';
3-
export * from './profile';
2+
export { setDispatcherProvider } from './dispatch';
3+
export { setRafProvider, createRafThrottler, createTicker, delayFrames } from './performance';
4+
export { setSecurityPolicy, validateProfile, parseProfileForest, exportProfile } from './profile';

packages/vue/src/components/TargetZone.vue

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,7 @@ import {
88
type LayoutBox,
99
type TargetZoneConfig,
1010
} from '@omnipad/core';
11-
import { resolveLayoutStyle, projectPercentToBox } from '@omnipad/web';
12-
import {
13-
supportsContainerQueries,
14-
dispatchKeyboardEvent,
15-
dispatchPointerEventAtPos,
16-
reclaimFocusAtPos,
17-
} from '@omnipad/web';
11+
import { resolveLayoutStyle, projectPercentToBox, supportsContainerQueries } from '@omnipad/web';
1812
import { useWidgetSetup } from '../composables/useWidgetSetup';
1913
2014
interface TargetZoneProps {
@@ -44,11 +38,11 @@ const defaultProps = {
4438
const { uid, core, state, domEvents, effectiveConfig, effectiveLayout, elementRef } =
4539
useWidgetSetup<TargetZoneCore, CursorState, TargetZoneConfig>(OmniPad.Types.TARGET_ZONE, props, {
4640
defaultProps,
47-
initialDelegates: {
48-
dispatchKeyboardEvent: dispatchKeyboardEvent,
49-
dispatchPointerEventAtPos: dispatchPointerEventAtPos,
50-
reclaimFocusAtPos: reclaimFocusAtPos,
51-
},
41+
// initialDelegates: {
42+
// dispatchKeyboardEvent: dispatchKeyboardEvent,
43+
// dispatchPointerEventAtPos: dispatchPointerEventAtPos,
44+
// reclaimFocusAtPos: reclaimFocusAtPos,
45+
// },
5246
});
5347
5448
const containerStyle = computed(() => {

packages/web/src/dom/action.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export const dispatchPointerEventAtPos = (
7575
*
7676
* @param x - The horizontal coordinate relative to the viewport.
7777
* @param y - The vertical coordinate relative to the viewport.
78-
* @returns True if the focus was successfully moved to the target; false if it was already focused or no target found.
78+
* @param callback - The function called when the focus is to be reclaimed.
7979
*/
8080
export const reclaimFocusAtPos = (x: number, y: number, callback: () => void): void => {
8181
// Find the deepest element at coordinates, penetrating Shadow DOM boundaries

packages/web/src/index.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import {
22
AbstractGamepad,
33
OmniPad,
4+
setDispatcherProvider,
45
setGamepadProvider,
56
setGlobalSignalHandler,
67
setRafProvider,
78
setSecurityPolicy,
89
} from '@omnipad/core';
9-
import { dispatchKeyboardEvent } from './dom/action';
10+
import { dispatchKeyboardEvent, dispatchPointerEventAtPos, reclaimFocusAtPos } from './dom/action';
1011
import { sanitizeCssClass, sanitizePrototypePollution } from './ts/security';
1112
import { validateLayoutBox } from './ts/layout';
1213

@@ -17,34 +18,64 @@ export * from './singletons/ElementObserver';
1718
export * from './singletons/IFrameManager';
1819
export * from './singletons/WindowManager';
1920

21+
// =============================================================
22+
// 1. Performance: Heartbeat Synchronization
23+
// 将核心库的逻辑循环与浏览器的渲染节拍同步
24+
// =============================================================
2025
if (typeof window !== 'undefined') {
2126
setRafProvider(
2227
window.requestAnimationFrame.bind(window),
2328
window.cancelAnimationFrame.bind(window),
2429
);
2530
}
2631

32+
// =============================================================
33+
// 2. Dispatcher: Physical Action Execution
34+
// 注入具体的 DOM 副作用实现,负责将抽象信号转化为真实的浏览器事件
35+
// =============================================================
36+
setDispatcherProvider({
37+
dispatchKeyboard: dispatchKeyboardEvent,
38+
dispatchPointerAtPos: dispatchPointerEventAtPos,
39+
reclaimFocus: reclaimFocusAtPos,
40+
});
41+
42+
// =============================================================
43+
// 3. Global Signals: Orphaned Signal Recovery
44+
// 兜底策略:当输入控件没有绑定具体的 TargetZone 时,信号直接打在顶层 window 上
45+
// =============================================================
2746
setGlobalSignalHandler((signal) => {
2847
if (signal.type === OmniPad.ActionTypes.KEYDOWN || signal.type === OmniPad.ActionTypes.KEYUP) {
2948
dispatchKeyboardEvent(signal.type as any, signal.payload as any);
3049
}
3150
});
3251

52+
// =============================================================
53+
// 4. Gamepad: Hardware Input Polling
54+
// 注入硬件采集能力。使用固定长度数组作为对象池,减少高频轮询产生的 GC 压力
55+
// =============================================================
3356
const gamepadSnapshot: (AbstractGamepad | null)[] = [null, null, null, null];
3457

3558
if (typeof navigator !== 'undefined' && navigator.getGamepads) {
3659
setGamepadProvider(() => {
3760
const rawPads = navigator.getGamepads();
61+
// 映射原生手柄数据到抽象接口,确保 Core 层平台无关性
62+
// Map native gamepad data to abstract interfaces for platform-agnostic core logic
3863
for (let i = 0; i < 4; i++) {
3964
gamepadSnapshot[i] = rawPads[i] || null;
4065
}
4166
return gamepadSnapshot;
4267
});
4368
}
4469

70+
// =============================================================
71+
// 5. Security: Policy Injection
72+
// 注入针对 Web 环境的安全校验规则(如 CSS 单位白名单、原型链防护)
73+
// =============================================================
4574
setSecurityPolicy({
75+
// 过滤非法键名,防止恶意配置篡改运行时原型 / Prevent prototype pollution from malicious JSON
4676
sanitizeObject: (obj) => sanitizePrototypePollution(obj),
4777

78+
// 执行业务层面的脱毒,确保布局参数符合浏览器 CSS 标准 / Sanitize business config for CSS standard
4879
validateConfig: (_, config) => {
4980
return {
5081
...config,

0 commit comments

Comments
 (0)