@lightningtv/solid 3.0.0-2 → 3.0.0-20
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/LICENSE +1 -1
- package/README.md +6 -0
- package/dist/src/jsx-runtime.d.ts +1 -3
- package/dist/src/primitives/Column.jsx +9 -10
- package/dist/src/primitives/Column.jsx.map +1 -1
- package/dist/src/primitives/Grid.d.ts +15 -6
- package/dist/src/primitives/Grid.jsx +35 -22
- package/dist/src/primitives/Grid.jsx.map +1 -1
- package/dist/src/primitives/Image.d.ts +8 -0
- package/dist/src/primitives/Image.jsx +24 -0
- package/dist/src/primitives/Image.jsx.map +1 -0
- package/dist/src/primitives/KeepAlive.d.ts +30 -0
- package/dist/src/primitives/KeepAlive.jsx +77 -0
- package/dist/src/primitives/KeepAlive.jsx.map +1 -0
- package/dist/src/primitives/Lazy.d.ts +8 -7
- package/dist/src/primitives/Lazy.jsx +49 -23
- package/dist/src/primitives/Lazy.jsx.map +1 -1
- package/dist/src/primitives/Marquee.d.ts +64 -0
- package/dist/src/primitives/Marquee.jsx +86 -0
- package/dist/src/primitives/Marquee.jsx.map +1 -0
- package/dist/src/primitives/Preserve.d.ts +4 -0
- package/dist/src/primitives/Preserve.jsx +11 -0
- package/dist/src/primitives/Preserve.jsx.map +1 -0
- package/dist/src/primitives/Row.jsx +9 -10
- package/dist/src/primitives/Row.jsx.map +1 -1
- package/dist/src/primitives/Suspense.d.ts +22 -0
- package/dist/src/primitives/Suspense.jsx +33 -0
- package/dist/src/primitives/Suspense.jsx.map +1 -0
- package/dist/src/primitives/Virtual.d.ts +18 -0
- package/dist/src/primitives/Virtual.jsx +434 -0
- package/dist/src/primitives/Virtual.jsx.map +1 -0
- package/dist/src/primitives/VirtualGrid.d.ts +13 -0
- package/dist/src/primitives/VirtualGrid.jsx +139 -0
- package/dist/src/primitives/VirtualGrid.jsx.map +1 -0
- package/dist/src/primitives/VirtualList.d.ts +11 -0
- package/dist/src/primitives/VirtualList.jsx +96 -0
- package/dist/src/primitives/VirtualList.jsx.map +1 -0
- package/dist/src/primitives/VirtualRow.d.ts +13 -0
- package/dist/src/primitives/VirtualRow.jsx +97 -0
- package/dist/src/primitives/VirtualRow.jsx.map +1 -0
- package/dist/src/primitives/Visible.d.ts +0 -1
- package/dist/src/primitives/Visible.jsx +1 -1
- package/dist/src/primitives/Visible.jsx.map +1 -1
- package/dist/src/primitives/announcer/announcer.d.ts +2 -0
- package/dist/src/primitives/announcer/announcer.js +7 -5
- package/dist/src/primitives/announcer/announcer.js.map +1 -1
- package/dist/src/primitives/announcer/index.d.ts +5 -1
- package/dist/src/primitives/announcer/index.js +8 -2
- package/dist/src/primitives/announcer/index.js.map +1 -1
- package/dist/src/primitives/announcer/speech.d.ts +2 -2
- package/dist/src/primitives/announcer/speech.js +157 -28
- package/dist/src/primitives/announcer/speech.js.map +1 -1
- package/dist/src/primitives/createFocusStack.d.ts +4 -4
- package/dist/src/primitives/createFocusStack.jsx +15 -6
- package/dist/src/primitives/createFocusStack.jsx.map +1 -1
- package/dist/src/primitives/createTag.d.ts +8 -0
- package/dist/src/primitives/createTag.jsx +20 -0
- package/dist/src/primitives/createTag.jsx.map +1 -0
- package/dist/src/primitives/index.d.ts +13 -3
- package/dist/src/primitives/index.js +13 -3
- package/dist/src/primitives/index.js.map +1 -1
- package/dist/src/primitives/types.d.ts +3 -0
- package/dist/src/primitives/useHold.d.ts +27 -0
- package/dist/src/primitives/useHold.js +54 -0
- package/dist/src/primitives/useHold.js.map +1 -0
- package/dist/src/primitives/useMouse.d.ts +24 -1
- package/dist/src/primitives/useMouse.js +153 -47
- package/dist/src/primitives/useMouse.js.map +1 -1
- package/dist/src/primitives/utils/chainFunctions.d.ts +30 -4
- package/dist/src/primitives/utils/chainFunctions.js +14 -3
- package/dist/src/primitives/utils/chainFunctions.js.map +1 -1
- package/dist/src/primitives/utils/createBlurredImage.d.ts +56 -0
- package/dist/src/primitives/utils/createBlurredImage.js +223 -0
- package/dist/src/primitives/utils/createBlurredImage.js.map +1 -0
- package/dist/src/primitives/utils/createSpriteMap.d.ts +2 -2
- package/dist/src/primitives/utils/createSpriteMap.js.map +1 -1
- package/dist/src/primitives/utils/handleNavigation.d.ts +85 -5
- package/dist/src/primitives/utils/handleNavigation.js +242 -69
- package/dist/src/primitives/utils/handleNavigation.js.map +1 -1
- package/dist/src/primitives/utils/withScrolling.d.ts +8 -1
- package/dist/src/primitives/utils/withScrolling.js +25 -6
- package/dist/src/primitives/utils/withScrolling.js.map +1 -1
- package/dist/src/render.d.ts +6 -5
- package/dist/src/render.js +4 -0
- package/dist/src/render.js.map +1 -1
- package/dist/src/solidOpts.d.ts +3 -2
- package/dist/src/solidOpts.js +31 -15
- package/dist/src/solidOpts.js.map +1 -1
- package/dist/src/universal.d.ts +25 -0
- package/dist/src/universal.js +232 -0
- package/dist/src/universal.js.map +1 -0
- package/dist/src/utils.d.ts +2 -0
- package/dist/src/utils.js +8 -0
- package/dist/src/utils.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/jsx-runtime.d.ts +2 -4
- package/package.json +19 -10
- package/src/primitives/Column.tsx +10 -12
- package/src/primitives/Grid.tsx +57 -33
- package/src/primitives/Image.tsx +36 -0
- package/src/primitives/KeepAlive.tsx +124 -0
- package/src/primitives/Lazy.tsx +60 -37
- package/src/primitives/Marquee.tsx +149 -0
- package/src/primitives/Preserve.tsx +18 -0
- package/src/primitives/Row.tsx +11 -12
- package/src/primitives/Suspense.tsx +39 -0
- package/src/primitives/Virtual.tsx +478 -0
- package/src/primitives/VirtualGrid.tsx +199 -0
- package/src/primitives/Visible.tsx +1 -2
- package/src/primitives/announcer/announcer.ts +16 -10
- package/src/primitives/announcer/index.ts +12 -2
- package/src/primitives/announcer/speech.ts +188 -27
- package/src/primitives/createFocusStack.tsx +18 -7
- package/src/primitives/createTag.tsx +31 -0
- package/src/primitives/index.ts +17 -3
- package/src/primitives/types.ts +10 -0
- package/src/primitives/useHold.ts +69 -0
- package/src/primitives/useMouse.ts +283 -66
- package/src/primitives/utils/chainFunctions.ts +40 -9
- package/src/primitives/utils/createBlurredImage.ts +366 -0
- package/src/primitives/utils/createSpriteMap.ts +6 -4
- package/src/primitives/utils/handleNavigation.ts +307 -84
- package/src/primitives/utils/withScrolling.ts +47 -16
- package/src/render.ts +9 -7
- package/src/solidOpts.ts +34 -19
- package/src/utils.ts +10 -0
package/src/primitives/index.ts
CHANGED
|
@@ -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
|
|
16
|
-
export
|
|
17
|
-
export
|
|
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
35
|
export type { KeyHandler } from '@lightningtv/core/focusManager';
|
package/src/primitives/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ElementNode, NodeProps, NodeStyles } from '@lightningtv/solid';
|
|
2
2
|
import type { KeyHandler } from '@lightningtv/core/focusManager';
|
|
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 {
|
|
@@ -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,65 @@
|
|
|
1
|
-
import type { ElementText,
|
|
1
|
+
import type { ElementText, TextNode } from '@lightningtv/core';
|
|
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
10
|
} from '@lightningtv/solid';
|
|
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
|
+
pressedStateDuration?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type UseMouseOptions =
|
|
27
|
+
| { customStates: MouseStateOptions }
|
|
28
|
+
| { customStates: undefined };
|
|
29
|
+
|
|
30
|
+
declare module '@lightningtv/core' {
|
|
31
|
+
interface ElementNode {
|
|
32
|
+
/** function to be called on mouse click */
|
|
33
|
+
onMouseClick?: (
|
|
34
|
+
this: ElementNode,
|
|
35
|
+
event: MouseEvent,
|
|
36
|
+
active: ElementNode,
|
|
37
|
+
) => void;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const DEFAULT_PRESSED_STATE_DURATION = 150;
|
|
42
|
+
|
|
43
|
+
export function addCustomStateToElement(
|
|
44
|
+
element: RenderableNode,
|
|
45
|
+
state: CustomState,
|
|
46
|
+
): void {
|
|
47
|
+
element.states?.add(state);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function removeCustomStateFromElement(
|
|
51
|
+
element: RenderableNode,
|
|
52
|
+
state: CustomState,
|
|
53
|
+
): void {
|
|
54
|
+
element?.states?.remove(state);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function hasCustomState(
|
|
58
|
+
element: RenderableNode,
|
|
59
|
+
state: CustomState,
|
|
60
|
+
): boolean {
|
|
61
|
+
return element.states?.has(state);
|
|
62
|
+
}
|
|
14
63
|
|
|
15
64
|
function createKeyboardEvent(
|
|
16
65
|
key: string,
|
|
@@ -29,6 +78,7 @@ function createKeyboardEvent(
|
|
|
29
78
|
});
|
|
30
79
|
}
|
|
31
80
|
|
|
81
|
+
let scrollTimeout: ReturnType<typeof setTimeout>;
|
|
32
82
|
const handleScroll = throttle((e: WheelEvent): void => {
|
|
33
83
|
const deltaY = e.deltaY;
|
|
34
84
|
if (deltaY < 0) {
|
|
@@ -36,30 +86,148 @@ const handleScroll = throttle((e: WheelEvent): void => {
|
|
|
36
86
|
} else if (deltaY > 0) {
|
|
37
87
|
document.body.dispatchEvent(createKeyboardEvent('ArrowDown', 40));
|
|
38
88
|
}
|
|
89
|
+
|
|
90
|
+
// clear the last timeout if the user is still scrolling
|
|
91
|
+
clearTimeout(scrollTimeout);
|
|
92
|
+
// after 250ms of no scroll events, we send a keyup event to stop the scrolling
|
|
93
|
+
scrollTimeout = setTimeout(() => {
|
|
94
|
+
document.body.dispatchEvent(createKeyboardEvent('ArrowUp', 38, 'keyup'));
|
|
95
|
+
document.body.dispatchEvent(createKeyboardEvent('ArrowDown', 40, 'keyup'));
|
|
96
|
+
}, 250);
|
|
39
97
|
}, 250);
|
|
40
98
|
|
|
41
|
-
|
|
99
|
+
function findElementWithCustomState<TApp extends ElementNode>(
|
|
100
|
+
myApp: TApp,
|
|
101
|
+
x: number,
|
|
102
|
+
y: number,
|
|
103
|
+
customState: CustomState,
|
|
104
|
+
): ElementNode | undefined {
|
|
105
|
+
const result = getChildrenByPosition(myApp, x, y).filter((el) =>
|
|
106
|
+
hasCustomState(el, customState),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (result.length === 0) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let element: ElementNode | undefined = result[result.length - 1];
|
|
114
|
+
|
|
115
|
+
while (element) {
|
|
116
|
+
const elmParent = element.parent;
|
|
117
|
+
if (elmParent?.forwardStates && hasCustomState(elmParent, customState)) {
|
|
118
|
+
element = elmParent;
|
|
119
|
+
} else {
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return element;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function findElementByActiveElement(e: MouseEvent): ElementNode | null {
|
|
42
128
|
const active = activeElement();
|
|
43
129
|
const precision = Config.rendererOptions?.deviceLogicalPixelRatio || 1;
|
|
130
|
+
|
|
44
131
|
if (
|
|
45
132
|
active instanceof ElementNode &&
|
|
46
133
|
testCollision(
|
|
47
134
|
e.clientX,
|
|
48
135
|
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,
|
|
136
|
+
((active.lng.absX as number) || 0) * precision,
|
|
137
|
+
((active.lng.absY as number) || 0) * precision,
|
|
138
|
+
(active.width || 0) * precision,
|
|
139
|
+
(active.height || 0) * precision,
|
|
53
140
|
)
|
|
54
141
|
) {
|
|
142
|
+
return active;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let parent = active?.parent;
|
|
146
|
+
while (parent) {
|
|
147
|
+
if (
|
|
148
|
+
isFunc(parent.onMouseClick) &&
|
|
149
|
+
active &&
|
|
150
|
+
testCollision(
|
|
151
|
+
e.clientX,
|
|
152
|
+
e.clientY,
|
|
153
|
+
((parent.lng.absX as number) || 0) * precision,
|
|
154
|
+
((parent.lng.absY as number) || 0) * precision,
|
|
155
|
+
(parent.width || 0) * precision,
|
|
156
|
+
(parent.height || 0) * precision,
|
|
157
|
+
)
|
|
158
|
+
) {
|
|
159
|
+
return parent;
|
|
160
|
+
}
|
|
161
|
+
parent = parent.parent;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function applyPressedState(
|
|
168
|
+
element: ElementNode,
|
|
169
|
+
pressedState: CustomState,
|
|
170
|
+
pressedStateDuration: number = DEFAULT_PRESSED_STATE_DURATION,
|
|
171
|
+
): void {
|
|
172
|
+
addCustomStateToElement(element, pressedState);
|
|
173
|
+
setTimeout(() => {
|
|
174
|
+
removeCustomStateFromElement(element, pressedState);
|
|
175
|
+
}, pressedStateDuration);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function handleElementClick(
|
|
179
|
+
clickedElement: ElementNode,
|
|
180
|
+
e: MouseEvent,
|
|
181
|
+
customStates?: MouseStateOptions,
|
|
182
|
+
): void {
|
|
183
|
+
if (customStates?.pressedState) {
|
|
184
|
+
applyPressedState(
|
|
185
|
+
clickedElement,
|
|
186
|
+
customStates.pressedState,
|
|
187
|
+
customStates.pressedStateDuration,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (isFunc(clickedElement.onMouseClick)) {
|
|
192
|
+
clickedElement.onMouseClick(e, clickedElement);
|
|
193
|
+
return;
|
|
194
|
+
} else if (isFunc(clickedElement.onEnter)) {
|
|
195
|
+
clickedElement.onEnter();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
clickedElement.setFocus();
|
|
200
|
+
setTimeout(() => {
|
|
55
201
|
document.dispatchEvent(createKeyboardEvent('Enter', 13));
|
|
56
202
|
setTimeout(
|
|
57
203
|
() =>
|
|
58
204
|
document.body.dispatchEvent(createKeyboardEvent('Enter', 13, 'keyup')),
|
|
59
205
|
1,
|
|
60
206
|
);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
207
|
+
}, 1);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function createHandleClick<TApp extends ElementNode>(
|
|
211
|
+
myApp: TApp,
|
|
212
|
+
customStates?: MouseStateOptions,
|
|
213
|
+
) {
|
|
214
|
+
return (e: MouseEvent): void => {
|
|
215
|
+
const clickedElement = customStates
|
|
216
|
+
? findElementWithCustomState(
|
|
217
|
+
myApp,
|
|
218
|
+
e.clientX,
|
|
219
|
+
e.clientY,
|
|
220
|
+
customStates.hoverState,
|
|
221
|
+
)
|
|
222
|
+
: findElementByActiveElement(e);
|
|
223
|
+
|
|
224
|
+
if (!clickedElement) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
handleElementClick(clickedElement, e, customStates);
|
|
229
|
+
};
|
|
230
|
+
}
|
|
63
231
|
|
|
64
232
|
function testCollision(
|
|
65
233
|
px: number,
|
|
@@ -72,106 +240,155 @@ function testCollision(
|
|
|
72
240
|
return px >= cx && px <= cx + cw && py >= cy && py <= cy + ch;
|
|
73
241
|
}
|
|
74
242
|
|
|
75
|
-
function
|
|
76
|
-
node: ElementNode,
|
|
243
|
+
function isNodeAtPosition(
|
|
244
|
+
node: ElementNode | ElementText | TextNode,
|
|
77
245
|
x: number,
|
|
78
246
|
y: number,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
247
|
+
precision: number,
|
|
248
|
+
): node is ElementNode {
|
|
249
|
+
if (!isElementNode(node)) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
node.alpha !== 0 &&
|
|
255
|
+
!node.skipFocus &&
|
|
256
|
+
testCollision(
|
|
257
|
+
x,
|
|
258
|
+
y,
|
|
259
|
+
((node.lng.absX as number) || 0) * precision,
|
|
260
|
+
((node.lng.absY as number) || 0) * precision,
|
|
261
|
+
(node.width || 0) * precision,
|
|
262
|
+
(node.height || 0) * precision,
|
|
263
|
+
)
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function findHighestZIndexNode(nodes: ElementNode[]): ElementNode | undefined {
|
|
268
|
+
if (nodes.length === 0) {
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (nodes.length === 1) {
|
|
273
|
+
return nodes[0];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
let maxZIndex = -1;
|
|
277
|
+
let highestNode: ElementNode | undefined = undefined;
|
|
278
|
+
|
|
279
|
+
for (const node of nodes) {
|
|
280
|
+
const zIndex = node.zIndex ?? -1;
|
|
281
|
+
if (zIndex >= maxZIndex) {
|
|
282
|
+
maxZIndex = zIndex;
|
|
283
|
+
highestNode = node;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
82
286
|
|
|
287
|
+
return highestNode;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function getChildrenByPosition<TElement extends ElementNode = ElementNode>(
|
|
291
|
+
node: TElement,
|
|
292
|
+
x: number,
|
|
293
|
+
y: number,
|
|
294
|
+
): TElement[] {
|
|
295
|
+
const result: TElement[] = [];
|
|
296
|
+
const precision = Config.rendererOptions?.deviceLogicalPixelRatio || 1;
|
|
83
297
|
// Queue for BFS
|
|
298
|
+
|
|
84
299
|
let queue: (ElementNode | ElementText | TextNode)[] = [node];
|
|
85
300
|
|
|
86
301
|
while (queue.length > 0) {
|
|
87
302
|
// Process nodes at the current level
|
|
88
|
-
const currentLevelNodes
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
}
|
|
303
|
+
const currentLevelNodes = queue.filter((currentNode) =>
|
|
304
|
+
isNodeAtPosition(currentNode, x, y, precision),
|
|
305
|
+
);
|
|
107
306
|
|
|
108
|
-
|
|
109
|
-
if (size === 0) {
|
|
307
|
+
if (currentLevelNodes.length === 0) {
|
|
110
308
|
break;
|
|
111
309
|
}
|
|
112
310
|
|
|
113
|
-
|
|
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
|
-
}
|
|
311
|
+
const highestZIndexNode = findHighestZIndexNode(currentLevelNodes);
|
|
129
312
|
|
|
130
|
-
if (highestZIndexNode
|
|
131
|
-
|
|
132
|
-
queue = highestZIndexNode.children;
|
|
133
|
-
} else {
|
|
134
|
-
queue = [];
|
|
313
|
+
if (!highestZIndexNode || isTextNode(highestZIndexNode)) {
|
|
314
|
+
break;
|
|
135
315
|
}
|
|
316
|
+
|
|
317
|
+
result.push(highestZIndexNode as TElement);
|
|
318
|
+
queue = highestZIndexNode.children;
|
|
136
319
|
}
|
|
137
320
|
|
|
138
321
|
return result;
|
|
139
322
|
}
|
|
140
323
|
|
|
141
|
-
export function useMouse(
|
|
142
|
-
myApp:
|
|
324
|
+
export function useMouse<TApp extends ElementNode = ElementNode>(
|
|
325
|
+
myApp: TApp = rootNode as TApp,
|
|
143
326
|
throttleBy: number = 100,
|
|
327
|
+
options?: UseMouseOptions,
|
|
144
328
|
): void {
|
|
145
329
|
const pos = useMousePosition();
|
|
146
330
|
const scheduled = createScheduled((fn) => throttle(fn, throttleBy));
|
|
331
|
+
let previousElement: ElementNode | null = null;
|
|
332
|
+
const customStates = options?.customStates;
|
|
333
|
+
const hoverState = customStates?.hoverState;
|
|
334
|
+
const handleClick = createHandleClick(myApp, customStates);
|
|
335
|
+
const owner = getOwner();
|
|
336
|
+
const handleClickContext = (e: MouseEvent) => {
|
|
337
|
+
runWithOwner(owner, () => handleClick(e));
|
|
338
|
+
};
|
|
339
|
+
|
|
147
340
|
makeEventListener(window, 'wheel', handleScroll);
|
|
148
|
-
makeEventListener(window, 'click',
|
|
341
|
+
makeEventListener(window, 'click', handleClickContext);
|
|
149
342
|
createEffect(() => {
|
|
150
343
|
if (scheduled()) {
|
|
151
344
|
const result = getChildrenByPosition(myApp, pos.x, pos.y).filter(
|
|
152
|
-
(el) =>
|
|
345
|
+
(el) =>
|
|
346
|
+
!!(
|
|
347
|
+
el.onEnter ||
|
|
348
|
+
el.onMouseClick ||
|
|
349
|
+
el.onFocus ||
|
|
350
|
+
el[Config.focusStateKey] ||
|
|
351
|
+
(hoverState ? el[hoverState] : false)
|
|
352
|
+
),
|
|
153
353
|
);
|
|
154
354
|
|
|
155
355
|
if (result.length) {
|
|
156
|
-
let activeElm = result[result.length - 1];
|
|
356
|
+
let activeElm: ElementNode | undefined = result[result.length - 1];
|
|
157
357
|
|
|
158
358
|
while (activeElm) {
|
|
159
359
|
const elmParent = activeElm.parent;
|
|
160
360
|
if (elmParent?.forwardStates) {
|
|
161
|
-
activeElm =
|
|
361
|
+
activeElm = elmParent;
|
|
162
362
|
} else {
|
|
163
363
|
break;
|
|
164
364
|
}
|
|
165
365
|
}
|
|
166
366
|
|
|
367
|
+
if (!activeElm) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
167
371
|
// Update Row & Column Selected property
|
|
168
|
-
const activeElmParent = activeElm
|
|
169
|
-
if (
|
|
372
|
+
const activeElmParent = activeElm.parent;
|
|
373
|
+
if (activeElmParent?.selected !== undefined) {
|
|
170
374
|
activeElmParent.selected =
|
|
171
375
|
activeElmParent.children.indexOf(activeElm);
|
|
172
376
|
}
|
|
173
377
|
|
|
174
|
-
activeElm
|
|
378
|
+
if (previousElement && previousElement !== activeElm && hoverState) {
|
|
379
|
+
removeCustomStateFromElement(previousElement, hoverState);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (hoverState) {
|
|
383
|
+
addCustomStateToElement(activeElm, hoverState);
|
|
384
|
+
} else {
|
|
385
|
+
activeElm.setFocus();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
previousElement = activeElm;
|
|
389
|
+
} else if (previousElement && hoverState) {
|
|
390
|
+
removeCustomStateFromElement(previousElement, hoverState);
|
|
391
|
+
previousElement = null;
|
|
175
392
|
}
|
|
176
393
|
}
|
|
177
394
|
});
|
|
@@ -1,13 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
import * as s from 'solid-js';
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
export function chainFunctions<T>(...args: (ChainableFunction | T)[]): T;
|
|
3
|
+
export type AnyFunction = (this: any, ...args: any[]) => any;
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
/**
|
|
6
|
+
* take an array of functions and if you return `true` from a function, it will stop the chain
|
|
7
|
+
* @param fns list of functions to chain together, can be `undefined`, `null`, or `false` to skip them
|
|
8
|
+
* @returns a function that will call each function in the list until one returns `true` or all functions are called.
|
|
9
|
+
* If no functions are provided, it will return `undefined`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* function Button (props: NodeProps) {
|
|
14
|
+
* function onEnter (el: ElementNode) {...}
|
|
15
|
+
* return <view onEnter={chainFunctions(props.onEnter, onEnter)} />
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function chainFunctions<T extends AnyFunction>(...fns: T[]): T;
|
|
20
|
+
export function chainFunctions<T extends AnyFunction>(
|
|
21
|
+
...fns: (T | undefined | null | false)[]
|
|
22
|
+
): T | undefined;
|
|
23
|
+
export function chainFunctions(
|
|
24
|
+
...fns: (AnyFunction | undefined | null | false)[]
|
|
25
|
+
): AnyFunction | undefined {
|
|
26
|
+
const onlyFunctions = fns.filter((func) => typeof func === 'function');
|
|
11
27
|
if (onlyFunctions.length === 0) {
|
|
12
28
|
return undefined;
|
|
13
29
|
}
|
|
@@ -16,7 +32,7 @@ export function chainFunctions<T extends ChainableFunction>(
|
|
|
16
32
|
return onlyFunctions[0];
|
|
17
33
|
}
|
|
18
34
|
|
|
19
|
-
return function (
|
|
35
|
+
return function (...innerArgs) {
|
|
20
36
|
let result;
|
|
21
37
|
for (const func of onlyFunctions) {
|
|
22
38
|
result = func.apply(this, innerArgs);
|
|
@@ -27,3 +43,18 @@ export function chainFunctions<T extends ChainableFunction>(
|
|
|
27
43
|
return result;
|
|
28
44
|
};
|
|
29
45
|
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Utility for chaining multiple `ref` assignments with `props.ref` forwarding.
|
|
49
|
+
* @param refs list of ref setters. Can be a `props.ref` prop for ref forwarding or a setter to a local variable (`el => ref = el`).
|
|
50
|
+
* @example
|
|
51
|
+
* ```tsx
|
|
52
|
+
* function Button (props: NodeProps) {
|
|
53
|
+
* let localRef: ElementNode | undefined
|
|
54
|
+
* return <view ref={chainRefs(props.ref, el => localRef = el)} />
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export const chainRefs = chainFunctions as <T>(
|
|
59
|
+
...refs: (s.Ref<T> | undefined)[]
|
|
60
|
+
) => (el: T) => void;
|