@lightningtv/solid 2.12.1 → 2.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +1 -0
- package/dist/src/primitives/index.js +1 -0
- package/dist/src/primitives/index.js.map +1 -1
- package/dist/src/primitives/useMouse.d.ts +19 -1
- package/dist/src/primitives/useMouse.js +142 -59
- package/dist/src/primitives/useMouse.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -3
- package/src/primitives/createTag.tsx +31 -0
- package/src/primitives/index.ts +1 -0
- package/src/primitives/useMouse.ts +254 -81
|
@@ -1,20 +1,35 @@
|
|
|
1
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
|
-
isFunc,
|
|
10
10
|
} from '@lightningtv/solid';
|
|
11
11
|
import { makeEventListener } from '@solid-primitives/event-listener';
|
|
12
12
|
import { useMousePosition } from '@solid-primitives/mouse';
|
|
13
13
|
import { createScheduled, throttle } from '@solid-primitives/scheduled';
|
|
14
|
-
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 };
|
|
15
29
|
|
|
16
30
|
declare module '@lightningtv/core' {
|
|
17
31
|
interface ElementNode {
|
|
32
|
+
onEnter?: () => void;
|
|
18
33
|
/** function to be called on mouse click */
|
|
19
34
|
onMouseClick?: (
|
|
20
35
|
this: ElementNode,
|
|
@@ -24,6 +39,29 @@ declare module '@lightningtv/core' {
|
|
|
24
39
|
}
|
|
25
40
|
}
|
|
26
41
|
|
|
42
|
+
const DEFAULT_PRESSED_STATE_DURATION = 150;
|
|
43
|
+
|
|
44
|
+
export function addCustomStateToElement(
|
|
45
|
+
element: RenderableNode,
|
|
46
|
+
state: CustomState,
|
|
47
|
+
): void {
|
|
48
|
+
element.states?.add(state);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function removeCustomStateFromElement(
|
|
52
|
+
element: RenderableNode,
|
|
53
|
+
state: CustomState,
|
|
54
|
+
): void {
|
|
55
|
+
element?.states?.remove(state);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function hasCustomState(
|
|
59
|
+
element: RenderableNode,
|
|
60
|
+
state: CustomState,
|
|
61
|
+
): boolean {
|
|
62
|
+
return element.states?.has(state);
|
|
63
|
+
}
|
|
64
|
+
|
|
27
65
|
function createKeyboardEvent(
|
|
28
66
|
key: string,
|
|
29
67
|
keyCode: number,
|
|
@@ -41,7 +79,7 @@ function createKeyboardEvent(
|
|
|
41
79
|
});
|
|
42
80
|
}
|
|
43
81
|
|
|
44
|
-
let scrollTimeout:
|
|
82
|
+
let scrollTimeout: ReturnType<typeof setTimeout>;
|
|
45
83
|
const handleScroll = throttle((e: WheelEvent): void => {
|
|
46
84
|
const deltaY = e.deltaY;
|
|
47
85
|
if (deltaY < 0) {
|
|
@@ -59,9 +97,38 @@ const handleScroll = throttle((e: WheelEvent): void => {
|
|
|
59
97
|
}, 250);
|
|
60
98
|
}, 250);
|
|
61
99
|
|
|
62
|
-
|
|
100
|
+
function findElementWithCustomState<TApp extends ElementNode>(
|
|
101
|
+
myApp: TApp,
|
|
102
|
+
x: number,
|
|
103
|
+
y: number,
|
|
104
|
+
customState: CustomState,
|
|
105
|
+
): ElementNode | undefined {
|
|
106
|
+
const result = getChildrenByPosition(myApp, x, y).filter((el) =>
|
|
107
|
+
hasCustomState(el, customState),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
if (result.length === 0) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let element: ElementNode | undefined = result[result.length - 1];
|
|
115
|
+
|
|
116
|
+
while (element) {
|
|
117
|
+
const elmParent = element.parent;
|
|
118
|
+
if (elmParent?.forwardStates && hasCustomState(elmParent, customState)) {
|
|
119
|
+
element = elmParent;
|
|
120
|
+
} else {
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return element;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function findElementByActiveElement(e: MouseEvent): ElementNode | null {
|
|
63
129
|
const active = activeElement();
|
|
64
130
|
const precision = Config.rendererOptions?.deviceLogicalPixelRatio || 1;
|
|
131
|
+
|
|
65
132
|
if (
|
|
66
133
|
active instanceof ElementNode &&
|
|
67
134
|
testCollision(
|
|
@@ -73,38 +140,95 @@ const handleClick = (e: MouseEvent): void => {
|
|
|
73
140
|
(active.height || 0) * precision,
|
|
74
141
|
)
|
|
75
142
|
) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
143
|
+
return active;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let parent = active?.parent;
|
|
147
|
+
while (parent) {
|
|
148
|
+
if (
|
|
149
|
+
isFunc(parent.onMouseClick) &&
|
|
150
|
+
active &&
|
|
151
|
+
testCollision(
|
|
152
|
+
e.clientX,
|
|
153
|
+
e.clientY,
|
|
154
|
+
((parent.lng.absX as number) || 0) * precision,
|
|
155
|
+
((parent.lng.absY as number) || 0) * precision,
|
|
156
|
+
(parent.width || 0) * precision,
|
|
157
|
+
(parent.height || 0) * precision,
|
|
158
|
+
)
|
|
159
|
+
) {
|
|
160
|
+
return parent;
|
|
79
161
|
}
|
|
162
|
+
parent = parent.parent;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function applyPressedState(
|
|
169
|
+
element: ElementNode,
|
|
170
|
+
pressedState: CustomState,
|
|
171
|
+
pressedStateDuration: number = DEFAULT_PRESSED_STATE_DURATION,
|
|
172
|
+
): void {
|
|
173
|
+
addCustomStateToElement(element, pressedState);
|
|
174
|
+
setTimeout(() => {
|
|
175
|
+
removeCustomStateFromElement(element, pressedState);
|
|
176
|
+
}, pressedStateDuration);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function handleElementClick(
|
|
180
|
+
clickedElement: ElementNode,
|
|
181
|
+
e: MouseEvent,
|
|
182
|
+
customStates?: MouseStateOptions,
|
|
183
|
+
): void {
|
|
184
|
+
if (customStates?.pressedState) {
|
|
185
|
+
applyPressedState(
|
|
186
|
+
clickedElement,
|
|
187
|
+
customStates.pressedState,
|
|
188
|
+
customStates.pressedStateDuration,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (isFunc(clickedElement.onMouseClick)) {
|
|
193
|
+
clickedElement.onMouseClick(e, clickedElement);
|
|
194
|
+
return;
|
|
195
|
+
} else if (isFunc(clickedElement.onEnter)) {
|
|
196
|
+
clickedElement.onEnter();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
80
199
|
|
|
200
|
+
clickedElement.setFocus();
|
|
201
|
+
setTimeout(() => {
|
|
81
202
|
document.dispatchEvent(createKeyboardEvent('Enter', 13));
|
|
82
203
|
setTimeout(
|
|
83
204
|
() =>
|
|
84
205
|
document.body.dispatchEvent(createKeyboardEvent('Enter', 13, 'keyup')),
|
|
85
206
|
1,
|
|
86
207
|
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
208
|
+
}, 1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function createHandleClick<TApp extends ElementNode>(
|
|
212
|
+
myApp: TApp,
|
|
213
|
+
customStates?: MouseStateOptions,
|
|
214
|
+
) {
|
|
215
|
+
return (e: MouseEvent): void => {
|
|
216
|
+
const clickedElement = customStates
|
|
217
|
+
? findElementWithCustomState(
|
|
218
|
+
myApp,
|
|
93
219
|
e.clientX,
|
|
94
220
|
e.clientY,
|
|
95
|
-
|
|
96
|
-
((parent.lng.absY as number) || 0) * precision,
|
|
97
|
-
(parent.width || 0) * precision,
|
|
98
|
-
(parent.height || 0) * precision,
|
|
221
|
+
customStates.hoverState,
|
|
99
222
|
)
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
parent = parent.parent;
|
|
223
|
+
: findElementByActiveElement(e);
|
|
224
|
+
|
|
225
|
+
if (!clickedElement) {
|
|
226
|
+
return;
|
|
105
227
|
}
|
|
106
|
-
|
|
107
|
-
|
|
228
|
+
|
|
229
|
+
handleElementClick(clickedElement, e, customStates);
|
|
230
|
+
};
|
|
231
|
+
}
|
|
108
232
|
|
|
109
233
|
function testCollision(
|
|
110
234
|
px: number,
|
|
@@ -117,106 +241,155 @@ function testCollision(
|
|
|
117
241
|
return px >= cx && px <= cx + cw && py >= cy && py <= cy + ch;
|
|
118
242
|
}
|
|
119
243
|
|
|
120
|
-
function
|
|
121
|
-
node: ElementNode,
|
|
244
|
+
function isNodeAtPosition(
|
|
245
|
+
node: ElementNode | ElementText | TextNode,
|
|
122
246
|
x: number,
|
|
123
247
|
y: number,
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
248
|
+
precision: number,
|
|
249
|
+
): node is ElementNode {
|
|
250
|
+
if (!isElementNode(node)) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
node.alpha !== 0 &&
|
|
256
|
+
!node.skipFocus &&
|
|
257
|
+
testCollision(
|
|
258
|
+
x,
|
|
259
|
+
y,
|
|
260
|
+
((node.lng.absX as number) || 0) * precision,
|
|
261
|
+
((node.lng.absY as number) || 0) * precision,
|
|
262
|
+
(node.width || 0) * precision,
|
|
263
|
+
(node.height || 0) * precision,
|
|
264
|
+
)
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function findHighestZIndexNode(nodes: ElementNode[]): ElementNode | undefined {
|
|
269
|
+
if (nodes.length === 0) {
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (nodes.length === 1) {
|
|
274
|
+
return nodes[0];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let maxZIndex = -1;
|
|
278
|
+
let highestNode: ElementNode | undefined = undefined;
|
|
279
|
+
|
|
280
|
+
for (const node of nodes) {
|
|
281
|
+
const zIndex = node.zIndex ?? -1;
|
|
282
|
+
if (zIndex >= maxZIndex) {
|
|
283
|
+
maxZIndex = zIndex;
|
|
284
|
+
highestNode = node;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
127
287
|
|
|
288
|
+
return highestNode;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function getChildrenByPosition<TElement extends ElementNode = ElementNode>(
|
|
292
|
+
node: TElement,
|
|
293
|
+
x: number,
|
|
294
|
+
y: number,
|
|
295
|
+
): TElement[] {
|
|
296
|
+
const result: TElement[] = [];
|
|
297
|
+
const precision = Config.rendererOptions?.deviceLogicalPixelRatio || 1;
|
|
128
298
|
// Queue for BFS
|
|
299
|
+
|
|
129
300
|
let queue: (ElementNode | ElementText | TextNode)[] = [node];
|
|
130
301
|
|
|
131
302
|
while (queue.length > 0) {
|
|
132
303
|
// Process nodes at the current level
|
|
133
|
-
const currentLevelNodes
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (
|
|
137
|
-
isElementNode(currentNode) &&
|
|
138
|
-
currentNode.alpha !== 0 &&
|
|
139
|
-
!currentNode.skipFocus &&
|
|
140
|
-
testCollision(
|
|
141
|
-
x,
|
|
142
|
-
y,
|
|
143
|
-
((currentNode.lng.absX as number) || 0) * precision,
|
|
144
|
-
((currentNode.lng.absY as number) || 0) * precision,
|
|
145
|
-
(currentNode.width || 0) * precision,
|
|
146
|
-
(currentNode.height || 0) * precision,
|
|
147
|
-
)
|
|
148
|
-
) {
|
|
149
|
-
currentLevelNodes.push(currentNode);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
304
|
+
const currentLevelNodes = queue.filter((currentNode) =>
|
|
305
|
+
isNodeAtPosition(currentNode, x, y, precision),
|
|
306
|
+
);
|
|
152
307
|
|
|
153
|
-
|
|
154
|
-
if (size === 0) {
|
|
308
|
+
if (currentLevelNodes.length === 0) {
|
|
155
309
|
break;
|
|
156
310
|
}
|
|
157
311
|
|
|
158
|
-
|
|
159
|
-
if (size === 1) {
|
|
160
|
-
highestZIndexNode = currentLevelNodes[0];
|
|
161
|
-
} else {
|
|
162
|
-
let maxZIndex = -1;
|
|
163
|
-
|
|
164
|
-
for (const node of currentLevelNodes) {
|
|
165
|
-
const zIndex = node.zIndex ?? -1;
|
|
166
|
-
if (zIndex > maxZIndex) {
|
|
167
|
-
maxZIndex = zIndex;
|
|
168
|
-
highestZIndexNode = node;
|
|
169
|
-
} else if (zIndex === maxZIndex) {
|
|
170
|
-
highestZIndexNode = node;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
312
|
+
const highestZIndexNode = findHighestZIndexNode(currentLevelNodes);
|
|
174
313
|
|
|
175
|
-
if (highestZIndexNode
|
|
176
|
-
|
|
177
|
-
queue = highestZIndexNode.children;
|
|
178
|
-
} else {
|
|
179
|
-
queue = [];
|
|
314
|
+
if (!highestZIndexNode || isTextNode(highestZIndexNode)) {
|
|
315
|
+
break;
|
|
180
316
|
}
|
|
317
|
+
|
|
318
|
+
result.push(highestZIndexNode as TElement);
|
|
319
|
+
queue = highestZIndexNode.children;
|
|
181
320
|
}
|
|
182
321
|
|
|
183
322
|
return result;
|
|
184
323
|
}
|
|
185
324
|
|
|
186
|
-
export function useMouse(
|
|
187
|
-
myApp:
|
|
325
|
+
export function useMouse<TApp extends ElementNode = ElementNode>(
|
|
326
|
+
myApp: TApp = rootNode as TApp,
|
|
188
327
|
throttleBy: number = 100,
|
|
328
|
+
options?: UseMouseOptions,
|
|
189
329
|
): void {
|
|
190
330
|
const pos = useMousePosition();
|
|
191
331
|
const scheduled = createScheduled((fn) => throttle(fn, throttleBy));
|
|
332
|
+
let previousElement: ElementNode | null = null;
|
|
333
|
+
const customStates = options?.customStates;
|
|
334
|
+
const hoverState = customStates?.hoverState;
|
|
335
|
+
const handleClick = createHandleClick(myApp, customStates);
|
|
336
|
+
const owner = getOwner();
|
|
337
|
+
const handleClickContext = (e: MouseEvent) => {
|
|
338
|
+
runWithOwner(owner, () => handleClick(e));
|
|
339
|
+
};
|
|
340
|
+
|
|
192
341
|
makeEventListener(window, 'wheel', handleScroll);
|
|
193
|
-
makeEventListener(window, 'click',
|
|
342
|
+
makeEventListener(window, 'click', handleClickContext);
|
|
194
343
|
createEffect(() => {
|
|
195
344
|
if (scheduled()) {
|
|
196
345
|
const result = getChildrenByPosition(myApp, pos.x, pos.y).filter(
|
|
197
|
-
(el) =>
|
|
346
|
+
(el) =>
|
|
347
|
+
!!(
|
|
348
|
+
el.onEnter ||
|
|
349
|
+
el.onMouseClick ||
|
|
350
|
+
el.onFocus ||
|
|
351
|
+
el[Config.focusStateKey] ||
|
|
352
|
+
(hoverState ? el[hoverState] : false)
|
|
353
|
+
),
|
|
198
354
|
);
|
|
199
355
|
|
|
200
356
|
if (result.length) {
|
|
201
|
-
let activeElm = result[result.length - 1];
|
|
357
|
+
let activeElm: ElementNode | undefined = result[result.length - 1];
|
|
202
358
|
|
|
203
359
|
while (activeElm) {
|
|
204
360
|
const elmParent = activeElm.parent;
|
|
205
361
|
if (elmParent?.forwardStates) {
|
|
206
|
-
activeElm =
|
|
362
|
+
activeElm = elmParent;
|
|
207
363
|
} else {
|
|
208
364
|
break;
|
|
209
365
|
}
|
|
210
366
|
}
|
|
211
367
|
|
|
368
|
+
if (!activeElm) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
212
372
|
// Update Row & Column Selected property
|
|
213
|
-
const activeElmParent = activeElm
|
|
214
|
-
if (
|
|
373
|
+
const activeElmParent = activeElm.parent;
|
|
374
|
+
if (activeElmParent?.selected !== undefined) {
|
|
215
375
|
activeElmParent.selected =
|
|
216
376
|
activeElmParent.children.indexOf(activeElm);
|
|
217
377
|
}
|
|
218
378
|
|
|
219
|
-
activeElm
|
|
379
|
+
if (previousElement && previousElement !== activeElm && hoverState) {
|
|
380
|
+
removeCustomStateFromElement(previousElement, hoverState);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (hoverState) {
|
|
384
|
+
addCustomStateToElement(activeElm, hoverState);
|
|
385
|
+
} else {
|
|
386
|
+
activeElm.setFocus();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
previousElement = activeElm;
|
|
390
|
+
} else if (previousElement && hoverState) {
|
|
391
|
+
removeCustomStateFromElement(previousElement, hoverState);
|
|
392
|
+
previousElement = null;
|
|
220
393
|
}
|
|
221
394
|
}
|
|
222
395
|
});
|