@lightningtv/solid 3.0.0-2 → 3.0.0-21

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 (206) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +6 -0
  3. package/dist/src/activeElement.d.ts +1 -1
  4. package/dist/src/core/animation.d.ts +35 -0
  5. package/dist/src/core/animation.js +120 -0
  6. package/dist/src/core/animation.js.map +1 -0
  7. package/dist/src/core/config.d.ts +47 -0
  8. package/dist/src/core/config.js +23 -0
  9. package/dist/src/core/config.js.map +1 -0
  10. package/dist/src/core/domRenderer.d.ts +117 -0
  11. package/dist/src/core/domRenderer.js +1160 -0
  12. package/dist/src/core/domRenderer.js.map +1 -0
  13. package/dist/src/core/elementNode.d.ts +209 -0
  14. package/dist/src/core/elementNode.js +829 -0
  15. package/dist/src/core/elementNode.js.map +1 -0
  16. package/dist/src/core/flex.d.ts +2 -0
  17. package/dist/src/core/flex.js +243 -0
  18. package/dist/src/core/flex.js.map +1 -0
  19. package/dist/src/core/focusKeyTypes.d.ts +42 -0
  20. package/dist/src/core/focusKeyTypes.js +2 -0
  21. package/dist/src/core/focusKeyTypes.js.map +1 -0
  22. package/dist/src/core/focusManager.d.ts +13 -0
  23. package/dist/src/core/focusManager.js +269 -0
  24. package/dist/src/core/focusManager.js.map +1 -0
  25. package/dist/src/core/index.d.ts +12 -0
  26. package/dist/src/core/index.js +12 -0
  27. package/dist/src/core/index.js.map +1 -0
  28. package/dist/src/core/intrinsicTypes.d.ts +90 -0
  29. package/dist/src/core/intrinsicTypes.js +2 -0
  30. package/dist/src/core/intrinsicTypes.js.map +1 -0
  31. package/dist/src/core/lightningInit.d.ts +89 -0
  32. package/dist/src/core/lightningInit.js +26 -0
  33. package/dist/src/core/lightningInit.js.map +1 -0
  34. package/dist/src/core/nodeTypes.d.ts +6 -0
  35. package/dist/src/core/nodeTypes.js +6 -0
  36. package/dist/src/core/nodeTypes.js.map +1 -0
  37. package/dist/src/core/shaders.d.ts +51 -0
  38. package/dist/src/core/shaders.js +446 -0
  39. package/dist/src/core/shaders.js.map +1 -0
  40. package/dist/src/core/states.d.ts +12 -0
  41. package/dist/src/core/states.js +84 -0
  42. package/dist/src/core/states.js.map +1 -0
  43. package/dist/src/core/timings.d.ts +36 -0
  44. package/dist/src/core/timings.js +199 -0
  45. package/dist/src/core/timings.js.map +1 -0
  46. package/dist/src/core/utils.d.ts +39 -0
  47. package/dist/src/core/utils.js +164 -0
  48. package/dist/src/core/utils.js.map +1 -0
  49. package/dist/src/devtools/index.d.ts +1 -1
  50. package/dist/src/devtools/index.js +1 -1
  51. package/dist/src/devtools/index.js.map +1 -1
  52. package/dist/src/index.d.ts +3 -3
  53. package/dist/src/index.js +1 -1
  54. package/dist/src/index.js.map +1 -1
  55. package/dist/src/jsx-runtime.d.ts +1 -3
  56. package/dist/src/primitives/Column.jsx +9 -10
  57. package/dist/src/primitives/Column.jsx.map +1 -1
  58. package/dist/src/primitives/FPSCounter.jsx +14 -1
  59. package/dist/src/primitives/FPSCounter.jsx.map +1 -1
  60. package/dist/src/primitives/Grid.d.ts +15 -6
  61. package/dist/src/primitives/Grid.jsx +35 -22
  62. package/dist/src/primitives/Grid.jsx.map +1 -1
  63. package/dist/src/primitives/Image.d.ts +8 -0
  64. package/dist/src/primitives/Image.jsx +24 -0
  65. package/dist/src/primitives/Image.jsx.map +1 -0
  66. package/dist/src/primitives/KeepAlive.d.ts +30 -0
  67. package/dist/src/primitives/KeepAlive.jsx +77 -0
  68. package/dist/src/primitives/KeepAlive.jsx.map +1 -0
  69. package/dist/src/primitives/Lazy.d.ts +8 -7
  70. package/dist/src/primitives/Lazy.jsx +52 -23
  71. package/dist/src/primitives/Lazy.jsx.map +1 -1
  72. package/dist/src/primitives/Marquee.d.ts +64 -0
  73. package/dist/src/primitives/Marquee.jsx +86 -0
  74. package/dist/src/primitives/Marquee.jsx.map +1 -0
  75. package/dist/src/primitives/Preserve.d.ts +4 -0
  76. package/dist/src/primitives/Preserve.jsx +11 -0
  77. package/dist/src/primitives/Preserve.jsx.map +1 -0
  78. package/dist/src/primitives/Row.jsx +9 -10
  79. package/dist/src/primitives/Row.jsx.map +1 -1
  80. package/dist/src/primitives/Suspense.d.ts +22 -0
  81. package/dist/src/primitives/Suspense.jsx +33 -0
  82. package/dist/src/primitives/Suspense.jsx.map +1 -0
  83. package/dist/src/primitives/Virtual.d.ts +18 -0
  84. package/dist/src/primitives/Virtual.jsx +434 -0
  85. package/dist/src/primitives/Virtual.jsx.map +1 -0
  86. package/dist/src/primitives/VirtualGrid.d.ts +13 -0
  87. package/dist/src/primitives/VirtualGrid.jsx +160 -0
  88. package/dist/src/primitives/VirtualGrid.jsx.map +1 -0
  89. package/dist/src/primitives/VirtualList.d.ts +11 -0
  90. package/dist/src/primitives/VirtualList.jsx +96 -0
  91. package/dist/src/primitives/VirtualList.jsx.map +1 -0
  92. package/dist/src/primitives/VirtualRow.d.ts +13 -0
  93. package/dist/src/primitives/VirtualRow.jsx +97 -0
  94. package/dist/src/primitives/VirtualRow.jsx.map +1 -0
  95. package/dist/src/primitives/Visible.d.ts +0 -1
  96. package/dist/src/primitives/Visible.jsx +1 -1
  97. package/dist/src/primitives/Visible.jsx.map +1 -1
  98. package/dist/src/primitives/announcer/announcer.d.ts +2 -0
  99. package/dist/src/primitives/announcer/announcer.js +7 -5
  100. package/dist/src/primitives/announcer/announcer.js.map +1 -1
  101. package/dist/src/primitives/announcer/index.d.ts +5 -1
  102. package/dist/src/primitives/announcer/index.js +8 -2
  103. package/dist/src/primitives/announcer/index.js.map +1 -1
  104. package/dist/src/primitives/announcer/speech.d.ts +2 -2
  105. package/dist/src/primitives/announcer/speech.js +157 -28
  106. package/dist/src/primitives/announcer/speech.js.map +1 -1
  107. package/dist/src/primitives/createFocusStack.d.ts +4 -4
  108. package/dist/src/primitives/createFocusStack.jsx +15 -6
  109. package/dist/src/primitives/createFocusStack.jsx.map +1 -1
  110. package/dist/src/primitives/createTag.d.ts +8 -0
  111. package/dist/src/primitives/createTag.jsx +20 -0
  112. package/dist/src/primitives/createTag.jsx.map +1 -0
  113. package/dist/src/primitives/index.d.ts +14 -4
  114. package/dist/src/primitives/index.js +13 -3
  115. package/dist/src/primitives/index.js.map +1 -1
  116. package/dist/src/primitives/types.d.ts +5 -2
  117. package/dist/src/primitives/useFocusManager.d.ts +2 -2
  118. package/dist/src/primitives/useFocusManager.js +2 -2
  119. package/dist/src/primitives/useFocusManager.js.map +1 -1
  120. package/dist/src/primitives/useHold.d.ts +27 -0
  121. package/dist/src/primitives/useHold.js +54 -0
  122. package/dist/src/primitives/useHold.js.map +1 -0
  123. package/dist/src/primitives/useMouse.d.ts +18 -2
  124. package/dist/src/primitives/useMouse.js +171 -47
  125. package/dist/src/primitives/useMouse.js.map +1 -1
  126. package/dist/src/primitives/utils/chainFunctions.d.ts +30 -4
  127. package/dist/src/primitives/utils/chainFunctions.js +14 -3
  128. package/dist/src/primitives/utils/chainFunctions.js.map +1 -1
  129. package/dist/src/primitives/utils/createBlurredImage.d.ts +56 -0
  130. package/dist/src/primitives/utils/createBlurredImage.js +223 -0
  131. package/dist/src/primitives/utils/createBlurredImage.js.map +1 -0
  132. package/dist/src/primitives/utils/createSpriteMap.d.ts +2 -2
  133. package/dist/src/primitives/utils/createSpriteMap.js +1 -1
  134. package/dist/src/primitives/utils/createSpriteMap.js.map +1 -1
  135. package/dist/src/primitives/utils/handleNavigation.d.ts +79 -5
  136. package/dist/src/primitives/utils/handleNavigation.js +242 -69
  137. package/dist/src/primitives/utils/handleNavigation.js.map +1 -1
  138. package/dist/src/primitives/utils/withScrolling.d.ts +14 -2
  139. package/dist/src/primitives/utils/withScrolling.js +66 -7
  140. package/dist/src/primitives/utils/withScrolling.js.map +1 -1
  141. package/dist/src/render.d.ts +8 -7
  142. package/dist/src/render.js +5 -1
  143. package/dist/src/render.js.map +1 -1
  144. package/dist/src/solidOpts.d.ts +1 -7
  145. package/dist/src/solidOpts.js +32 -16
  146. package/dist/src/solidOpts.js.map +1 -1
  147. package/dist/src/types.d.ts +1 -13
  148. package/dist/src/universal.d.ts +25 -0
  149. package/dist/src/universal.js +232 -0
  150. package/dist/src/universal.js.map +1 -0
  151. package/dist/src/utils.d.ts +3 -1
  152. package/dist/src/utils.js +9 -1
  153. package/dist/src/utils.js.map +1 -1
  154. package/dist/tsconfig.tsbuildinfo +1 -1
  155. package/jsx-runtime.d.ts +2 -4
  156. package/package.json +17 -15
  157. package/src/activeElement.ts +1 -1
  158. package/src/core/animation.ts +183 -0
  159. package/src/core/config.ts +77 -0
  160. package/src/core/domRenderer.ts +1308 -0
  161. package/src/core/elementNode.ts +1198 -0
  162. package/src/core/flex.ts +284 -0
  163. package/src/core/focusKeyTypes.ts +87 -0
  164. package/src/core/focusManager.ts +359 -0
  165. package/src/core/index.ts +13 -0
  166. package/src/core/intrinsicTypes.ts +199 -0
  167. package/src/core/lightningInit.ts +147 -0
  168. package/src/core/nodeTypes.ts +6 -0
  169. package/src/core/shaders.ts +567 -0
  170. package/src/core/states.ts +91 -0
  171. package/src/core/timings.ts +261 -0
  172. package/src/core/utils.ts +222 -0
  173. package/src/devtools/index.ts +1 -1
  174. package/src/index.ts +3 -3
  175. package/src/primitives/Column.tsx +10 -12
  176. package/src/primitives/FPSCounter.tsx +15 -1
  177. package/src/primitives/Grid.tsx +57 -33
  178. package/src/primitives/Image.tsx +36 -0
  179. package/src/primitives/KeepAlive.tsx +124 -0
  180. package/src/primitives/Lazy.tsx +66 -37
  181. package/src/primitives/Marquee.tsx +149 -0
  182. package/src/primitives/Preserve.tsx +18 -0
  183. package/src/primitives/Row.tsx +13 -14
  184. package/src/primitives/Suspense.tsx +39 -0
  185. package/src/primitives/Virtual.tsx +478 -0
  186. package/src/primitives/VirtualGrid.tsx +220 -0
  187. package/src/primitives/Visible.tsx +1 -2
  188. package/src/primitives/announcer/announcer.ts +16 -10
  189. package/src/primitives/announcer/index.ts +12 -2
  190. package/src/primitives/announcer/speech.ts +188 -27
  191. package/src/primitives/createFocusStack.tsx +18 -7
  192. package/src/primitives/createTag.tsx +31 -0
  193. package/src/primitives/index.ts +18 -4
  194. package/src/primitives/types.ts +12 -2
  195. package/src/primitives/useFocusManager.ts +3 -3
  196. package/src/primitives/useHold.ts +69 -0
  197. package/src/primitives/useMouse.ts +306 -67
  198. package/src/primitives/utils/chainFunctions.ts +40 -9
  199. package/src/primitives/utils/createBlurredImage.ts +366 -0
  200. package/src/primitives/utils/createSpriteMap.ts +6 -4
  201. package/src/primitives/utils/handleNavigation.ts +300 -84
  202. package/src/primitives/utils/withScrolling.ts +91 -18
  203. package/src/render.ts +10 -8
  204. package/src/solidOpts.ts +31 -24
  205. package/src/types.ts +1 -15
  206. package/src/utils.ts +11 -1
@@ -0,0 +1,31 @@
1
+ import * as s from 'solid-js'
2
+ import * as lng from '@lightningtv/solid'
3
+
4
+ interface Destroyable {
5
+ (props: lng.NodeProps): s.JSX.Element;
6
+ destroy: () => void;
7
+ }
8
+
9
+ export function createTag(children: s.JSX.Element): Destroyable {
10
+ const [texture, setTexture] = s.createSignal<lng.Texture | null | undefined>(null);
11
+ const Tag = <view
12
+ display='flex'
13
+ onLayout={(n) => {
14
+ if (n.preFlexwidth && n.width !== n.preFlexwidth) {
15
+ n.rtt = true;
16
+ setTimeout(() => setTexture(n.texture), 1);
17
+ }
18
+ }}
19
+ parent={lng.rootNode} children={children}
20
+ textureOptions={{
21
+ preventCleanup: true
22
+ }} /> as any as lng.ElementNode
23
+ Tag.render(false);
24
+
25
+ const TagComponent = (props: lng.NodeProps) => {
26
+ return <view color={0xffffffff} autosize {...props} texture={texture()} />;
27
+ };
28
+ TagComponent.destroy = () => Tag.destroy();
29
+
30
+ return TagComponent;
31
+ }
@@ -4,6 +4,7 @@ export * from './createInfiniteItems.js';
4
4
  export * from './useMouse.js';
5
5
  export * from './portal.jsx';
6
6
  export * from './Lazy.jsx';
7
+ export * from './Image.jsx';
7
8
  export * from './Visible.jsx';
8
9
  export * from './router.js';
9
10
  export * from './Column.jsx';
@@ -11,11 +12,24 @@ export * from './Row.jsx';
11
12
  export * from './Grid.jsx';
12
13
  export * from './FPSCounter.jsx';
13
14
  export * from './FadeInOut.jsx';
15
+ export * from './Preserve.jsx';
16
+ export * from './Suspense.jsx';
17
+ export * from './Marquee.jsx';
14
18
  export * from './createFocusStack.jsx';
15
- export { withScrolling } from './utils/withScrolling.js';
16
- export { chainFunctions } from './utils/chainFunctions.js';
17
- export { handleNavigation, onGridFocus } from './utils/handleNavigation.js';
19
+ export * from './useHold.js';
20
+ export * from './KeepAlive.jsx';
21
+ export * from './VirtualGrid.jsx';
22
+ export * from './Virtual.jsx';
23
+ export * from './utils/withScrolling.js';
24
+ export * from './createTag.jsx';
25
+ export {
26
+ type AnyFunction,
27
+ chainFunctions,
28
+ chainRefs,
29
+ } from './utils/chainFunctions.js';
30
+ export * from './utils/handleNavigation.js';
18
31
  export { createSpriteMap, type SpriteDef } from './utils/createSpriteMap.js';
32
+ export { createBlurredImage } from './utils/createBlurredImage.js';
19
33
 
20
34
  export type * from './types.js';
21
- export type { KeyHandler } from '@lightningtv/core/focusManager';
35
+ export type { KeyHandler } from '../core/focusManager.js';
@@ -1,5 +1,6 @@
1
- import type { ElementNode, NodeProps, NodeStyles } from '@lightningtv/solid';
2
- import type { KeyHandler } from '@lightningtv/core/focusManager';
1
+ import type { ElementNode, NodeProps, NodeStyles } from '../index.js';
2
+ import type { KeyHandler } from '../core/focusManager.js';
3
+
3
4
  export type OnSelectedChanged = (
4
5
  this: NavigableElement,
5
6
  selectedIndex: number,
@@ -7,6 +8,7 @@ export type OnSelectedChanged = (
7
8
  active: ElementNode,
8
9
  lastSelectedIndex?: number,
9
10
  ) => void;
11
+
10
12
  export interface NavigableProps extends NodeProps {
11
13
  /** function to be called when the selected of the component changes */
12
14
  onSelectedChanged?: OnSelectedChanged;
@@ -40,11 +42,19 @@ export interface NavigableProps extends NodeProps {
40
42
  * Wrap the row so active goes back to the beginning of the row
41
43
  */
42
44
  wrap?: boolean;
45
+
46
+ /** function to be called when scrolled */
47
+ onScrolled?: (
48
+ elm: NavigableElement,
49
+ offset: number,
50
+ isInitial: boolean,
51
+ ) => void;
43
52
  }
44
53
 
45
54
  // @ts-expect-error animationSettings is not identical - weird
46
55
  export interface NavigableElement extends ElementNode, NavigableProps {
47
56
  selected: number;
57
+ scrollToIndex: (this: NavigableElement, index: number) => void;
48
58
  }
49
59
 
50
60
  export interface NavigableStyleProperties {
@@ -6,13 +6,13 @@ import {
6
6
  getOwner,
7
7
  runWithOwner,
8
8
  } from 'solid-js';
9
- import { Config } from '@lightningtv/core';
10
- import type { ElementNode } from '@lightningtv/core';
9
+ import { Config } from '../core/index.js';
10
+ import type { ElementNode } from '../core/index.js';
11
11
  import {
12
12
  useFocusManager as useFocusManagerCore,
13
13
  type KeyMap,
14
14
  type KeyHoldOptions,
15
- } from '@lightningtv/core/focusManager';
15
+ } from '../core/focusManager.js';
16
16
  import { activeElement, setActiveElement } from '../activeElement.js';
17
17
 
18
18
  const [focusPath, setFocusPath] = createSignal<ElementNode[]>([]);
@@ -0,0 +1,69 @@
1
+ import { createMemo } from 'solid-js';
2
+
3
+ export type UseHoldProps = {
4
+ onHold: () => void;
5
+ onEnter: () => void;
6
+ onRelease?: () => void;
7
+ holdThreshold?: number;
8
+ performOnEnterImmediately?: boolean;
9
+ };
10
+
11
+ /**
12
+ * @example
13
+ * const [holdRight, releaseRight] = useHold({
14
+ * onHold: handleHoldRight,
15
+ * onEnter: handleOnRight,
16
+ * onRelease: handleReleaseHold,
17
+ * holdThreshold: 200,
18
+ * performOnEnterImmediately: true
19
+ * });
20
+ *
21
+ * <View
22
+ * onRight={holdRight}
23
+ * onRightRelease={releaseRight}
24
+ * />
25
+ *
26
+ * @param {UseHoldProps} props - The properties for configuring the hold behavior.
27
+ * @returns {[() => boolean, () => boolean]} A tuple containing `startHold` and `releaseHold` functions.
28
+ */
29
+
30
+ export function useHold(props: UseHoldProps) {
31
+ const holdThreshold = createMemo(() => props.holdThreshold ?? 500);
32
+ const performOnEnterImmediately = createMemo(
33
+ () => props.performOnEnterImmediately ?? false,
34
+ );
35
+
36
+ let holdTimeout = -1;
37
+ let wasHeld = false;
38
+
39
+ const startHold = () => {
40
+ if (holdTimeout === -1) {
41
+ if (performOnEnterImmediately()) {
42
+ props.onEnter();
43
+ }
44
+ holdTimeout = setTimeout(() => {
45
+ wasHeld = true;
46
+ props.onHold();
47
+ }, holdThreshold()) as unknown as number;
48
+ }
49
+ return true;
50
+ };
51
+
52
+ const releaseHold = () => {
53
+ if (holdTimeout !== -1) {
54
+ clearTimeout(holdTimeout);
55
+ holdTimeout = -1;
56
+ if (!wasHeld) {
57
+ if (!performOnEnterImmediately()) props.onEnter();
58
+ return;
59
+ }
60
+ props.onRelease?.();
61
+ wasHeld = false;
62
+ }
63
+ return true;
64
+ };
65
+
66
+ return [startHold, releaseHold];
67
+ }
68
+
69
+ export default useHold;
@@ -1,16 +1,51 @@
1
- import type { ElementText, INode, TextNode } from '@lightningtv/core';
1
+ import type { ElementText, TextNode } from '../core/index.js';
2
2
  import {
3
+ Config,
3
4
  ElementNode,
4
5
  activeElement,
5
6
  isElementNode,
7
+ isFunc,
6
8
  isTextNode,
7
9
  rootNode,
8
- Config,
9
- } from '@lightningtv/solid';
10
+ } from '../index.js';
10
11
  import { makeEventListener } from '@solid-primitives/event-listener';
11
12
  import { useMousePosition } from '@solid-primitives/mouse';
12
13
  import { createScheduled, throttle } from '@solid-primitives/scheduled';
13
- import { createEffect } from 'solid-js';
14
+ import { createEffect, getOwner, runWithOwner } from 'solid-js';
15
+
16
+ type CustomState = `$${string}`;
17
+
18
+ type RenderableNode = ElementNode | ElementText | TextNode;
19
+
20
+ interface MouseStateOptions {
21
+ hoverState: CustomState;
22
+ pressedState: CustomState;
23
+ }
24
+
25
+ type UseMouseOptions =
26
+ | { customStates: MouseStateOptions }
27
+ | { customStates: undefined };
28
+
29
+ export function addCustomStateToElement(
30
+ element: RenderableNode,
31
+ state: CustomState,
32
+ ): void {
33
+ element.states?.add(state);
34
+ }
35
+
36
+ export function removeCustomStateFromElement(
37
+ element: RenderableNode,
38
+ state: CustomState,
39
+ ): void {
40
+ element?.states?.remove(state);
41
+ }
42
+
43
+ export function hasCustomState(
44
+ element: RenderableNode,
45
+ state: CustomState,
46
+ ): boolean {
47
+ return element.states?.has(state);
48
+ }
14
49
 
15
50
  function createKeyboardEvent(
16
51
  key: string,
@@ -29,6 +64,7 @@ function createKeyboardEvent(
29
64
  });
30
65
  }
31
66
 
67
+ let scrollTimeout: ReturnType<typeof setTimeout>;
32
68
  const handleScroll = throttle((e: WheelEvent): void => {
33
69
  const deltaY = e.deltaY;
34
70
  if (deltaY < 0) {
@@ -36,30 +72,174 @@ const handleScroll = throttle((e: WheelEvent): void => {
36
72
  } else if (deltaY > 0) {
37
73
  document.body.dispatchEvent(createKeyboardEvent('ArrowDown', 40));
38
74
  }
75
+
76
+ // clear the last timeout if the user is still scrolling
77
+ clearTimeout(scrollTimeout);
78
+ // after 250ms of no scroll events, we send a keyup event to stop the scrolling
79
+ scrollTimeout = setTimeout(() => {
80
+ document.body.dispatchEvent(createKeyboardEvent('ArrowUp', 38, 'keyup'));
81
+ document.body.dispatchEvent(createKeyboardEvent('ArrowDown', 40, 'keyup'));
82
+ }, 250);
39
83
  }, 250);
40
84
 
41
- const handleClick = (e: MouseEvent): void => {
85
+ function findElementWithCustomState<TApp extends ElementNode>(
86
+ myApp: TApp,
87
+ x: number,
88
+ y: number,
89
+ customState: CustomState,
90
+ ): ElementNode | undefined {
91
+ const result = getChildrenByPosition(myApp, x, y).filter((el) =>
92
+ hasCustomState(el, customState),
93
+ );
94
+
95
+ if (result.length === 0) {
96
+ return undefined;
97
+ }
98
+
99
+ let element: ElementNode | undefined = result[result.length - 1];
100
+
101
+ while (element) {
102
+ const elmParent = element.parent;
103
+ if (elmParent?.forwardStates && hasCustomState(elmParent, customState)) {
104
+ element = elmParent;
105
+ } else {
106
+ break;
107
+ }
108
+ }
109
+
110
+ return element;
111
+ }
112
+
113
+ function findElementByActiveElement(e: MouseEvent): ElementNode | null {
42
114
  const active = activeElement();
43
115
  const precision = Config.rendererOptions?.deviceLogicalPixelRatio || 1;
116
+
44
117
  if (
45
118
  active instanceof ElementNode &&
46
119
  testCollision(
47
120
  e.clientX,
48
121
  e.clientY,
49
- (active.lng.absX as number) || 0 * precision,
50
- (active.lng.absY as number) || 0 * precision,
51
- active.width || 0 * precision,
52
- active.height || 0 * precision,
122
+ ((active.lng.absX as number) || 0) * precision,
123
+ ((active.lng.absY as number) || 0) * precision,
124
+ (active.width || 0) * precision,
125
+ (active.height || 0) * precision,
53
126
  )
54
127
  ) {
128
+ return active;
129
+ }
130
+
131
+ let parent = active?.parent;
132
+ while (parent) {
133
+ if (
134
+ isFunc(parent.onMouseClick) &&
135
+ active &&
136
+ testCollision(
137
+ e.clientX,
138
+ e.clientY,
139
+ ((parent.lng.absX as number) || 0) * precision,
140
+ ((parent.lng.absY as number) || 0) * precision,
141
+ (parent.width || 0) * precision,
142
+ (parent.height || 0) * precision,
143
+ )
144
+ ) {
145
+ return parent;
146
+ }
147
+ parent = parent.parent;
148
+ }
149
+
150
+ return null;
151
+ }
152
+
153
+ function applyPressedState(
154
+ element: ElementNode,
155
+ pressedState: CustomState,
156
+ ): void {
157
+ addCustomStateToElement(element, pressedState);
158
+ }
159
+
160
+ function handleElementClick(
161
+ clickedElement: ElementNode,
162
+ e: MouseEvent,
163
+ customStates?: MouseStateOptions,
164
+ pressedElementRef?: { current: ElementNode | null },
165
+ ): void {
166
+ if (customStates?.pressedState && pressedElementRef?.current) {
167
+ removeCustomStateFromElement(
168
+ pressedElementRef.current,
169
+ customStates.pressedState,
170
+ );
171
+ pressedElementRef.current = null;
172
+ }
173
+
174
+ if (isFunc(clickedElement.onMouseClick)) {
175
+ clickedElement.onMouseClick(e, clickedElement);
176
+ return;
177
+ } else if (isFunc(clickedElement.onEnter)) {
178
+ clickedElement.onEnter();
179
+ return;
180
+ }
181
+
182
+ clickedElement.setFocus();
183
+ setTimeout(() => {
55
184
  document.dispatchEvent(createKeyboardEvent('Enter', 13));
56
185
  setTimeout(
57
186
  () =>
58
187
  document.body.dispatchEvent(createKeyboardEvent('Enter', 13, 'keyup')),
59
188
  1,
60
189
  );
61
- }
62
- };
190
+ }, 1);
191
+ }
192
+
193
+ function createHandleClick<TApp extends ElementNode>(
194
+ myApp: TApp,
195
+ customStates?: MouseStateOptions,
196
+ pressedElementRef?: { current: ElementNode | null },
197
+ ) {
198
+ return (e: MouseEvent): void => {
199
+ const clickedElement = customStates
200
+ ? findElementWithCustomState(
201
+ myApp,
202
+ e.clientX,
203
+ e.clientY,
204
+ customStates.hoverState,
205
+ )
206
+ : findElementByActiveElement(e);
207
+
208
+ if (!clickedElement) {
209
+ return;
210
+ }
211
+
212
+ handleElementClick(clickedElement, e, customStates, pressedElementRef);
213
+ };
214
+ }
215
+
216
+ function createHandleMouseDown<TApp extends ElementNode>(
217
+ myApp: TApp,
218
+ customStates?: MouseStateOptions,
219
+ pressedElementRef?: { current: ElementNode | null },
220
+ ) {
221
+ return (e: MouseEvent): void => {
222
+ if (!customStates) {
223
+ return;
224
+ }
225
+
226
+ const pressedElement = findElementWithCustomState(
227
+ myApp,
228
+ e.clientX,
229
+ e.clientY,
230
+ customStates.hoverState,
231
+ );
232
+
233
+ if (!pressedElement) {
234
+ return;
235
+ }
236
+
237
+ applyPressedState(pressedElement, customStates.pressedState);
238
+ if (pressedElementRef) {
239
+ pressedElementRef.current = pressedElement;
240
+ }
241
+ };
242
+ }
63
243
 
64
244
  function testCollision(
65
245
  px: number,
@@ -72,106 +252,165 @@ function testCollision(
72
252
  return px >= cx && px <= cx + cw && py >= cy && py <= cy + ch;
73
253
  }
74
254
 
75
- function getChildrenByPosition(
76
- node: ElementNode,
255
+ function isNodeAtPosition(
256
+ node: ElementNode | ElementText | TextNode,
77
257
  x: number,
78
258
  y: number,
79
- ): ElementNode[] {
80
- const result: ElementNode[] = [];
81
- const precision = Config.rendererOptions?.deviceLogicalPixelRatio || 1;
259
+ precision: number,
260
+ ): node is ElementNode {
261
+ if (!isElementNode(node)) {
262
+ return false;
263
+ }
264
+
265
+ return (
266
+ node.alpha !== 0 &&
267
+ !node.skipFocus &&
268
+ testCollision(
269
+ x,
270
+ y,
271
+ ((node.lng.absX as number) || 0) * precision,
272
+ ((node.lng.absY as number) || 0) * precision,
273
+ (node.width || 0) * precision,
274
+ (node.height || 0) * precision,
275
+ )
276
+ );
277
+ }
278
+
279
+ function findHighestZIndexNode(nodes: ElementNode[]): ElementNode | undefined {
280
+ if (nodes.length === 0) {
281
+ return undefined;
282
+ }
283
+
284
+ if (nodes.length === 1) {
285
+ return nodes[0];
286
+ }
287
+
288
+ let maxZIndex = -1;
289
+ let highestNode: ElementNode | undefined = undefined;
290
+
291
+ for (const node of nodes) {
292
+ const zIndex = node.zIndex ?? -1;
293
+ if (zIndex >= maxZIndex) {
294
+ maxZIndex = zIndex;
295
+ highestNode = node;
296
+ }
297
+ }
298
+
299
+ return highestNode;
300
+ }
82
301
 
302
+ function getChildrenByPosition<TElement extends ElementNode = ElementNode>(
303
+ node: TElement,
304
+ x: number,
305
+ y: number,
306
+ ): TElement[] {
307
+ const result: TElement[] = [];
308
+ const precision = Config.rendererOptions?.deviceLogicalPixelRatio || 1;
83
309
  // Queue for BFS
310
+
84
311
  let queue: (ElementNode | ElementText | TextNode)[] = [node];
85
312
 
86
313
  while (queue.length > 0) {
87
314
  // Process nodes at the current level
88
- const currentLevelNodes: ElementNode[] = [];
89
-
90
- for (const currentNode of queue) {
91
- if (
92
- isElementNode(currentNode) &&
93
- currentNode.alpha !== 0 &&
94
- !currentNode.skipFocus &&
95
- testCollision(
96
- x,
97
- y,
98
- (currentNode.lng.absX as number) || 0 * precision,
99
- (currentNode.lng.absY as number) || 0 * precision,
100
- (currentNode.width || 0) * precision,
101
- (currentNode.height || 0) * precision,
102
- )
103
- ) {
104
- currentLevelNodes.push(currentNode);
105
- }
106
- }
315
+ const currentLevelNodes = queue.filter((currentNode) =>
316
+ isNodeAtPosition(currentNode, x, y, precision),
317
+ );
107
318
 
108
- const size = currentLevelNodes.length;
109
- if (size === 0) {
319
+ if (currentLevelNodes.length === 0) {
110
320
  break;
111
321
  }
112
322
 
113
- let highestZIndexNode = null;
114
- if (size === 1) {
115
- highestZIndexNode = currentLevelNodes[0];
116
- } else {
117
- let maxZIndex = -1;
118
-
119
- for (const node of currentLevelNodes) {
120
- const zIndex = node.zIndex ?? -1;
121
- if (zIndex > maxZIndex) {
122
- maxZIndex = zIndex;
123
- highestZIndexNode = node;
124
- } else if (zIndex === maxZIndex) {
125
- highestZIndexNode = node;
126
- }
127
- }
128
- }
323
+ const highestZIndexNode = findHighestZIndexNode(currentLevelNodes);
129
324
 
130
- if (highestZIndexNode && !isTextNode(highestZIndexNode)) {
131
- result.push(highestZIndexNode);
132
- queue = highestZIndexNode.children;
133
- } else {
134
- queue = [];
325
+ if (!highestZIndexNode || isTextNode(highestZIndexNode)) {
326
+ break;
135
327
  }
328
+
329
+ result.push(highestZIndexNode as TElement);
330
+ queue = highestZIndexNode.children;
136
331
  }
137
332
 
138
333
  return result;
139
334
  }
140
335
 
141
- export function useMouse(
142
- myApp: ElementNode = rootNode,
336
+ export function useMouse<TApp extends ElementNode = ElementNode>(
337
+ myApp: TApp = rootNode as TApp,
143
338
  throttleBy: number = 100,
339
+ options?: UseMouseOptions,
144
340
  ): void {
145
341
  const pos = useMousePosition();
146
342
  const scheduled = createScheduled((fn) => throttle(fn, throttleBy));
343
+ let previousElement: ElementNode | null = null;
344
+ const pressedElementRef: { current: ElementNode | null } = { current: null };
345
+ const customStates = options?.customStates;
346
+ const hoverState = customStates?.hoverState;
347
+ const handleClick = createHandleClick(myApp, customStates, pressedElementRef);
348
+ const handleMouseDown = createHandleMouseDown(
349
+ myApp,
350
+ customStates,
351
+ pressedElementRef,
352
+ );
353
+ const owner = getOwner();
354
+ const handleClickContext = (e: MouseEvent) => {
355
+ runWithOwner(owner, () => handleClick(e));
356
+ };
357
+ const handleMouseDownContext = (e: MouseEvent) => {
358
+ runWithOwner(owner, () => handleMouseDown(e));
359
+ };
360
+
147
361
  makeEventListener(window, 'wheel', handleScroll);
148
- makeEventListener(window, 'click', handleClick);
362
+ makeEventListener(window, 'click', handleClickContext);
363
+ makeEventListener(window, 'mousedown', handleMouseDownContext);
149
364
  createEffect(() => {
150
365
  if (scheduled()) {
151
366
  const result = getChildrenByPosition(myApp, pos.x, pos.y).filter(
152
- (el) => el.focus || el.onFocus || el.onEnter,
367
+ (el) =>
368
+ !!(
369
+ el.onEnter ||
370
+ el.onMouseClick ||
371
+ el.onFocus ||
372
+ el[Config.focusStateKey] ||
373
+ (hoverState ? el[hoverState] : false)
374
+ ),
153
375
  );
154
376
 
155
377
  if (result.length) {
156
- let activeElm = result[result.length - 1];
378
+ let activeElm: ElementNode | undefined = result[result.length - 1];
157
379
 
158
380
  while (activeElm) {
159
381
  const elmParent = activeElm.parent;
160
382
  if (elmParent?.forwardStates) {
161
- activeElm = activeElm.parent;
383
+ activeElm = elmParent;
162
384
  } else {
163
385
  break;
164
386
  }
165
387
  }
166
388
 
389
+ if (!activeElm) {
390
+ return;
391
+ }
392
+
167
393
  // Update Row & Column Selected property
168
- const activeElmParent = activeElm?.parent;
169
- if (activeElm && activeElmParent?.selected !== undefined) {
394
+ const activeElmParent = activeElm.parent;
395
+ if (activeElmParent?.selected !== undefined) {
170
396
  activeElmParent.selected =
171
397
  activeElmParent.children.indexOf(activeElm);
172
398
  }
173
399
 
174
- activeElm?.setFocus();
400
+ if (previousElement && previousElement !== activeElm && hoverState) {
401
+ removeCustomStateFromElement(previousElement, hoverState);
402
+ }
403
+
404
+ if (hoverState) {
405
+ addCustomStateToElement(activeElm, hoverState);
406
+ } else {
407
+ activeElm.setFocus();
408
+ }
409
+
410
+ previousElement = activeElm;
411
+ } else if (previousElement && hoverState) {
412
+ removeCustomStateFromElement(previousElement, hoverState);
413
+ previousElement = null;
175
414
  }
176
415
  }
177
416
  });