@flighthq/interaction 0.2.0 → 0.2.1-next.410.a23f189

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.
Files changed (74) hide show
  1. package/dist/cursorBackend.d.ts +12 -0
  2. package/dist/cursorBackend.d.ts.map +1 -0
  3. package/dist/cursorBackend.js +17 -0
  4. package/dist/cursorBackend.js.map +1 -0
  5. package/dist/displayHitTests.d.ts +10 -11
  6. package/dist/displayHitTests.d.ts.map +1 -1
  7. package/dist/displayHitTests.js +12 -14
  8. package/dist/displayHitTests.js.map +1 -1
  9. package/dist/enableInteractionGuards.d.ts +23 -0
  10. package/dist/enableInteractionGuards.d.ts.map +1 -0
  11. package/dist/enableInteractionGuards.js +75 -0
  12. package/dist/enableInteractionGuards.js.map +1 -0
  13. package/dist/focusManager.d.ts +46 -0
  14. package/dist/focusManager.d.ts.map +1 -0
  15. package/dist/focusManager.js +243 -0
  16. package/dist/focusManager.js.map +1 -0
  17. package/dist/hitTests.d.ts +45 -24
  18. package/dist/hitTests.d.ts.map +1 -1
  19. package/dist/hitTests.js +164 -47
  20. package/dist/hitTests.js.map +1 -1
  21. package/dist/index.d.ts +9 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +9 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/interactionManager.d.ts +10 -2
  26. package/dist/interactionManager.d.ts.map +1 -1
  27. package/dist/interactionManager.js +58 -6
  28. package/dist/interactionManager.js.map +1 -1
  29. package/dist/interactionSpatialIndex.d.ts +17 -0
  30. package/dist/interactionSpatialIndex.d.ts.map +1 -0
  31. package/dist/interactionSpatialIndex.js +77 -0
  32. package/dist/interactionSpatialIndex.js.map +1 -0
  33. package/dist/nodeInteractionState.d.ts +39 -0
  34. package/dist/nodeInteractionState.d.ts.map +1 -0
  35. package/dist/nodeInteractionState.js +72 -0
  36. package/dist/nodeInteractionState.js.map +1 -0
  37. package/dist/registerBitmapHitTest.d.ts +11 -0
  38. package/dist/registerBitmapHitTest.d.ts.map +1 -0
  39. package/dist/registerBitmapHitTest.js +55 -0
  40. package/dist/registerBitmapHitTest.js.map +1 -0
  41. package/dist/registerDefaultHitTests.d.ts +2 -0
  42. package/dist/registerDefaultHitTests.d.ts.map +1 -0
  43. package/dist/registerDefaultHitTests.js +20 -0
  44. package/dist/registerDefaultHitTests.js.map +1 -0
  45. package/dist/registerShapeHitTest.d.ts +10 -0
  46. package/dist/registerShapeHitTest.d.ts.map +1 -0
  47. package/dist/registerShapeHitTest.js +32 -0
  48. package/dist/registerShapeHitTest.js.map +1 -0
  49. package/dist/registerTextHitTest.d.ts +12 -0
  50. package/dist/registerTextHitTest.d.ts.map +1 -0
  51. package/dist/registerTextHitTest.js +32 -0
  52. package/dist/registerTextHitTest.js.map +1 -0
  53. package/dist/spriteHitTests.d.ts +3 -3
  54. package/dist/spriteHitTests.d.ts.map +1 -1
  55. package/dist/spriteHitTests.js +5 -5
  56. package/dist/spriteHitTests.js.map +1 -1
  57. package/package.json +13 -7
  58. package/src/cursorBackend.test.ts +24 -0
  59. package/src/displayHitTests.test.ts +39 -47
  60. package/src/enableInteractionGuards.test.ts +102 -0
  61. package/src/focusManager.test.ts +307 -0
  62. package/src/hitTests.test.ts +235 -98
  63. package/src/interactionManager.test.ts +59 -8
  64. package/src/interactionSpatialIndex.test.ts +103 -0
  65. package/src/nodeInteractionState.test.ts +141 -0
  66. package/src/registerBitmapHitTest.test.ts +22 -0
  67. package/src/{registerDefaultHitTestPoints.test.ts → registerDefaultHitTests.test.ts} +10 -7
  68. package/src/registerShapeHitTest.test.ts +27 -0
  69. package/src/registerTextHitTest.test.ts +54 -0
  70. package/src/spriteHitTests.test.ts +15 -15
  71. package/dist/registerDefaultHitTestPoints.d.ts +0 -2
  72. package/dist/registerDefaultHitTestPoints.d.ts.map +0 -1
  73. package/dist/registerDefaultHitTestPoints.js +0 -21
  74. package/dist/registerDefaultHitTestPoints.js.map +0 -1
@@ -0,0 +1,12 @@
1
+ import type { CursorBackend } from '@flighthq/types';
2
+ /**
3
+ * Web cursor backend: applies the resolved rollover cursor to a DOM element's `style.cursor`
4
+ * (typically the render canvas). `setCursor(null)` clears back to the element's default. Pass the
5
+ * result to `createInteractionManager` via `cursorBackend`, or assign it to `manager.cursorBackend`.
6
+ *
7
+ * The backend is held per manager rather than globally, so multiple canvases each drive their own
8
+ * cursor zone. Import side-effect-free: this only constructs the backend; nothing is applied until
9
+ * pointer dispatch resolves a rollover.
10
+ */
11
+ export declare function createWebCursorBackend(element: HTMLElement): CursorBackend;
12
+ //# sourceMappingURL=cursorBackend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursorBackend.d.ts","sourceRoot":"","sources":["../src/cursorBackend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE7D;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,aAAa,CAM1E"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Web cursor backend: applies the resolved rollover cursor to a DOM element's `style.cursor`
3
+ * (typically the render canvas). `setCursor(null)` clears back to the element's default. Pass the
4
+ * result to `createInteractionManager` via `cursorBackend`, or assign it to `manager.cursorBackend`.
5
+ *
6
+ * The backend is held per manager rather than globally, so multiple canvases each drive their own
7
+ * cursor zone. Import side-effect-free: this only constructs the backend; nothing is applied until
8
+ * pointer dispatch resolves a rollover.
9
+ */
10
+ export function createWebCursorBackend(element) {
11
+ return {
12
+ setCursor(cursor) {
13
+ element.style.cursor = cursor ?? '';
14
+ },
15
+ };
16
+ }
17
+ //# sourceMappingURL=cursorBackend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursorBackend.js","sourceRoot":"","sources":["../src/cursorBackend.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAoB;IACzD,OAAO;QACL,SAAS,CAAC,MAAqB;YAC7B,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;QACtC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -1,13 +1,12 @@
1
1
  import type { NodeAny } from '@flighthq/types';
2
- export declare function defaultBitmapHitTestPointHandler(source: NodeAny, x: number, y: number, _shapeFlag: boolean): boolean;
3
- export declare function defaultDisplayObjectHitTestPointHandler(_source: NodeAny, _x: number, _y: number, _shapeFlag: boolean): boolean;
4
- export declare function defaultHtmlViewHitTestPointHandler(_source: NodeAny, _x: number, _y: number, _shapeFlag: boolean): boolean;
5
- export declare function defaultMovieClipHitTestPointHandler(_source: NodeAny, _x: number, _y: number, _shapeFlag: boolean): boolean;
6
- export declare function defaultRenderViewHitTestPointHandler(source: NodeAny, x: number, y: number, _shapeFlag: boolean): boolean;
7
- export declare function defaultRichTextHitTestPointHandler(source: NodeAny, x: number, y: number, _shapeFlag: boolean): boolean;
8
- export declare function defaultShapeHitTestPointHandler(source: NodeAny, x: number, y: number, _shapeFlag: boolean): boolean;
9
- export declare function defaultStageHitTestPointHandler(_source: NodeAny, _x: number, _y: number, _shapeFlag: boolean): boolean;
10
- export declare function defaultTextHitTestPointHandler(source: NodeAny, x: number, y: number, _shapeFlag: boolean): boolean;
11
- export declare function defaultTextInputHitTestPointHandler(source: NodeAny, x: number, y: number, _shapeFlag: boolean): boolean;
12
- export declare function defaultVideoHitTestPointHandler(source: NodeAny, x: number, y: number, _shapeFlag: boolean): boolean;
2
+ export declare function defaultBitmapHitTestHandler(source: NodeAny, x: number, y: number): boolean;
3
+ export declare function defaultDisplayObjectHitTestHandler(_source: NodeAny, _x: number, _y: number): boolean;
4
+ export declare function defaultHtmlViewHitTestHandler(_source: NodeAny, _x: number, _y: number): boolean;
5
+ export declare function defaultMovieClipHitTestHandler(_source: NodeAny, _x: number, _y: number): boolean;
6
+ export declare function defaultRenderViewHitTestHandler(source: NodeAny, x: number, y: number): boolean;
7
+ export declare function defaultRichTextHitTestHandler(source: NodeAny, x: number, y: number): boolean;
8
+ export declare function defaultShapeHitTestHandler(source: NodeAny, x: number, y: number): boolean;
9
+ export declare function defaultTextHitTestHandler(source: NodeAny, x: number, y: number): boolean;
10
+ export declare function defaultTextInputHitTestHandler(source: NodeAny, x: number, y: number): boolean;
11
+ export declare function defaultVideoHitTestHandler(source: NodeAny, x: number, y: number): boolean;
13
12
  //# sourceMappingURL=displayHitTests.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"displayHitTests.d.ts","sourceRoot":"","sources":["../src/displayHitTests.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAI/C,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,OAAO,CAEpH;AAED,wBAAgB,uCAAuC,CACrD,OAAO,EAAE,OAAO,EAChB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,OAAO,GAClB,OAAO,CAET;AAED,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,OAAO,EAChB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,OAAO,GAClB,OAAO,CAGT;AAED,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,OAAO,EAChB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,OAAO,GAClB,OAAO,CAGT;AAED,wBAAgB,oCAAoC,CAClD,MAAM,EAAE,OAAO,EACf,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,UAAU,EAAE,OAAO,GAClB,OAAO,CAET;AAED,wBAAgB,kCAAkC,CAChD,MAAM,EAAE,OAAO,EACf,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,UAAU,EAAE,OAAO,GAClB,OAAO,CAET;AAED,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,OAAO,CAEnH;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,OAAO,EAChB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,OAAO,GAClB,OAAO,CAGT;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,OAAO,CAElH;AAED,wBAAgB,mCAAmC,CACjD,MAAM,EAAE,OAAO,EACf,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,UAAU,EAAE,OAAO,GAClB,OAAO,CAET;AAED,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,OAAO,CAEnH"}
1
+ {"version":3,"file":"displayHitTests.d.ts","sourceRoot":"","sources":["../src/displayHitTests.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAI/C,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAE1F;AAED,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAEpG;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAG/F;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAGhG;AAED,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAE9F;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAE5F;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAIzF;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAExF;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAE7F;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAEzF"}
@@ -1,38 +1,36 @@
1
1
  import { hitTestGraphLocalBounds } from './hitTests';
2
- export function defaultBitmapHitTestPointHandler(source, x, y, _shapeFlag) {
2
+ export function defaultBitmapHitTestHandler(source, x, y) {
3
3
  return hitTestGraphLocalBounds(source, x, y);
4
4
  }
5
- export function defaultDisplayObjectHitTestPointHandler(_source, _x, _y, _shapeFlag) {
5
+ export function defaultDisplayObjectHitTestHandler(_source, _x, _y) {
6
6
  return false;
7
7
  }
8
- export function defaultHtmlViewHitTestPointHandler(_source, _x, _y, _shapeFlag) {
8
+ export function defaultHtmlViewHitTestHandler(_source, _x, _y) {
9
9
  // HtmlView elements handle pointer events through the browser — not the canvas interaction system.
10
10
  return false;
11
11
  }
12
- export function defaultMovieClipHitTestPointHandler(_source, _x, _y, _shapeFlag) {
12
+ export function defaultMovieClipHitTestHandler(_source, _x, _y) {
13
13
  // Containers have no self hit area — findGraphHitTarget traverses children separately.
14
14
  return false;
15
15
  }
16
- export function defaultRenderViewHitTestPointHandler(source, x, y, _shapeFlag) {
16
+ export function defaultRenderViewHitTestHandler(source, x, y) {
17
17
  return hitTestGraphLocalBounds(source, x, y);
18
18
  }
19
- export function defaultRichTextHitTestPointHandler(source, x, y, _shapeFlag) {
19
+ export function defaultRichTextHitTestHandler(source, x, y) {
20
20
  return hitTestGraphLocalBounds(source, x, y);
21
21
  }
22
- export function defaultShapeHitTestPointHandler(source, x, y, _shapeFlag) {
22
+ export function defaultShapeHitTestHandler(source, x, y) {
23
+ // Coarse bounds box. Shape-accurate winding is opt-in via `registerShapeHitTest` so the
24
+ // default registrar stays free of `@flighthq/shape` / `@flighthq/path`.
23
25
  return hitTestGraphLocalBounds(source, x, y);
24
26
  }
25
- export function defaultStageHitTestPointHandler(_source, _x, _y, _shapeFlag) {
26
- // Containers have no self hit area — findGraphHitTarget traverses children separately.
27
- return false;
28
- }
29
- export function defaultTextHitTestPointHandler(source, x, y, _shapeFlag) {
27
+ export function defaultTextHitTestHandler(source, x, y) {
30
28
  return hitTestGraphLocalBounds(source, x, y);
31
29
  }
32
- export function defaultTextInputHitTestPointHandler(source, x, y, _shapeFlag) {
30
+ export function defaultTextInputHitTestHandler(source, x, y) {
33
31
  return hitTestGraphLocalBounds(source, x, y);
34
32
  }
35
- export function defaultVideoHitTestPointHandler(source, x, y, _shapeFlag) {
33
+ export function defaultVideoHitTestHandler(source, x, y) {
36
34
  return hitTestGraphLocalBounds(source, x, y);
37
35
  }
38
36
  //# sourceMappingURL=displayHitTests.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"displayHitTests.js","sourceRoot":"","sources":["../src/displayHitTests.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,UAAU,gCAAgC,CAAC,MAAe,EAAE,CAAS,EAAE,CAAS,EAAE,UAAmB;IACzG,OAAO,uBAAuB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,uCAAuC,CACrD,OAAgB,EAChB,EAAU,EACV,EAAU,EACV,UAAmB;IAEnB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,kCAAkC,CAChD,OAAgB,EAChB,EAAU,EACV,EAAU,EACV,UAAmB;IAEnB,mGAAmG;IACnG,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,mCAAmC,CACjD,OAAgB,EAChB,EAAU,EACV,EAAU,EACV,UAAmB;IAEnB,uFAAuF;IACvF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,oCAAoC,CAClD,MAAe,EACf,CAAS,EACT,CAAS,EACT,UAAmB;IAEnB,OAAO,uBAAuB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,kCAAkC,CAChD,MAAe,EACf,CAAS,EACT,CAAS,EACT,UAAmB;IAEnB,OAAO,uBAAuB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,+BAA+B,CAAC,MAAe,EAAE,CAAS,EAAE,CAAS,EAAE,UAAmB;IACxG,OAAO,uBAAuB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC7C,OAAgB,EAChB,EAAU,EACV,EAAU,EACV,UAAmB;IAEnB,uFAAuF;IACvF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,MAAe,EAAE,CAAS,EAAE,CAAS,EAAE,UAAmB;IACvG,OAAO,uBAAuB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,mCAAmC,CACjD,MAAe,EACf,CAAS,EACT,CAAS,EACT,UAAmB;IAEnB,OAAO,uBAAuB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,+BAA+B,CAAC,MAAe,EAAE,CAAS,EAAE,CAAS,EAAE,UAAmB;IACxG,OAAO,uBAAuB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC"}
1
+ {"version":3,"file":"displayHitTests.js","sourceRoot":"","sources":["../src/displayHitTests.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,UAAU,2BAA2B,CAAC,MAAe,EAAE,CAAS,EAAE,CAAS;IAC/E,OAAO,uBAAuB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,kCAAkC,CAAC,OAAgB,EAAE,EAAU,EAAE,EAAU;IACzF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,OAAgB,EAAE,EAAU,EAAE,EAAU;IACpF,mGAAmG;IACnG,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,OAAgB,EAAE,EAAU,EAAE,EAAU;IACrF,uFAAuF;IACvF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,+BAA+B,CAAC,MAAe,EAAE,CAAS,EAAE,CAAS;IACnF,OAAO,uBAAuB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,MAAe,EAAE,CAAS,EAAE,CAAS;IACjF,OAAO,uBAAuB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,MAAe,EAAE,CAAS,EAAE,CAAS;IAC9E,wFAAwF;IACxF,wEAAwE;IACxE,OAAO,uBAAuB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,MAAe,EAAE,CAAS,EAAE,CAAS;IAC7E,OAAO,uBAAuB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,MAAe,EAAE,CAAS,EAAE,CAAS;IAClF,OAAO,uBAAuB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,MAAe,EAAE,CAAS,EAAE,CAAS;IAC9E,OAAO,uBAAuB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { NodeAny } from '@flighthq/types';
2
+ /** Plain-data result of `explainInteractionHitEligibility`. */
3
+ export interface InteractionHitEligibility {
4
+ eligible: boolean;
5
+ hasEligibleInSubtree: boolean;
6
+ }
7
+ /** Uninstalls the guard installed by `enableInteractionGuards`. */
8
+ export declare function disableInteractionGuards(): void;
9
+ /**
10
+ * Installs the caller-facing interaction guard (opt-in, dev-only). It warns once — through
11
+ * `@flighthq/log` — when a pointer listener is connected to a node whose subtree contains no
12
+ * hit-testable node, i.e. the "I added a click handler but nothing fires" footgun under opt-in
13
+ * eligibility. Because hit testing is opt-in, this catches forgetting `setNodeHitTestEnabled`.
14
+ *
15
+ * Assumes opt-in precedes the connect (the recommended order); a node opted in *after* connecting a
16
+ * listener would warn spuriously. Not importing this module costs production nothing — the message and
17
+ * the `@flighthq/log` dependency live only here.
18
+ */
19
+ export declare function enableInteractionGuards(): void;
20
+ /** Reports whether a node is a hit candidate and whether any node in its subtree is — the seam a
21
+ * developer queries to understand why events do or do not reach a node. */
22
+ export declare function explainInteractionHitEligibility(node: Readonly<NodeAny>): InteractionHitEligibility;
23
+ //# sourceMappingURL=enableInteractionGuards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enableInteractionGuards.d.ts","sourceRoot":"","sources":["../src/enableInteractionGuards.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAyB,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAMtE,+DAA+D;AAC/D,MAAM,WAAW,yBAAyB;IAExC,QAAQ,EAAE,OAAO,CAAC;IAElB,oBAAoB,EAAE,OAAO,CAAC;CAC/B;AAED,mEAAmE;AACnE,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;2EAC2E;AAC3E,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,yBAAyB,CAKnG"}
@@ -0,0 +1,75 @@
1
+ import { logOnce } from '@flighthq/log';
2
+ import { getNodeRuntime } from '@flighthq/node';
3
+ import { LogLevel } from '@flighthq/types';
4
+ import { setInteractionConnectGuard } from './interactionManager';
5
+ import { isNodeFocusable, isNodeHitTestEnabled } from './nodeInteractionState';
6
+ /** Uninstalls the guard installed by `enableInteractionGuards`. */
7
+ export function disableInteractionGuards() {
8
+ setInteractionConnectGuard(null);
9
+ }
10
+ /**
11
+ * Installs the caller-facing interaction guard (opt-in, dev-only). It warns once — through
12
+ * `@flighthq/log` — when a pointer listener is connected to a node whose subtree contains no
13
+ * hit-testable node, i.e. the "I added a click handler but nothing fires" footgun under opt-in
14
+ * eligibility. Because hit testing is opt-in, this catches forgetting `setNodeHitTestEnabled`.
15
+ *
16
+ * Assumes opt-in precedes the connect (the recommended order); a node opted in *after* connecting a
17
+ * listener would warn spuriously. Not importing this module costs production nothing — the message and
18
+ * the `@flighthq/log` dependency live only here.
19
+ */
20
+ export function enableInteractionGuards() {
21
+ setInteractionConnectGuard(warnOnInertInteractionTarget);
22
+ }
23
+ /** Reports whether a node is a hit candidate and whether any node in its subtree is — the seam a
24
+ * developer queries to understand why events do or do not reach a node. */
25
+ export function explainInteractionHitEligibility(node) {
26
+ return {
27
+ eligible: isNodeHitTestEnabled(node),
28
+ hasEligibleInSubtree: hasEligibleNodeInSubtree(node),
29
+ };
30
+ }
31
+ function hasEligibleNodeInSubtree(node) {
32
+ if (isNodeHitTestEnabled(node))
33
+ return true;
34
+ const children = getNodeRuntime(node).children;
35
+ if (children !== null) {
36
+ for (const child of children) {
37
+ if (hasEligibleNodeInSubtree(child))
38
+ return true;
39
+ }
40
+ }
41
+ return false;
42
+ }
43
+ function hasFocusableNodeInSubtree(node) {
44
+ if (isNodeFocusable(node))
45
+ return true;
46
+ const children = getNodeRuntime(node).children;
47
+ if (children !== null) {
48
+ for (const child of children) {
49
+ if (hasFocusableNodeInSubtree(child))
50
+ return true;
51
+ }
52
+ }
53
+ return false;
54
+ }
55
+ function warnOnInertInteractionTarget(target, name) {
56
+ // Keyboard signals dispatch to the manager root regardless of eligibility, so they are exempt.
57
+ if (name === 'onKeyDown' || name === 'onKeyUp')
58
+ return;
59
+ // Focus signals dispatch from the focus manager based on `focusable`, not hit eligibility, so they get
60
+ // the symmetric check: warn when nothing in the subtree is a focus stop (forgot setNodeFocusable).
61
+ if (name === 'onFocusIn' || name === 'onFocusOut') {
62
+ if (hasFocusableNodeInSubtree(target))
63
+ return;
64
+ logOnce('interaction:focus-listener-on-non-focusable', LogLevel.Warn, {
65
+ message: `connectInteractionSignal('${name}'): target has no focusable node in its subtree — call setNodeFocusable(node, true) so it can receive focus events`,
66
+ }, 'interaction');
67
+ return;
68
+ }
69
+ if (hasEligibleNodeInSubtree(target))
70
+ return;
71
+ logOnce('interaction:listener-on-inert-node', LogLevel.Warn, {
72
+ message: `connectInteractionSignal('${name}'): target has no hit-testable node in its subtree — call setNodeHitTestEnabled(node, true) so it can receive pointer events`,
73
+ }, 'interaction');
74
+ }
75
+ //# sourceMappingURL=enableInteractionGuards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enableInteractionGuards.js","sourceRoot":"","sources":["../src/enableInteractionGuards.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAU/E,mEAAmE;AACnE,MAAM,UAAU,wBAAwB;IACtC,0BAA0B,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,uBAAuB;IACrC,0BAA0B,CAAC,4BAA4B,CAAC,CAAC;AAC3D,CAAC;AAED;2EAC2E;AAC3E,MAAM,UAAU,gCAAgC,CAAC,IAAuB;IACtE,OAAO;QACL,QAAQ,EAAE,oBAAoB,CAAC,IAAI,CAAC;QACpC,oBAAoB,EAAE,wBAAwB,CAAC,IAAI,CAAC;KACrD,CAAC;AACJ,CAAC;AAED,SAAS,wBAAwB,CAAC,IAAuB;IACvD,IAAI,oBAAoB,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC;IAC/C,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;YAC7B,IAAI,wBAAwB,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;QACnD,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,yBAAyB,CAAC,IAAuB;IACxD,IAAI,eAAe,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC;IAC/C,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;YAC7B,IAAI,yBAAyB,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;QACpD,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,4BAA4B,CAAC,MAAe,EAAE,IAA2B;IAChF,+FAA+F;IAC/F,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO;IAEvD,uGAAuG;IACvG,mGAAmG;IACnG,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;QAClD,IAAI,yBAAyB,CAAC,MAAM,CAAC;YAAE,OAAO;QAC9C,OAAO,CACL,6CAA6C,EAC7C,QAAQ,CAAC,IAAI,EACb;YACE,OAAO,EAAE,6BAA6B,IAAI,oHAAoH;SAC/J,EACD,aAAa,CACd,CAAC;QACF,OAAO;IACT,CAAC;IAED,IAAI,wBAAwB,CAAC,MAAM,CAAC;QAAE,OAAO;IAC7C,OAAO,CACL,oCAAoC,EACpC,QAAQ,CAAC,IAAI,EACb;QACE,OAAO,EAAE,6BAA6B,IAAI,8HAA8H;KACzK,EACD,aAAa,CACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,46 @@
1
+ import type { FocusDirection, FocusManager, FocusManagerOptions, FocusNavigationInput, FocusNavigationOptions, NodeAny } from '@flighthq/types';
2
+ /** Clears focus: the previously focused node (if any) receives `onFocusOut` and the manager holds none. */
3
+ export declare function clearFocus<N extends NodeAny>(manager: FocusManager<N>): void;
4
+ /**
5
+ * Wires a keyboard source into a focus manager: Tab advances focus, Shift+Tab retreats, and — when
6
+ * `options.arrowKeys` is set — the arrow keys drive directional (spatial) navigation. Returns a disconnect
7
+ * function. The manager owns focus state; this only translates keys into navigation calls, so an app can
8
+ * equally drive `focusNextNode` / `focusNodeInDirection` from its own input handling instead.
9
+ **/
10
+ export declare function connectFocusNavigation<N extends NodeAny>(input: FocusNavigationInput, manager: FocusManager<N>, options?: Readonly<FocusNavigationOptions>): () => void;
11
+ /** Allocates a focus manager for `root`. `wrap` (default `true`) cycles tab navigation past the ends. */
12
+ export declare function createFocusManager<N extends NodeAny>(root: N, options?: Readonly<FocusManagerOptions>): FocusManager<N>;
13
+ /**
14
+ * Moves focus to the next stop in tab order and returns it, or `null` if there is nowhere to go (no
15
+ * focusable nodes, or already at the last stop with `wrap` off). With nothing focused, focuses the first.
16
+ **/
17
+ export declare function focusNextNode<N extends NodeAny>(manager: FocusManager<N>): N | null;
18
+ /**
19
+ * Moves focus to the nearest focus stop in `direction` from the focused node's world-bounds center and
20
+ * returns it, or `null` when nothing is focused yet or no stop lies that way. The D-pad / analog-stick
21
+ * navigation model: candidates are scored by distance along the axis plus a perpendicular-offset penalty,
22
+ * so the most directly-aligned stop wins.
23
+ **/
24
+ export declare function focusNodeInDirection<N extends NodeAny>(manager: FocusManager<N>, direction: FocusDirection): N | null;
25
+ /** Moves focus to the previous stop in tab order; the reverse of `focusNextNode`. */
26
+ export declare function focusPreviousNode<N extends NodeAny>(manager: FocusManager<N>): N | null;
27
+ /** The node currently holding focus, or `null`. */
28
+ export declare function getFocusedNode<N extends NodeAny>(manager: FocusManager<N>): N | null;
29
+ /**
30
+ * Fills `out` (cleared first) with the manager's focus stops in tab order and returns it: every focusable,
31
+ * enabled node in the subtree, with explicit `tabIndex >= 0` stops sorted ascending ahead of the natural
32
+ * tree-order stops (`tabIndex -1`). A disabled node and its whole subtree are skipped. This is the engine
33
+ * primitive the navigation functions walk; call it directly to render a focus ring or inspect tab order.
34
+ **/
35
+ export declare function getFocusOrder<N extends NodeAny>(manager: FocusManager<N>, out?: N[]): N[];
36
+ /** Whether `node` is the manager's currently focused node. */
37
+ export declare function isNodeFocused<N extends NodeAny>(manager: FocusManager<N>, node: N): boolean;
38
+ /**
39
+ * Focuses a specific node (or `null` to clear focus), firing `onFocusOut` on the previously focused node
40
+ * and `onFocusIn` on the new one — both bubbling up to `root`, with `relatedTarget` set to the node on the
41
+ * other side of the change. Returns `false` without changing anything if `node` is non-null but not
42
+ * focusable (`setNodeFocusable` was never called); `true` otherwise, including a no-op refocus of the same
43
+ * node. `node` is assumed to live in the managed subtree; ancestry is not validated.
44
+ **/
45
+ export declare function setFocusedNode<N extends NodeAny>(manager: FocusManager<N>, node: N | null): boolean;
46
+ //# sourceMappingURL=focusManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focusManager.d.ts","sourceRoot":"","sources":["../src/focusManager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EAEd,YAAY,EACZ,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,EAEtB,OAAO,EAGR,MAAM,iBAAiB,CAAC;AAKzB,2GAA2G;AAC3G,wBAAgB,UAAU,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,CAE5E;AAED;;;;;IAKI;AACJ,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,OAAO,EACtD,KAAK,EAAE,oBAAoB,EAC3B,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EACxB,OAAO,GAAE,QAAQ,CAAC,sBAAsB,CAAM,GAC7C,MAAM,IAAI,CAcZ;AAED,yGAAyG;AACzG,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,OAAO,EAClD,IAAI,EAAE,CAAC,EACP,OAAO,GAAE,QAAQ,CAAC,mBAAmB,CAAM,GAC1C,YAAY,CAAC,CAAC,CAAC,CAMjB;AAED;;;IAGI;AACJ,wBAAgB,aAAa,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAEnF;AAED;;;;;IAKI;AACJ,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,cAAc,GAAG,CAAC,GAAG,IAAI,CAqBrH;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAEvF;AAED,mDAAmD;AACnD,wBAAgB,cAAc,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAEpF;AAED;;;;;IAKI;AACJ,wBAAgB,aAAa,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,GAAG,GAAE,CAAC,EAAO,GAAG,CAAC,EAAE,CAK7F;AAED,8DAA8D;AAC9D,wBAAgB,aAAa,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAE3F;AAED;;;;;;IAMI;AACJ,wBAAgB,cAAc,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,GAAG,OAAO,CASnG"}
@@ -0,0 +1,243 @@
1
+ import { getNodeParent, getNodeRuntime, getNodeWorldBoundsRectangle } from '@flighthq/node';
2
+ import { connectSignal, disconnectSignal, emitSignal } from '@flighthq/signals';
3
+ import { getInteractionSignals } from './interactionManager';
4
+ import { getNodeTabIndex, isNodeFocusable } from './nodeInteractionState';
5
+ /** Clears focus: the previously focused node (if any) receives `onFocusOut` and the manager holds none. */
6
+ export function clearFocus(manager) {
7
+ setFocusedNode(manager, null);
8
+ }
9
+ /**
10
+ * Wires a keyboard source into a focus manager: Tab advances focus, Shift+Tab retreats, and — when
11
+ * `options.arrowKeys` is set — the arrow keys drive directional (spatial) navigation. Returns a disconnect
12
+ * function. The manager owns focus state; this only translates keys into navigation calls, so an app can
13
+ * equally drive `focusNextNode` / `focusNodeInDirection` from its own input handling instead.
14
+ **/
15
+ export function connectFocusNavigation(input, manager, options = {}) {
16
+ const arrowKeys = options.arrowKeys ?? false;
17
+ const onKeyDown = (data) => {
18
+ if (data.key === 'Tab') {
19
+ if (data.shiftKey)
20
+ focusPreviousNode(manager);
21
+ else
22
+ focusNextNode(manager);
23
+ return;
24
+ }
25
+ if (!arrowKeys)
26
+ return;
27
+ const direction = arrowKeyDirection(data.key);
28
+ if (direction !== null)
29
+ focusNodeInDirection(manager, direction);
30
+ };
31
+ connectSignal(input.onKeyDown, onKeyDown);
32
+ return () => disconnectSignal(input.onKeyDown, onKeyDown);
33
+ }
34
+ /** Allocates a focus manager for `root`. `wrap` (default `true`) cycles tab navigation past the ends. */
35
+ export function createFocusManager(root, options = {}) {
36
+ return {
37
+ focused: null,
38
+ root,
39
+ wrap: options.wrap ?? true,
40
+ };
41
+ }
42
+ /**
43
+ * Moves focus to the next stop in tab order and returns it, or `null` if there is nowhere to go (no
44
+ * focusable nodes, or already at the last stop with `wrap` off). With nothing focused, focuses the first.
45
+ **/
46
+ export function focusNextNode(manager) {
47
+ return stepFocus(manager, 1);
48
+ }
49
+ /**
50
+ * Moves focus to the nearest focus stop in `direction` from the focused node's world-bounds center and
51
+ * returns it, or `null` when nothing is focused yet or no stop lies that way. The D-pad / analog-stick
52
+ * navigation model: candidates are scored by distance along the axis plus a perpendicular-offset penalty,
53
+ * so the most directly-aligned stop wins.
54
+ **/
55
+ export function focusNodeInDirection(manager, direction) {
56
+ const current = manager.focused;
57
+ if (current === null)
58
+ return null;
59
+ const order = getFocusOrder(manager, _orderScratch);
60
+ const origin = boundsCenter(current, _originCenter);
61
+ const originX = origin.x;
62
+ const originY = origin.y;
63
+ let best = null;
64
+ let bestScore = Number.POSITIVE_INFINITY;
65
+ for (const candidate of order) {
66
+ if (candidate === current)
67
+ continue;
68
+ const center = boundsCenter(candidate, _candidateCenter);
69
+ const score = directionScore(direction, originX, originY, center.x, center.y);
70
+ if (score < bestScore) {
71
+ bestScore = score;
72
+ best = candidate;
73
+ }
74
+ }
75
+ if (best !== null)
76
+ setFocusedNode(manager, best);
77
+ return best;
78
+ }
79
+ /** Moves focus to the previous stop in tab order; the reverse of `focusNextNode`. */
80
+ export function focusPreviousNode(manager) {
81
+ return stepFocus(manager, -1);
82
+ }
83
+ /** The node currently holding focus, or `null`. */
84
+ export function getFocusedNode(manager) {
85
+ return manager.focused;
86
+ }
87
+ /**
88
+ * Fills `out` (cleared first) with the manager's focus stops in tab order and returns it: every focusable,
89
+ * enabled node in the subtree, with explicit `tabIndex >= 0` stops sorted ascending ahead of the natural
90
+ * tree-order stops (`tabIndex -1`). A disabled node and its whole subtree are skipped. This is the engine
91
+ * primitive the navigation functions walk; call it directly to render a focus ring or inspect tab order.
92
+ **/
93
+ export function getFocusOrder(manager, out = []) {
94
+ out.length = 0;
95
+ collectFocusStops(manager.root, out);
96
+ out.sort(byTabIndexOrder);
97
+ return out;
98
+ }
99
+ /** Whether `node` is the manager's currently focused node. */
100
+ export function isNodeFocused(manager, node) {
101
+ return manager.focused === node;
102
+ }
103
+ /**
104
+ * Focuses a specific node (or `null` to clear focus), firing `onFocusOut` on the previously focused node
105
+ * and `onFocusIn` on the new one — both bubbling up to `root`, with `relatedTarget` set to the node on the
106
+ * other side of the change. Returns `false` without changing anything if `node` is non-null but not
107
+ * focusable (`setNodeFocusable` was never called); `true` otherwise, including a no-op refocus of the same
108
+ * node. `node` is assumed to live in the managed subtree; ancestry is not validated.
109
+ **/
110
+ export function setFocusedNode(manager, node) {
111
+ if (node !== null && !isNodeFocusable(node))
112
+ return false;
113
+ const previous = manager.focused;
114
+ if (node === previous)
115
+ return true;
116
+ manager.focused = node;
117
+ if (previous !== null)
118
+ emitFocusSignal(previous, manager.root, 'onFocusOut', node);
119
+ if (node !== null)
120
+ emitFocusSignal(node, manager.root, 'onFocusIn', previous);
121
+ return true;
122
+ }
123
+ function arrowKeyDirection(key) {
124
+ switch (key) {
125
+ case 'ArrowDown':
126
+ return 'down';
127
+ case 'ArrowLeft':
128
+ return 'left';
129
+ case 'ArrowRight':
130
+ return 'right';
131
+ case 'ArrowUp':
132
+ return 'up';
133
+ default:
134
+ return null;
135
+ }
136
+ }
137
+ // Writes the world-bounds center of a node into `out` and returns it. Directional navigation compares
138
+ // these centers; the node is treated as a 2D spatial node (all display objects are).
139
+ function boundsCenter(node, out) {
140
+ const bounds = getNodeWorldBoundsRectangle(node);
141
+ out.x = bounds.x + bounds.width / 2;
142
+ out.y = bounds.y + bounds.height / 2;
143
+ return out;
144
+ }
145
+ // Stable-sort comparator: explicit `tabIndex >= 0` stops ascending, then natural (`-1`) stops. A stable
146
+ // sort keeps the DFS (tree) order among equal keys, so natural stops stay in document order.
147
+ function byTabIndexOrder(a, b) {
148
+ return focusOrderKey(a) - focusOrderKey(b);
149
+ }
150
+ function collectFocusStops(node, out) {
151
+ if (!node.enabled)
152
+ return;
153
+ if (isNodeFocusable(node))
154
+ out.push(node);
155
+ const children = getNodeRuntime(node).children;
156
+ if (children !== null) {
157
+ for (const child of children)
158
+ collectFocusStops(child, out);
159
+ }
160
+ }
161
+ // Scores a candidate center against the origin for `direction`: distance along the axis plus twice the
162
+ // perpendicular offset (so aligned stops beat far-off-axis ones). Returns Infinity for anything not on the
163
+ // requested side, excluding it.
164
+ function directionScore(direction, ox, oy, cx, cy) {
165
+ const dx = cx - ox;
166
+ const dy = cy - oy;
167
+ let along;
168
+ let perpendicular;
169
+ switch (direction) {
170
+ case 'right':
171
+ along = dx;
172
+ perpendicular = dy;
173
+ break;
174
+ case 'left':
175
+ along = -dx;
176
+ perpendicular = dy;
177
+ break;
178
+ case 'down':
179
+ along = dy;
180
+ perpendicular = dx;
181
+ break;
182
+ default:
183
+ along = -dy;
184
+ perpendicular = dx;
185
+ break;
186
+ }
187
+ if (along <= DIRECTION_EPSILON)
188
+ return Number.POSITIVE_INFINITY;
189
+ return along + 2 * Math.abs(perpendicular);
190
+ }
191
+ // Bubbles a focus signal from `target` up to `root`, reusing the shared `_focusData` payload. Focus
192
+ // signals bubble (like pointer signals) but are not cancelable — there is no meaningful "prevent" for a
193
+ // focus change that has already happened.
194
+ function emitFocusSignal(target, root, name, relatedTarget) {
195
+ _focusData.relatedTarget = relatedTarget;
196
+ _focusData.target = target;
197
+ let current = target;
198
+ while (current !== null) {
199
+ _focusData.currentTarget = current;
200
+ const signals = getInteractionSignals(current);
201
+ const signal = signals !== null ? signals[name] : null;
202
+ if (signal !== null)
203
+ emitSignal(signal, _focusData);
204
+ if (current === root)
205
+ break;
206
+ current = getNodeParent(current);
207
+ }
208
+ }
209
+ function focusOrderKey(node) {
210
+ const tabIndex = getNodeTabIndex(node);
211
+ return tabIndex < 0 ? Number.POSITIVE_INFINITY : tabIndex;
212
+ }
213
+ // Advances focus by `delta` (+1 next, -1 previous) through the current focus order, wrapping when the
214
+ // manager allows it. Captures the target before emitting, so a slot re-entering navigation cannot corrupt
215
+ // the returned value. Returns the newly focused node, or `null` when there is nowhere to move.
216
+ function stepFocus(manager, delta) {
217
+ const order = getFocusOrder(manager, _orderScratch);
218
+ if (order.length === 0)
219
+ return null;
220
+ const current = manager.focused;
221
+ const from = current !== null ? order.indexOf(current) : -1;
222
+ let index;
223
+ if (from === -1) {
224
+ index = delta > 0 ? 0 : order.length - 1;
225
+ }
226
+ else {
227
+ index = from + delta;
228
+ if (index < 0 || index >= order.length) {
229
+ if (!manager.wrap)
230
+ return null;
231
+ index = (index + order.length) % order.length;
232
+ }
233
+ }
234
+ const next = order[index];
235
+ setFocusedNode(manager, next);
236
+ return next;
237
+ }
238
+ const DIRECTION_EPSILON = 0.0001;
239
+ const _candidateCenter = { x: 0, y: 0 };
240
+ const _focusData = { currentTarget: null, relatedTarget: null, target: null };
241
+ const _orderScratch = [];
242
+ const _originCenter = { x: 0, y: 0 };
243
+ //# sourceMappingURL=focusManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focusManager.js","sourceRoot":"","sources":["../src/focusManager.ts"],"names":[],"mappings":"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"}