@table-js/core 0.0.1
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/README.md +38 -0
- package/dist/index.d.mts +178 -0
- package/dist/index.d.ts +178 -0
- package/dist/index.js +602 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +554 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +51 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
// src/focus/FocusManager.ts
|
|
2
|
+
var FocusManager = class {
|
|
3
|
+
constructor(options = {}) {
|
|
4
|
+
this.state = {
|
|
5
|
+
focusedId: null,
|
|
6
|
+
nodes: /* @__PURE__ */ new Map(),
|
|
7
|
+
groups: /* @__PURE__ */ new Map()
|
|
8
|
+
};
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
registerNode(node) {
|
|
12
|
+
this.state.nodes.set(node.id, node);
|
|
13
|
+
if (this.state.focusedId === null && this.options.initialFocusId === node.id) {
|
|
14
|
+
this.setFocus(node.id);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
unregisterNode(id) {
|
|
18
|
+
this.state.nodes.delete(id);
|
|
19
|
+
if (this.state.focusedId === id) {
|
|
20
|
+
this.state.focusedId = null;
|
|
21
|
+
this.options.onFocusChange?.(null);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
registerGroup(group) {
|
|
25
|
+
this.state.groups.set(group.id, group);
|
|
26
|
+
}
|
|
27
|
+
unregisterGroup(id) {
|
|
28
|
+
this.state.groups.delete(id);
|
|
29
|
+
}
|
|
30
|
+
setFocus(id) {
|
|
31
|
+
const node = this.state.nodes.get(id);
|
|
32
|
+
if (!node || node.disabled) return;
|
|
33
|
+
const prev = this.state.focusedId;
|
|
34
|
+
if (prev) {
|
|
35
|
+
const prevNode = this.state.nodes.get(prev);
|
|
36
|
+
prevNode?.onBlur?.();
|
|
37
|
+
prevNode?.el.blur();
|
|
38
|
+
if (prevNode?.groupId) {
|
|
39
|
+
const group = this.state.groups.get(prevNode.groupId);
|
|
40
|
+
if (group) group.lastFocusedId = prev;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
this.state.focusedId = id;
|
|
44
|
+
node.el.focus({ preventScroll: true });
|
|
45
|
+
node.onFocus?.();
|
|
46
|
+
this.options.onFocusChange?.(id);
|
|
47
|
+
}
|
|
48
|
+
move(direction) {
|
|
49
|
+
const currentId = this.state.focusedId;
|
|
50
|
+
if (!currentId) {
|
|
51
|
+
this.focusFirst();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const current = this.state.nodes.get(currentId);
|
|
55
|
+
if (!current) return;
|
|
56
|
+
const groupId = current.groupId;
|
|
57
|
+
const group = groupId ? this.state.groups.get(groupId) : null;
|
|
58
|
+
const next = group ? this.findNextInGroup(currentId, direction, group) : this.findNextSpatial(currentId, direction);
|
|
59
|
+
if (next) this.setFocus(next);
|
|
60
|
+
}
|
|
61
|
+
select() {
|
|
62
|
+
const node = this.state.focusedId ? this.state.nodes.get(this.state.focusedId) : null;
|
|
63
|
+
node?.onSelect?.();
|
|
64
|
+
node?.el.click();
|
|
65
|
+
}
|
|
66
|
+
focusFirst() {
|
|
67
|
+
const first = this.state.nodes.values().next().value;
|
|
68
|
+
if (first) this.setFocus(first.id);
|
|
69
|
+
}
|
|
70
|
+
getFocusedId() {
|
|
71
|
+
return this.state.focusedId;
|
|
72
|
+
}
|
|
73
|
+
findNextInGroup(currentId, direction, group) {
|
|
74
|
+
const siblings = [...this.state.nodes.values()].filter(
|
|
75
|
+
(n) => n.groupId === group.id && !n.disabled
|
|
76
|
+
);
|
|
77
|
+
const index = siblings.findIndex((n) => n.id === currentId);
|
|
78
|
+
if (index === -1) return null;
|
|
79
|
+
const isForward = group.orientation === "horizontal" && direction === "right" || group.orientation === "vertical" && direction === "down";
|
|
80
|
+
const isBackward = group.orientation === "horizontal" && direction === "left" || group.orientation === "vertical" && direction === "up";
|
|
81
|
+
if (isForward) {
|
|
82
|
+
if (index < siblings.length - 1) return siblings[index + 1]?.id ?? null;
|
|
83
|
+
if (group.loop) return siblings[0]?.id ?? null;
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (isBackward) {
|
|
87
|
+
if (index > 0) return siblings[index - 1]?.id ?? null;
|
|
88
|
+
if (group.loop) return siblings[siblings.length - 1]?.id ?? null;
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return this.findNextSpatial(currentId, direction);
|
|
92
|
+
}
|
|
93
|
+
findNextSpatial(currentId, direction) {
|
|
94
|
+
const current = this.state.nodes.get(currentId);
|
|
95
|
+
if (!current) return null;
|
|
96
|
+
const currentRect = current.el.getBoundingClientRect();
|
|
97
|
+
const candidates = [...this.state.nodes.values()].filter(
|
|
98
|
+
(n) => n.id !== currentId && !n.disabled
|
|
99
|
+
);
|
|
100
|
+
const inDirection = candidates.filter((n) => {
|
|
101
|
+
const rect = n.el.getBoundingClientRect();
|
|
102
|
+
if (direction === "right") return rect.left > currentRect.right - 1;
|
|
103
|
+
if (direction === "left") return rect.right < currentRect.left + 1;
|
|
104
|
+
if (direction === "down") return rect.top > currentRect.bottom - 1;
|
|
105
|
+
if (direction === "up") return rect.bottom < currentRect.top + 1;
|
|
106
|
+
return false;
|
|
107
|
+
});
|
|
108
|
+
if (inDirection.length === 0) return null;
|
|
109
|
+
return inDirection.reduce((best, node) => {
|
|
110
|
+
const a = node.el.getBoundingClientRect();
|
|
111
|
+
const b = best.el.getBoundingClientRect();
|
|
112
|
+
const distA = distance(currentRect, a, direction);
|
|
113
|
+
const distB = distance(currentRect, b, direction);
|
|
114
|
+
return distA < distB ? node : best;
|
|
115
|
+
}).id;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
function distance(from, to, direction) {
|
|
119
|
+
const fromCx = from.left + from.width / 2;
|
|
120
|
+
const fromCy = from.top + from.height / 2;
|
|
121
|
+
const toCx = to.left + to.width / 2;
|
|
122
|
+
const toCy = to.top + to.height / 2;
|
|
123
|
+
const primary = direction === "right" || direction === "left" ? Math.abs(toCx - fromCx) : Math.abs(toCy - fromCy);
|
|
124
|
+
const secondary = direction === "right" || direction === "left" ? Math.abs(toCy - fromCy) : Math.abs(toCx - fromCx);
|
|
125
|
+
return primary + secondary * 0.5;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/focus/FocusProvider.tsx
|
|
129
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
130
|
+
|
|
131
|
+
// src/focus/context.ts
|
|
132
|
+
import { createContext, useContext } from "react";
|
|
133
|
+
var FocusContext = createContext(null);
|
|
134
|
+
function useFocusContext() {
|
|
135
|
+
const ctx = useContext(FocusContext);
|
|
136
|
+
if (ctx === null) {
|
|
137
|
+
throw new Error("useFocusContext must be used within a TableApp");
|
|
138
|
+
}
|
|
139
|
+
return ctx;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/focus/KeyHandler.ts
|
|
143
|
+
var KEY_MAP = {
|
|
144
|
+
ArrowUp: { type: "move", direction: "up" },
|
|
145
|
+
ArrowDown: { type: "move", direction: "down" },
|
|
146
|
+
ArrowLeft: { type: "move", direction: "left" },
|
|
147
|
+
ArrowRight: { type: "move", direction: "right" },
|
|
148
|
+
Enter: { type: "select" },
|
|
149
|
+
" ": { type: "select" },
|
|
150
|
+
Backspace: { type: "back" },
|
|
151
|
+
Escape: { type: "back" }
|
|
152
|
+
};
|
|
153
|
+
var TIZEN_KEY_MAP = {
|
|
154
|
+
38: { type: "move", direction: "up" },
|
|
155
|
+
40: { type: "move", direction: "down" },
|
|
156
|
+
37: { type: "move", direction: "left" },
|
|
157
|
+
39: { type: "move", direction: "right" },
|
|
158
|
+
13: { type: "select" },
|
|
159
|
+
10009: { type: "back" }
|
|
160
|
+
};
|
|
161
|
+
var WEBOS_KEY_MAP = {
|
|
162
|
+
38: { type: "move", direction: "up" },
|
|
163
|
+
40: { type: "move", direction: "down" },
|
|
164
|
+
37: { type: "move", direction: "left" },
|
|
165
|
+
39: { type: "move", direction: "right" },
|
|
166
|
+
13: { type: "select" },
|
|
167
|
+
461: { type: "back" }
|
|
168
|
+
};
|
|
169
|
+
var ANDROID_TV_KEY_MAP = {
|
|
170
|
+
38: { type: "move", direction: "up" },
|
|
171
|
+
40: { type: "move", direction: "down" },
|
|
172
|
+
37: { type: "move", direction: "left" },
|
|
173
|
+
39: { type: "move", direction: "right" },
|
|
174
|
+
13: { type: "select" },
|
|
175
|
+
85: { type: "select" },
|
|
176
|
+
4: { type: "back" }
|
|
177
|
+
};
|
|
178
|
+
function detectPlatform() {
|
|
179
|
+
if (typeof window === "undefined") return "web";
|
|
180
|
+
const ua = navigator.userAgent;
|
|
181
|
+
if (ua.includes("Tizen")) return "tizen";
|
|
182
|
+
if (ua.includes("Web0S") || ua.includes("webOS")) return "webos";
|
|
183
|
+
if (ua.includes("Android")) return "android";
|
|
184
|
+
return "web";
|
|
185
|
+
}
|
|
186
|
+
function resolveKeyAction(event) {
|
|
187
|
+
const byKey = KEY_MAP[event.key];
|
|
188
|
+
if (byKey) return byKey;
|
|
189
|
+
const platform = detectPlatform();
|
|
190
|
+
const platformMap = platform === "tizen" ? TIZEN_KEY_MAP : platform === "webos" ? WEBOS_KEY_MAP : platform === "android" ? ANDROID_TV_KEY_MAP : {};
|
|
191
|
+
return platformMap[event.keyCode] ?? { type: "unknown" };
|
|
192
|
+
}
|
|
193
|
+
var KeyHandler = class {
|
|
194
|
+
constructor() {
|
|
195
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
196
|
+
this.bound = null;
|
|
197
|
+
}
|
|
198
|
+
mount() {
|
|
199
|
+
this.bound = (e) => {
|
|
200
|
+
const action = resolveKeyAction(e);
|
|
201
|
+
if (action.type === "unknown") return;
|
|
202
|
+
e.preventDefault();
|
|
203
|
+
this.listeners.forEach((fn) => fn(action));
|
|
204
|
+
};
|
|
205
|
+
window.addEventListener("keydown", this.bound);
|
|
206
|
+
}
|
|
207
|
+
unmount() {
|
|
208
|
+
if (this.bound) {
|
|
209
|
+
window.removeEventListener("keydown", this.bound);
|
|
210
|
+
this.bound = null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
subscribe(fn) {
|
|
214
|
+
this.listeners.add(fn);
|
|
215
|
+
return () => this.listeners.delete(fn);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// src/focus/FocusProvider.tsx
|
|
220
|
+
import { jsx } from "react/jsx-runtime";
|
|
221
|
+
function FocusProvider({ children, options }) {
|
|
222
|
+
const managerRef = useRef(null);
|
|
223
|
+
const keyHandlerRef = useRef(null);
|
|
224
|
+
if (!managerRef.current) {
|
|
225
|
+
managerRef.current = new FocusManager(options ?? {});
|
|
226
|
+
}
|
|
227
|
+
if (!keyHandlerRef.current) {
|
|
228
|
+
keyHandlerRef.current = new KeyHandler();
|
|
229
|
+
}
|
|
230
|
+
const manager = managerRef.current;
|
|
231
|
+
const keyHandler = keyHandlerRef.current;
|
|
232
|
+
useEffect(() => {
|
|
233
|
+
keyHandler.mount();
|
|
234
|
+
const unsub = keyHandler.subscribe((action) => {
|
|
235
|
+
if (action.type === "move") {
|
|
236
|
+
manager.move(action.direction);
|
|
237
|
+
}
|
|
238
|
+
if (action.type === "select") {
|
|
239
|
+
manager.select();
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
return () => {
|
|
243
|
+
unsub();
|
|
244
|
+
keyHandler.unmount();
|
|
245
|
+
};
|
|
246
|
+
}, [manager, keyHandler]);
|
|
247
|
+
const value = useMemo(
|
|
248
|
+
() => ({ manager, keyHandler }),
|
|
249
|
+
[manager, keyHandler]
|
|
250
|
+
);
|
|
251
|
+
return /* @__PURE__ */ jsx(FocusContext.Provider, { value, children });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/focus/hooks.ts
|
|
255
|
+
import {
|
|
256
|
+
useEffect as useEffect2,
|
|
257
|
+
useId,
|
|
258
|
+
useRef as useRef2,
|
|
259
|
+
useState
|
|
260
|
+
} from "react";
|
|
261
|
+
function useFocusable(options = {}) {
|
|
262
|
+
const id = useId();
|
|
263
|
+
const ref = useRef2(null);
|
|
264
|
+
const [focused, setFocused] = useState(false);
|
|
265
|
+
const { manager } = useFocusContext();
|
|
266
|
+
const { groupId, disabled = false, onFocus, onBlur, onSelect, autoFocus } = options;
|
|
267
|
+
useEffect2(() => {
|
|
268
|
+
const el = ref.current;
|
|
269
|
+
if (!el) return;
|
|
270
|
+
manager.registerNode({
|
|
271
|
+
id,
|
|
272
|
+
el,
|
|
273
|
+
groupId: groupId ?? null,
|
|
274
|
+
disabled,
|
|
275
|
+
onFocus: () => {
|
|
276
|
+
setFocused(true);
|
|
277
|
+
onFocus?.();
|
|
278
|
+
},
|
|
279
|
+
onBlur: () => {
|
|
280
|
+
setFocused(false);
|
|
281
|
+
onBlur?.();
|
|
282
|
+
},
|
|
283
|
+
onSelect: onSelect || (() => {
|
|
284
|
+
})
|
|
285
|
+
});
|
|
286
|
+
if (autoFocus) {
|
|
287
|
+
manager.setFocus(id);
|
|
288
|
+
}
|
|
289
|
+
return () => {
|
|
290
|
+
manager.unregisterNode(id);
|
|
291
|
+
};
|
|
292
|
+
}, [id, groupId, disabled, autoFocus, manager, onFocus, onBlur, onSelect]);
|
|
293
|
+
const focusableProps = {
|
|
294
|
+
"data-focusable": id,
|
|
295
|
+
"data-focused": focused,
|
|
296
|
+
tabIndex: focused ? 0 : -1
|
|
297
|
+
};
|
|
298
|
+
return { ref, focused, focusableProps };
|
|
299
|
+
}
|
|
300
|
+
function useFocusGroup(options) {
|
|
301
|
+
const id = useId();
|
|
302
|
+
const { manager } = useFocusContext();
|
|
303
|
+
useEffect2(() => {
|
|
304
|
+
manager.registerGroup({
|
|
305
|
+
id,
|
|
306
|
+
parentId: null,
|
|
307
|
+
lastFocusedId: null,
|
|
308
|
+
...options
|
|
309
|
+
});
|
|
310
|
+
return () => {
|
|
311
|
+
manager.unregisterGroup(id);
|
|
312
|
+
};
|
|
313
|
+
}, [id, manager, options.orientation, options.loop, options.rememberFocus]);
|
|
314
|
+
return { groupId: id };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/focus/components.tsx
|
|
318
|
+
import { forwardRef } from "react";
|
|
319
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
320
|
+
var Focusable = forwardRef(
|
|
321
|
+
({ children, as: Tag = "div", className, style, ...options }, _ref) => {
|
|
322
|
+
const { ref, focused, focusableProps } = useFocusable(options);
|
|
323
|
+
return /* @__PURE__ */ jsx2(
|
|
324
|
+
Tag,
|
|
325
|
+
{
|
|
326
|
+
ref,
|
|
327
|
+
className,
|
|
328
|
+
style,
|
|
329
|
+
...focusableProps,
|
|
330
|
+
children: typeof children === "function" ? children(focused) : children
|
|
331
|
+
}
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
);
|
|
335
|
+
Focusable.displayName = "Focusable";
|
|
336
|
+
function FocusGroup({
|
|
337
|
+
children,
|
|
338
|
+
as: Tag = "div",
|
|
339
|
+
className,
|
|
340
|
+
style,
|
|
341
|
+
...options
|
|
342
|
+
}) {
|
|
343
|
+
const { groupId } = useFocusGroup(options);
|
|
344
|
+
return /* @__PURE__ */ jsx2(
|
|
345
|
+
Tag,
|
|
346
|
+
{
|
|
347
|
+
className,
|
|
348
|
+
style,
|
|
349
|
+
"data-focus-group": groupId,
|
|
350
|
+
children
|
|
351
|
+
}
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// src/router/Router.tsx
|
|
356
|
+
import React3, { useCallback as useCallback2, useEffect as useEffect3, useState as useState2, useTransition } from "react";
|
|
357
|
+
|
|
358
|
+
// src/router/matcher.ts
|
|
359
|
+
function parseSegments(path) {
|
|
360
|
+
return path.split("/").filter(Boolean).map((segment) => {
|
|
361
|
+
if (segment.startsWith("[...") && segment.endsWith("]")) {
|
|
362
|
+
return { type: "catch-all", value: segment.slice(4, -1) };
|
|
363
|
+
}
|
|
364
|
+
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
365
|
+
return { type: "dynamic", value: segment.slice(1, -1) };
|
|
366
|
+
}
|
|
367
|
+
return { type: "static", value: segment };
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
function matchRoute(pathname, routes) {
|
|
371
|
+
const incomingSegments = pathname.split("/").filter(Boolean);
|
|
372
|
+
for (const route of routes) {
|
|
373
|
+
const result = matchSegments(incomingSegments, route.segments);
|
|
374
|
+
if (result !== null) {
|
|
375
|
+
return { route, params: result };
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
function matchSegments(incoming, segments) {
|
|
381
|
+
const params = {};
|
|
382
|
+
let i = 0;
|
|
383
|
+
for (const segment of segments) {
|
|
384
|
+
if (segment.type === "catch-all") {
|
|
385
|
+
params[segment.value] = incoming.slice(i).join("/");
|
|
386
|
+
return params;
|
|
387
|
+
}
|
|
388
|
+
const part = incoming[i];
|
|
389
|
+
if (part === void 0) return null;
|
|
390
|
+
if (segment.type === "static") {
|
|
391
|
+
if (part !== segment.value) return null;
|
|
392
|
+
}
|
|
393
|
+
if (segment.type === "dynamic") {
|
|
394
|
+
params[segment.value] = part;
|
|
395
|
+
}
|
|
396
|
+
i++;
|
|
397
|
+
}
|
|
398
|
+
if (i !== incoming.length) return null;
|
|
399
|
+
return params;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// src/router/context.ts
|
|
403
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
404
|
+
var RouterContext = createContext2(null);
|
|
405
|
+
function useRouter() {
|
|
406
|
+
const ctx = useContext2(RouterContext);
|
|
407
|
+
if (ctx === null) {
|
|
408
|
+
throw new Error("useRouter must be used within a TableApp");
|
|
409
|
+
}
|
|
410
|
+
return ctx;
|
|
411
|
+
}
|
|
412
|
+
function useParams() {
|
|
413
|
+
const { route } = useRouter();
|
|
414
|
+
return route.params;
|
|
415
|
+
}
|
|
416
|
+
function useQuery() {
|
|
417
|
+
const { route } = useRouter();
|
|
418
|
+
return route.query;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// src/router/Router.tsx
|
|
422
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
423
|
+
function parseQuery(search) {
|
|
424
|
+
const query = {};
|
|
425
|
+
const params = new URLSearchParams(search);
|
|
426
|
+
params.forEach((value, key) => {
|
|
427
|
+
const existing = query[key];
|
|
428
|
+
if (existing === void 0) {
|
|
429
|
+
query[key] = value;
|
|
430
|
+
} else if (Array.isArray(existing)) {
|
|
431
|
+
existing.push(value);
|
|
432
|
+
} else {
|
|
433
|
+
query[key] = [existing, value];
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
return query;
|
|
437
|
+
}
|
|
438
|
+
function buildRoute(pathname, search, params) {
|
|
439
|
+
return {
|
|
440
|
+
path: pathname,
|
|
441
|
+
params,
|
|
442
|
+
query: parseQuery(search)
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function Router({ routes, initialPath = "/" }) {
|
|
446
|
+
const [, startTransition] = useTransition();
|
|
447
|
+
const [currentPath, setCurrentPath] = useState2(() => {
|
|
448
|
+
if (typeof window !== "undefined") return window.location.pathname;
|
|
449
|
+
return initialPath;
|
|
450
|
+
});
|
|
451
|
+
const [search, setSearch] = useState2(() => {
|
|
452
|
+
if (typeof window !== "undefined") return window.location.search;
|
|
453
|
+
return "";
|
|
454
|
+
});
|
|
455
|
+
const [history, setHistory] = useState2([]);
|
|
456
|
+
const match = matchRoute(currentPath, routes);
|
|
457
|
+
const route = match ? buildRoute(currentPath, search, match.params) : buildRoute(currentPath, search, {});
|
|
458
|
+
const navigate = useCallback2(
|
|
459
|
+
(path, options = {}) => {
|
|
460
|
+
const [pathname, queryString] = path.split("?");
|
|
461
|
+
const nextSearch = queryString ? `?${queryString}` : "";
|
|
462
|
+
startTransition(() => {
|
|
463
|
+
if (options.replace) {
|
|
464
|
+
window.history.replaceState(null, "", path);
|
|
465
|
+
} else {
|
|
466
|
+
window.history.pushState(null, "", path);
|
|
467
|
+
setHistory((prev) => [...prev, route]);
|
|
468
|
+
}
|
|
469
|
+
setCurrentPath(pathname ?? "/");
|
|
470
|
+
setSearch(nextSearch);
|
|
471
|
+
});
|
|
472
|
+
},
|
|
473
|
+
[route]
|
|
474
|
+
);
|
|
475
|
+
const back = useCallback2(() => {
|
|
476
|
+
if (history.length === 0) return;
|
|
477
|
+
const prev = history[history.length - 1];
|
|
478
|
+
if (!prev) return;
|
|
479
|
+
setHistory((h) => h.slice(0, -1));
|
|
480
|
+
window.history.back();
|
|
481
|
+
setCurrentPath(prev.path);
|
|
482
|
+
setSearch("");
|
|
483
|
+
}, [history]);
|
|
484
|
+
const prefetch = useCallback2(
|
|
485
|
+
(path) => {
|
|
486
|
+
const [pathname] = path.split("?");
|
|
487
|
+
if (!pathname) return;
|
|
488
|
+
const result = matchRoute(pathname, routes);
|
|
489
|
+
if (result) {
|
|
490
|
+
void result.route.component;
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
[routes]
|
|
494
|
+
);
|
|
495
|
+
useEffect3(() => {
|
|
496
|
+
const onPopState = () => {
|
|
497
|
+
setCurrentPath(window.location.pathname);
|
|
498
|
+
setSearch(window.location.search);
|
|
499
|
+
};
|
|
500
|
+
window.addEventListener("popstate", onPopState);
|
|
501
|
+
return () => window.removeEventListener("popstate", onPopState);
|
|
502
|
+
}, []);
|
|
503
|
+
if (!match) {
|
|
504
|
+
return /* @__PURE__ */ jsx3(NotFound, { path: currentPath });
|
|
505
|
+
}
|
|
506
|
+
const { component: Page } = match.route;
|
|
507
|
+
return /* @__PURE__ */ jsx3(RouterContext.Provider, { value: { route, navigate, back, prefetch }, children: /* @__PURE__ */ jsx3(React3.Suspense, { fallback: /* @__PURE__ */ jsx3(PageLoader, {}), children: /* @__PURE__ */ jsx3(Page, {}) }) });
|
|
508
|
+
}
|
|
509
|
+
function NotFound({ path }) {
|
|
510
|
+
return /* @__PURE__ */ jsx3("div", { "data-table-not-found": true, children: /* @__PURE__ */ jsxs("span", { children: [
|
|
511
|
+
"404 \u2014 ",
|
|
512
|
+
path,
|
|
513
|
+
" not found"
|
|
514
|
+
] }) });
|
|
515
|
+
}
|
|
516
|
+
function PageLoader() {
|
|
517
|
+
return /* @__PURE__ */ jsx3("div", { "data-table-loader": true });
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// src/TableApp.tsx
|
|
521
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
522
|
+
function TableApp({ routes, initialPath, focus }) {
|
|
523
|
+
return /* @__PURE__ */ jsx4(FocusProvider, { ...focus && { options: focus }, children: /* @__PURE__ */ jsx4(Router, { routes, ...initialPath && { initialPath } }) });
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/router/defineRoutes.ts
|
|
527
|
+
import React4 from "react";
|
|
528
|
+
function defineRoutes(routes) {
|
|
529
|
+
return routes.map((r) => ({
|
|
530
|
+
filePath: r.path,
|
|
531
|
+
segments: parseSegments(r.path),
|
|
532
|
+
component: React4.lazy(r.component)
|
|
533
|
+
}));
|
|
534
|
+
}
|
|
535
|
+
export {
|
|
536
|
+
FocusContext,
|
|
537
|
+
FocusGroup,
|
|
538
|
+
FocusManager,
|
|
539
|
+
FocusProvider,
|
|
540
|
+
Focusable,
|
|
541
|
+
KeyHandler,
|
|
542
|
+
Router,
|
|
543
|
+
RouterContext,
|
|
544
|
+
TableApp,
|
|
545
|
+
defineRoutes,
|
|
546
|
+
resolveKeyAction,
|
|
547
|
+
useFocusContext,
|
|
548
|
+
useFocusGroup,
|
|
549
|
+
useFocusable,
|
|
550
|
+
useParams,
|
|
551
|
+
useQuery,
|
|
552
|
+
useRouter
|
|
553
|
+
};
|
|
554
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/focus/FocusManager.ts","../src/focus/FocusProvider.tsx","../src/focus/context.ts","../src/focus/KeyHandler.ts","../src/focus/hooks.ts","../src/focus/components.tsx","../src/router/Router.tsx","../src/router/matcher.ts","../src/router/context.ts","../src/TableApp.tsx","../src/router/defineRoutes.ts"],"sourcesContent":["import type {\n FocusableId,\n FocusableNode,\n FocusDirection,\n FocusGroup,\n FocusManagerOptions,\n FocusState,\n} from './types'\n\nexport class FocusManager {\n private state: FocusState = {\n focusedId: null,\n nodes: new Map(),\n groups: new Map(),\n }\n\n private options: FocusManagerOptions\n\n constructor(options: FocusManagerOptions = {}) {\n this.options = options\n }\n\n registerNode(node: FocusableNode): void {\n this.state.nodes.set(node.id, node)\n if (\n this.state.focusedId === null &&\n this.options.initialFocusId === node.id\n ) {\n this.setFocus(node.id)\n }\n }\n\n unregisterNode(id: FocusableId): void {\n this.state.nodes.delete(id)\n if (this.state.focusedId === id) {\n this.state.focusedId = null\n this.options.onFocusChange?.(null)\n }\n }\n\n registerGroup(group: FocusGroup): void {\n this.state.groups.set(group.id, group)\n }\n\n unregisterGroup(id: FocusableId): void {\n this.state.groups.delete(id)\n }\n\n setFocus(id: FocusableId): void {\n const node = this.state.nodes.get(id)\n if (!node || node.disabled) return\n\n const prev = this.state.focusedId\n if (prev) {\n const prevNode = this.state.nodes.get(prev)\n prevNode?.onBlur?.()\n prevNode?.el.blur()\n\n if (prevNode?.groupId) {\n const group = this.state.groups.get(prevNode.groupId)\n if (group) group.lastFocusedId = prev\n }\n }\n\n this.state.focusedId = id\n node.el.focus({ preventScroll: true })\n node.onFocus?.()\n this.options.onFocusChange?.(id)\n }\n\n move(direction: FocusDirection): void {\n const currentId = this.state.focusedId\n if (!currentId) {\n this.focusFirst()\n return\n }\n\n const current = this.state.nodes.get(currentId)\n if (!current) return\n\n const groupId = current.groupId\n const group = groupId ? this.state.groups.get(groupId) : null\n\n const next = group\n ? this.findNextInGroup(currentId, direction, group)\n : this.findNextSpatial(currentId, direction)\n\n if (next) this.setFocus(next)\n }\n\n select(): void {\n const node = this.state.focusedId\n ? this.state.nodes.get(this.state.focusedId)\n : null\n node?.onSelect?.()\n node?.el.click()\n }\n\n focusFirst(): void {\n const first = this.state.nodes.values().next().value as FocusableNode | undefined\n if (first) this.setFocus(first.id)\n }\n\n getFocusedId(): FocusableId | null {\n return this.state.focusedId\n }\n\n private findNextInGroup(\n currentId: FocusableId,\n direction: FocusDirection,\n group: FocusGroup,\n ): FocusableId | null {\n const siblings = [...this.state.nodes.values()].filter(\n (n) => n.groupId === group.id && !n.disabled,\n )\n const index = siblings.findIndex((n) => n.id === currentId)\n if (index === -1) return null\n\n const isForward =\n (group.orientation === 'horizontal' && direction === 'right') ||\n (group.orientation === 'vertical' && direction === 'down')\n\n const isBackward =\n (group.orientation === 'horizontal' && direction === 'left') ||\n (group.orientation === 'vertical' && direction === 'up')\n\n if (isForward) {\n if (index < siblings.length - 1) return siblings[index + 1]?.id ?? null\n if (group.loop) return siblings[0]?.id ?? null\n return null\n }\n\n if (isBackward) {\n if (index > 0) return siblings[index - 1]?.id ?? null\n if (group.loop) return siblings[siblings.length - 1]?.id ?? null\n return null\n }\n\n return this.findNextSpatial(currentId, direction)\n }\n\n private findNextSpatial(currentId: FocusableId, direction: FocusDirection): FocusableId | null {\n const current = this.state.nodes.get(currentId)\n if (!current) return null\n\n const currentRect = current.el.getBoundingClientRect()\n const candidates = [...this.state.nodes.values()].filter(\n (n) => n.id !== currentId && !n.disabled,\n )\n\n const inDirection = candidates.filter((n) => {\n const rect = n.el.getBoundingClientRect()\n if (direction === 'right') return rect.left > currentRect.right - 1\n if (direction === 'left') return rect.right < currentRect.left + 1\n if (direction === 'down') return rect.top > currentRect.bottom - 1\n if (direction === 'up') return rect.bottom < currentRect.top + 1\n return false\n })\n\n if (inDirection.length === 0) return null\n\n return inDirection.reduce((best, node) => {\n const a = node.el.getBoundingClientRect()\n const b = best.el.getBoundingClientRect()\n const distA = distance(currentRect, a, direction)\n const distB = distance(currentRect, b, direction)\n return distA < distB ? node : best\n }).id\n }\n}\n\n\nfunction distance(\n from: DOMRect,\n to: DOMRect,\n direction: FocusDirection,\n): number {\n const fromCx = from.left + from.width / 2\n const fromCy = from.top + from.height / 2\n const toCx = to.left + to.width / 2\n const toCy = to.top + to.height / 2\n\n const primary =\n direction === 'right' || direction === 'left'\n ? Math.abs(toCx - fromCx)\n : Math.abs(toCy - fromCy)\n\n const secondary =\n direction === 'right' || direction === 'left'\n ? Math.abs(toCy - fromCy)\n : Math.abs(toCx - fromCx)\n\n return primary + secondary * 0.5\n}","import React, { useEffect, useMemo, useRef } from 'react'\nimport { FocusContext } from './context'\nimport { FocusManager } from './FocusManager'\nimport { KeyHandler } from './KeyHandler'\nimport type { FocusManagerOptions } from './types'\n\ninterface FocusProviderProps {\n children: React.ReactNode\n options?: FocusManagerOptions\n}\n\nexport function FocusProvider({ children, options }: FocusProviderProps) {\n const managerRef = useRef<FocusManager | null>(null)\n const keyHandlerRef = useRef<KeyHandler | null>(null)\n\n if (!managerRef.current) {\n managerRef.current = new FocusManager(options ?? {})\n }\n if (!keyHandlerRef.current) {\n keyHandlerRef.current = new KeyHandler()\n }\n\n const manager = managerRef.current\n const keyHandler = keyHandlerRef.current\n\n useEffect(() => {\n keyHandler.mount()\n\n const unsub = keyHandler.subscribe((action) => {\n if (action.type === 'move') {\n manager.move(action.direction)\n }\n if (action.type === 'select') {\n manager.select()\n }\n })\n\n return () => {\n unsub()\n keyHandler.unmount()\n }\n }, [manager, keyHandler])\n\n const value = useMemo(\n () => ({ manager, keyHandler }),\n [manager, keyHandler],\n )\n\n return (\n <FocusContext.Provider value={value}>\n {children}\n </FocusContext.Provider>\n )\n}","import { createContext, useContext } from 'react'\nimport type { FocusManager } from './FocusManager'\nimport type { KeyHandler } from './KeyHandler'\n\nexport interface FocusContextValue {\n manager: FocusManager\n keyHandler: KeyHandler\n}\n\nexport const FocusContext = createContext<FocusContextValue | null>(null)\n\nexport function useFocusContext(): FocusContextValue {\n const ctx = useContext(FocusContext)\n if (ctx === null) {\n throw new Error('useFocusContext must be used within a TableApp')\n }\n return ctx\n}","import type { FocusDirection } from './types'\n\nexport type KeyAction =\n | { type: 'move'; direction: FocusDirection }\n | { type: 'select' }\n | { type: 'back' }\n | { type: 'unknown' }\n\nconst KEY_MAP: Record<string, KeyAction> = {\n ArrowUp: { type: 'move', direction: 'up' },\n ArrowDown: { type: 'move', direction: 'down' },\n ArrowLeft: { type: 'move', direction: 'left' },\n ArrowRight: { type: 'move', direction: 'right' },\n Enter: { type: 'select' },\n ' ': { type: 'select' },\n Backspace: { type: 'back' },\n Escape: { type: 'back' },\n}\n\nconst TIZEN_KEY_MAP: Record<number, KeyAction> = {\n 38: { type: 'move', direction: 'up' },\n 40: { type: 'move', direction: 'down' },\n 37: { type: 'move', direction: 'left' },\n 39: { type: 'move', direction: 'right' },\n 13: { type: 'select' },\n 10009: { type: 'back' },\n}\n\nconst WEBOS_KEY_MAP: Record<number, KeyAction> = {\n 38: { type: 'move', direction: 'up' },\n 40: { type: 'move', direction: 'down' },\n 37: { type: 'move', direction: 'left' },\n 39: { type: 'move', direction: 'right' },\n 13: { type: 'select' },\n 461: { type: 'back' },\n}\n\nconst ANDROID_TV_KEY_MAP: Record<number, KeyAction> = {\n 38: { type: 'move', direction: 'up' },\n 40: { type: 'move', direction: 'down' },\n 37: { type: 'move', direction: 'left' },\n 39: { type: 'move', direction: 'right' },\n 13: { type: 'select' },\n 85: { type: 'select' },\n 4: { type: 'back' },\n}\n\nfunction detectPlatform(): 'tizen' | 'webos' | 'android' | 'web' {\n if (typeof window === 'undefined') return 'web'\n const ua = navigator.userAgent\n if (ua.includes('Tizen')) return 'tizen'\n if (ua.includes('Web0S') || ua.includes('webOS')) return 'webos'\n if (ua.includes('Android')) return 'android'\n return 'web'\n}\n\nexport function resolveKeyAction(event: KeyboardEvent): KeyAction {\n const byKey = KEY_MAP[event.key]\n if (byKey) return byKey\n\n const platform = detectPlatform()\n\n const platformMap =\n platform === 'tizen' ? TIZEN_KEY_MAP :\n platform === 'webos' ? WEBOS_KEY_MAP :\n platform === 'android' ? ANDROID_TV_KEY_MAP :\n {}\n\n return platformMap[event.keyCode] ?? { type: 'unknown' }\n}\n\nexport class KeyHandler {\n private listeners = new Set<(action: KeyAction) => void>()\n private bound: ((e: KeyboardEvent) => void) | null = null\n\n mount(): void {\n this.bound = (e: KeyboardEvent) => {\n const action = resolveKeyAction(e)\n if (action.type === 'unknown') return\n e.preventDefault()\n this.listeners.forEach((fn) => fn(action))\n }\n window.addEventListener('keydown', this.bound)\n }\n\n unmount(): void {\n if (this.bound) {\n window.removeEventListener('keydown', this.bound)\n this.bound = null\n }\n }\n\n subscribe(fn: (action: KeyAction) => void): () => void {\n this.listeners.add(fn)\n return () => this.listeners.delete(fn)\n }\n}","import {\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n} from 'react'\nimport { useFocusContext } from './context'\nimport type { FocusGroup } from './types'\n\nexport interface UseFocusableOptions {\n groupId?: string\n disabled?: boolean\n onFocus?: () => void\n onBlur?: () => void\n onSelect?: () => void\n autoFocus?: boolean\n}\n\nexport interface UseFocusableResult {\n ref: React.RefObject<HTMLElement>\n focused: boolean\n focusableProps: {\n 'data-focusable': string\n 'data-focused': boolean\n tabIndex: number\n }\n}\n\nexport function useFocusable(options: UseFocusableOptions = {}): UseFocusableResult {\n const id = useId()\n const ref = useRef<HTMLElement>(null)\n const [focused, setFocused] = useState(false)\n const { manager } = useFocusContext()\n\n const { groupId, disabled = false, onFocus, onBlur, onSelect, autoFocus } = options\n\n useEffect(() => {\n const el = ref.current\n if (!el) return\n\n manager.registerNode({\n id,\n el,\n groupId: groupId ?? null,\n disabled,\n onFocus: () => {\n setFocused(true)\n onFocus?.()\n },\n onBlur: () => {\n setFocused(false)\n onBlur?.()\n },\n onSelect: onSelect || (() => {}),\n })\n\n if (autoFocus) {\n manager.setFocus(id)\n }\n\n return () => {\n manager.unregisterNode(id)\n }\n }, [id, groupId, disabled, autoFocus, manager, onFocus, onBlur, onSelect])\n\n const focusableProps = {\n 'data-focusable': id,\n 'data-focused': focused,\n tabIndex: focused ? 0 : -1,\n }\n\n return { ref: ref as React.RefObject<HTMLElement>, focused, focusableProps }\n}\n\nexport type UseFocusGroupOptions = Omit<FocusGroup, 'id' | 'lastFocusedId' | 'parentId'>\n\nexport function useFocusGroup(options: UseFocusGroupOptions) {\n const id = useId()\n const { manager } = useFocusContext()\n\n useEffect(() => {\n manager.registerGroup({\n id,\n parentId: null,\n lastFocusedId: null,\n ...options,\n })\n return () => {\n manager.unregisterGroup(id)\n }\n }, [id, manager, options.orientation, options.loop, options.rememberFocus])\n\n return { groupId: id }\n}","import React, { forwardRef } from 'react'\nimport type { JSX } from 'react'\nimport { useFocusable, useFocusGroup } from './hooks'\nimport type { UseFocusableOptions, UseFocusGroupOptions } from './hooks'\n\ninterface FocusableProps extends UseFocusableOptions {\n children: React.ReactNode | ((focused: boolean) => React.ReactNode)\n as?: React.ElementType\n className?: string\n style?: React.CSSProperties\n}\n\nexport const Focusable = forwardRef<HTMLElement, FocusableProps>(\n ({ children, as: Tag = 'div', className, style, ...options }, _ref) => {\n const { ref, focused, focusableProps } = useFocusable(options)\n\n return (\n <Tag\n ref={ref as never}\n className={className}\n style={style}\n {...focusableProps}\n >\n {typeof children === 'function' ? children(focused) : children}\n </Tag>\n )\n },\n)\n\nFocusable.displayName = 'Focusable'\n\ninterface FocusGroupProps extends UseFocusGroupOptions {\n children: React.ReactNode\n as?: React.ElementType\n className?: string\n style?: React.CSSProperties\n}\n\nexport function FocusGroup({\n children,\n as: Tag = 'div',\n className,\n style,\n ...options\n}: FocusGroupProps) {\n const { groupId } = useFocusGroup(options)\n\n return (\n <Tag\n className={className}\n style={style}\n data-focus-group={groupId}\n >\n {children}\n </Tag>\n )\n}","import React, { useCallback, useEffect, useState, useTransition } from 'react'\nimport type { NavigateOptions, Route, RouteDefinition, RouteQuery } from './types'\nimport { matchRoute } from './matcher'\nimport { RouterContext } from './context'\n\ninterface RouterProps {\n routes: RouteDefinition[]\n initialPath?: string\n}\n\nfunction parseQuery(search: string): RouteQuery {\n const query: RouteQuery = {}\n const params = new URLSearchParams(search)\n params.forEach((value, key) => {\n const existing = query[key]\n if (existing === undefined) {\n query[key] = value\n } else if (Array.isArray(existing)) {\n existing.push(value)\n } else {\n query[key] = [existing, value]\n }\n })\n return query\n}\n\nfunction buildRoute(pathname: string, search: string, params: Record<string, string>): Route {\n return {\n path: pathname,\n params,\n query: parseQuery(search),\n }\n}\n\nexport function Router({ routes, initialPath = '/' }: RouterProps) {\n const [, startTransition] = useTransition()\n const [currentPath, setCurrentPath] = useState(() => {\n if (typeof window !== 'undefined') return window.location.pathname\n return initialPath\n })\n const [search, setSearch] = useState(() => {\n if (typeof window !== 'undefined') return window.location.search\n return ''\n })\n const [history, setHistory] = useState<Route[]>([])\n\n const match = matchRoute(currentPath, routes)\n const route = match\n ? buildRoute(currentPath, search, match.params)\n : buildRoute(currentPath, search, {})\n\n const navigate = useCallback(\n (path: string, options: NavigateOptions = {}) => {\n const [pathname, queryString] = path.split('?')\n const nextSearch = queryString ? `?${queryString}` : ''\n\n startTransition(() => {\n if (options.replace) {\n window.history.replaceState(null, '', path)\n } else {\n window.history.pushState(null, '', path)\n setHistory((prev) => [...prev, route])\n }\n setCurrentPath(pathname ?? '/')\n setSearch(nextSearch)\n })\n },\n [route],\n )\n\n const back = useCallback(() => {\n if (history.length === 0) return\n const prev = history[history.length - 1]\n if (!prev) return\n setHistory((h) => h.slice(0, -1))\n window.history.back()\n setCurrentPath(prev.path)\n setSearch('')\n }, [history])\n\n const prefetch = useCallback(\n (path: string) => {\n const [pathname] = path.split('?')\n if (!pathname) return\n const result = matchRoute(pathname, routes)\n if (result) {\n void result.route.component\n }\n },\n [routes],\n )\n\n useEffect(() => {\n const onPopState = () => {\n setCurrentPath(window.location.pathname)\n setSearch(window.location.search)\n }\n\n window.addEventListener('popstate', onPopState)\n return () => window.removeEventListener('popstate', onPopState)\n }, [])\n\n if (!match) {\n return <NotFound path={currentPath} />\n }\n\n const { component: Page } = match.route\n\n return (\n <RouterContext.Provider value={{ route, navigate, back, prefetch }}>\n <React.Suspense fallback={<PageLoader />}>\n <Page />\n </React.Suspense>\n </RouterContext.Provider>\n )\n}\n\nfunction NotFound({ path }: { path: string }) {\n return (\n <div data-table-not-found>\n <span>404 — {path} not found</span>\n </div>\n )\n}\n\nfunction PageLoader() {\n return <div data-table-loader />\n}\n","import type { RouteDefinition, RouteParams, RouteSegment } from './types'\n\nexport function parseSegments(path: string): RouteSegment[] {\n return path\n .split('/')\n .filter(Boolean)\n .map((segment) => {\n if (segment.startsWith('[...') && segment.endsWith(']')) {\n return { type: 'catch-all', value: segment.slice(4, -1) }\n }\n if (segment.startsWith('[') && segment.endsWith(']')) {\n return { type: 'dynamic', value: segment.slice(1, -1) }\n }\n return { type: 'static', value: segment }\n })\n}\n\nexport function matchRoute(\n pathname: string,\n routes: RouteDefinition[],\n): { route: RouteDefinition; params: RouteParams } | null {\n const incomingSegments = pathname.split(\"/\").filter(Boolean)\n\n for (const route of routes) {\n const result = matchSegments(incomingSegments, route.segments)\n if (result !== null) {\n return { route, params: result }\n }\n }\n\n return null\n}\n\nfunction matchSegments(incoming: string[], segments: RouteSegment[]): RouteParams | null {\n const params: RouteParams = {}\n let i = 0\n\n for (const segment of segments) {\n if (segment.type === 'catch-all') {\n params[segment.value] = incoming.slice(i).join('/')\n return params\n }\n\n const part = incoming[i]\n if (part === undefined) return null\n\n if (segment.type === 'static') {\n if (part !== segment.value) return null\n }\n\n if (segment.type === 'dynamic') {\n params[segment.value] = part\n }\n\n i++\n }\n\n if (i !== incoming.length) return null\n\n return params\n}\n","import { createContext, useContext } from 'react'\nimport type { NavigateOptions, Route } from './types'\n\nexport interface RouterContextValue {\n route: Route\n navigate: (path: string, options?: NavigateOptions) => void\n back: () => void\n prefetch: (path: string) => void\n}\n\nexport const RouterContext = createContext<RouterContextValue | null>(null)\n\nexport function useRouter(): RouterContextValue {\n const ctx = useContext(RouterContext)\n if (ctx === null) {\n throw new Error('useRouter must be used within a TableApp')\n }\n return ctx\n}\n\nexport function useParams<T extends Record<string, string>>(): T {\n const { route } = useRouter()\n return route.params as T\n}\n\nexport function useQuery<T extends Record<string, string | string[] | undefined>>(): T {\n const { route } = useRouter()\n return route.query as T\n}\n","import type { RouteDefinition } from './router/types'\nimport type { FocusManagerOptions } from './focus/types'\nimport { FocusProvider } from './focus'\nimport { Router } from './router/Router'\n\nexport interface TableAppProps {\n routes: RouteDefinition[]\n initialPath?: string\n focus?: FocusManagerOptions\n}\n\nexport function TableApp({ routes, initialPath, focus }: TableAppProps) {\n return (\n <FocusProvider {...(focus && { options: focus })}>\n <Router routes={routes} {...(initialPath && { initialPath })} />\n </FocusProvider>\n )\n}","import React from 'react'\nimport { parseSegments } from './matcher'\nimport type { RouteDefinition } from './types'\n\ntype RouteInput = {\n path: string\n component: () => Promise<{ default: React.ComponentType }>\n}\n\nexport function defineRoutes(routes: RouteInput[]): RouteDefinition[] {\n return routes.map((r) => ({\n filePath: r.path,\n segments: parseSegments(r.path),\n component: React.lazy(r.component),\n }))\n}"],"mappings":"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","names":["useEffect","useRef","useRef","useEffect","jsx","React","useCallback","useEffect","useState","createContext","useContext","jsx","useState","useCallback","useEffect","React","jsx","React","React"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@table-js/core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Table.js core runtime - router, focus, navigation for Smart TV",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"clean": "rm -rf dist"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"smart-tv",
|
|
26
|
+
"tizen",
|
|
27
|
+
"webos",
|
|
28
|
+
"android-tv",
|
|
29
|
+
"react",
|
|
30
|
+
"focus",
|
|
31
|
+
"router"
|
|
32
|
+
],
|
|
33
|
+
"author": "Table.js Team",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/tablejs/tablejs"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@table/tsconfig": "workspace:*",
|
|
41
|
+
"@types/react": "^19.2.14",
|
|
42
|
+
"react": "^19.2.4",
|
|
43
|
+
"react-dom": "^19.2.4",
|
|
44
|
+
"tsup": "^8.5.1",
|
|
45
|
+
"typescript": "^6.0.2"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
49
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
50
|
+
}
|
|
51
|
+
}
|