@uniflowed/hooks 0.0.0-alpha.2 → 0.0.0-alpha.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.
- package/async.js +170 -0
- package/browser.js +1033 -0
- package/channels.js +224 -0
- package/dom.js +407 -0
- package/events.js +298 -0
- package/index.js +197 -13
- package/keyboard.js +328 -0
- package/{internal/lifecycle.js → lifecycle.js} +21 -7
- package/package.json +16 -5
- package/render.js +354 -0
- package/state.js +520 -0
- package/timing.js +437 -0
- package/internal/async.js +0 -77
- package/internal/browser.js +0 -145
- package/internal/element.js +0 -186
- package/internal/state.js +0 -159
- package/internal/timing.js +0 -116
package/keyboard.js
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
//
|
|
3
|
+
// `@uniflowed/hooks/keyboard`: a chord is not an event.
|
|
4
|
+
//
|
|
5
|
+
// This is the third subject in the package that is neither one element nor the
|
|
6
|
+
// ambient environment, and it earns a file because none of the difficulty is
|
|
7
|
+
// in the listening. `document.addEventListener("keydown", …)` is one line; the
|
|
8
|
+
// four things after it are what people get wrong, and they get them wrong the
|
|
9
|
+
// same way in every project:
|
|
10
|
+
//
|
|
11
|
+
// **A combination is not a key.** `⌘K` is a `keydown` whose `key` is `"k"` and
|
|
12
|
+
// whose `metaKey` is true, and a handler that checks only the first fires on a
|
|
13
|
+
// plain `k` — so a search box opens while somebody is typing a name. Checking
|
|
14
|
+
// the modifiers that *are* named is half of it; refusing the ones that are not
|
|
15
|
+
// is the other half, and it is the half that gets left out.
|
|
16
|
+
//
|
|
17
|
+
// **`mod` is not `ctrl`.** The shortcut is ⌘K on a Mac and Ctrl+K everywhere
|
|
18
|
+
// else. A library that makes the caller write two bindings makes every caller
|
|
19
|
+
// write the same platform test.
|
|
20
|
+
//
|
|
21
|
+
// **A shortcut inside a text field is a keystroke.** `?` should open help,
|
|
22
|
+
// except while somebody is typing a question mark into a comment. The default
|
|
23
|
+
// here is that a chord does not fire while the reader is typing, and a caller
|
|
24
|
+
// who means it to says so.
|
|
25
|
+
//
|
|
26
|
+
// **A held key repeats.** Holding a key sends `keydown` twenty times a second.
|
|
27
|
+
// A shortcut that opens a dialog should open it once.
|
|
28
|
+
//
|
|
29
|
+
// # What belongs in this module
|
|
30
|
+
//
|
|
31
|
+
// A hook whose subject is what is being pressed: a chord, and whether a key is
|
|
32
|
+
// down. Not the key events of one element — `useEventListener(ref, "keydown",
|
|
33
|
+
// …)` in `dom.js` is that, and it is the right tool when the subject really is
|
|
34
|
+
// the element. These listen on the document by default, because a shortcut
|
|
35
|
+
// belongs to the page rather than to whatever happens to have focus.
|
|
36
|
+
//
|
|
37
|
+
// # Before hydration
|
|
38
|
+
//
|
|
39
|
+
// Nothing. Both hooks do their work in effects, `useKeyHeld` reports `false`
|
|
40
|
+
// on a server and in the first client render, and the platform test that
|
|
41
|
+
// decides what `mod` means is made inside the effect rather than during a
|
|
42
|
+
// render — so nothing here can differ between the two passes React compares.
|
|
43
|
+
|
|
44
|
+
import { useEffect, useState } from "@uniflowed/react";
|
|
45
|
+
|
|
46
|
+
import { browserWindow } from "./browser.js";
|
|
47
|
+
import type { Ref } from "./dom.js";
|
|
48
|
+
import { useStableCallback } from "./lifecycle.js";
|
|
49
|
+
|
|
50
|
+
/** A combination, once its spelling has been resolved. */
|
|
51
|
+
type Chord = {|
|
|
52
|
+
readonly key: string,
|
|
53
|
+
readonly ctrl: boolean,
|
|
54
|
+
readonly meta: boolean,
|
|
55
|
+
readonly alt: boolean,
|
|
56
|
+
readonly shift: boolean,
|
|
57
|
+
/** Ctrl, or Command on Apple platforms. */
|
|
58
|
+
readonly mod: boolean,
|
|
59
|
+
|};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The spellings people actually write, mapped to the one `KeyboardEvent.key`
|
|
63
|
+
* uses.
|
|
64
|
+
*
|
|
65
|
+
* `key` reports the character produced, so the arrows are `"ArrowUp"` and the
|
|
66
|
+
* space bar is a literal space — neither of which anybody writes in a
|
|
67
|
+
* shortcut.
|
|
68
|
+
*/
|
|
69
|
+
const KEY_ALIASES: { readonly [string]: string } = {
|
|
70
|
+
esc: "escape",
|
|
71
|
+
space: " ",
|
|
72
|
+
spacebar: " ",
|
|
73
|
+
ret: "enter",
|
|
74
|
+
return: "enter",
|
|
75
|
+
up: "arrowup",
|
|
76
|
+
down: "arrowdown",
|
|
77
|
+
left: "arrowleft",
|
|
78
|
+
right: "arrowright",
|
|
79
|
+
del: "delete",
|
|
80
|
+
plus: "+",
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Read `"mod+shift+k"` into a chord.
|
|
85
|
+
*
|
|
86
|
+
* Unknown modifier names are treated as the key rather than rejected: a typo
|
|
87
|
+
* produces a shortcut that never fires, which the author notices, and a throw
|
|
88
|
+
* during a render would take the page down instead.
|
|
89
|
+
*/
|
|
90
|
+
function parseChord(combo: string): Chord {
|
|
91
|
+
let ctrl = false;
|
|
92
|
+
let meta = false;
|
|
93
|
+
let alt = false;
|
|
94
|
+
let shift = false;
|
|
95
|
+
let mod = false;
|
|
96
|
+
let key = "";
|
|
97
|
+
|
|
98
|
+
for (const raw of combo.split("+")) {
|
|
99
|
+
const part = raw.trim().toLowerCase();
|
|
100
|
+
if (part === "") {
|
|
101
|
+
// `"shift++"` is shift and the plus key, and splitting leaves a hole.
|
|
102
|
+
key = "+";
|
|
103
|
+
} else if (part === "ctrl" || part === "control") {
|
|
104
|
+
ctrl = true;
|
|
105
|
+
} else if (part === "cmd" || part === "command" || part === "meta" || part === "super") {
|
|
106
|
+
meta = true;
|
|
107
|
+
} else if (part === "alt" || part === "opt" || part === "option") {
|
|
108
|
+
alt = true;
|
|
109
|
+
} else if (part === "shift") {
|
|
110
|
+
shift = true;
|
|
111
|
+
} else if (part === "mod") {
|
|
112
|
+
mod = true;
|
|
113
|
+
} else {
|
|
114
|
+
key = KEY_ALIASES[part] ?? part;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { key, ctrl, meta, alt, shift, mod };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Whether `mod` means Command here.
|
|
123
|
+
*
|
|
124
|
+
* Read from the user agent because that is the only thing every browser
|
|
125
|
+
* agrees on: `navigator.platform` is deprecated and frozen, and
|
|
126
|
+
* `userAgentData` exists in Chromium alone. Called from inside an effect, so a
|
|
127
|
+
* server render never asks.
|
|
128
|
+
*/
|
|
129
|
+
function onApple(): boolean {
|
|
130
|
+
const agent = browserWindow()?.navigator.userAgent ?? "";
|
|
131
|
+
return /mac|iphone|ipad|ipod/i.test(agent);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Whether the event landed in something the reader is typing into. */
|
|
135
|
+
function typing(target: EventTarget): boolean {
|
|
136
|
+
if (typeof HTMLElement === "undefined" || !(target instanceof HTMLElement)) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if (target.isContentEditable) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
const tag = target.tagName.toLowerCase();
|
|
143
|
+
return tag === "input" || tag === "textarea" || tag === "select";
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Whether a key is one that shift is needed to type.
|
|
148
|
+
*
|
|
149
|
+
* A single character that is neither a letter nor a digit: `"?"`, `"+"`,
|
|
150
|
+
* `"!"`. `KeyboardEvent.key` reports the character *produced*, so those arrive
|
|
151
|
+
* with `shiftKey` true on a US layout and false on layouts where they have
|
|
152
|
+
* their own key.
|
|
153
|
+
*/
|
|
154
|
+
function shiftedSymbol(key: string): boolean {
|
|
155
|
+
return key.length === 1 && !/[a-z0-9]/.test(key);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Whether `event` is this chord.
|
|
160
|
+
*
|
|
161
|
+
* The modifiers named must be down and the ones not named must be up. That
|
|
162
|
+
* second half is what makes `"mod+k"` refuse Ctrl+Shift+K, which is a
|
|
163
|
+
* different shortcut somebody else has probably bound.
|
|
164
|
+
*
|
|
165
|
+
* Shift is the one exception, and only for a key that shift is needed to
|
|
166
|
+
* type: `"?"` is the most common single-key shortcut on the web and arrives
|
|
167
|
+
* with `shiftKey` true on a US layout and false on a German one, so requiring
|
|
168
|
+
* either would make it unbindable on half the keyboards in the world. For a
|
|
169
|
+
* letter, a digit or a named key it is checked like the rest.
|
|
170
|
+
*/
|
|
171
|
+
function isChord(chord: Chord, event: KeyboardEvent, apple: boolean): boolean {
|
|
172
|
+
if (event.key.toLowerCase() !== chord.key) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
const meta = chord.meta || (chord.mod && apple);
|
|
176
|
+
const ctrl = chord.ctrl || (chord.mod && !apple);
|
|
177
|
+
if (event.metaKey !== meta || event.ctrlKey !== ctrl || event.altKey !== chord.alt) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
if (chord.shift) {
|
|
181
|
+
return event.shiftKey;
|
|
182
|
+
}
|
|
183
|
+
return shiftedSymbol(chord.key) || !event.shiftKey;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** `event` as a keyboard event, or `null` for anything else. */
|
|
187
|
+
function asKeyboardEvent(event: Event): KeyboardEvent | null {
|
|
188
|
+
if (typeof KeyboardEvent === "undefined" || !(event instanceof KeyboardEvent)) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
return event;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** How a chord is listened for. */
|
|
195
|
+
export type KeyComboOptions = {|
|
|
196
|
+
/** Listen on this element instead of the document. */
|
|
197
|
+
readonly target?: Ref<HTMLElement> | null,
|
|
198
|
+
/** Fire even while the reader is typing into a field. Off by default. */
|
|
199
|
+
readonly whileTyping?: boolean,
|
|
200
|
+
/** Fire again while the key is held down. Off by default. */
|
|
201
|
+
readonly repeat?: boolean,
|
|
202
|
+
/** Call `preventDefault` when it fires. On by default, since ⌘K is the browser's too. */
|
|
203
|
+
readonly preventDefault?: boolean,
|
|
204
|
+
/** Turn the binding off without changing where the hook is called. */
|
|
205
|
+
readonly enabled?: boolean,
|
|
206
|
+
|};
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Call `handler` when a key combination is pressed.
|
|
210
|
+
*
|
|
211
|
+
* ```js
|
|
212
|
+
* useKeyCombo("mod+k", () => setSearchOpen(true));
|
|
213
|
+
* useKeyCombo("escape", close, { whileTyping: true });
|
|
214
|
+
* ```
|
|
215
|
+
*
|
|
216
|
+
* `preventDefault` defaults to on because the combinations worth binding are
|
|
217
|
+
* the ones the browser also wants — ⌘K is the address bar in Chrome, ⌘S is
|
|
218
|
+
* Save Page — and a shortcut that fires *and* opens a browser dialog is worse
|
|
219
|
+
* than either alone. A chord that is only the application's, like `escape`,
|
|
220
|
+
* loses nothing by it.
|
|
221
|
+
*/
|
|
222
|
+
export hook useKeyCombo(
|
|
223
|
+
combo: string,
|
|
224
|
+
handler: (event: KeyboardEvent) => mixed,
|
|
225
|
+
options?: KeyComboOptions,
|
|
226
|
+
): void {
|
|
227
|
+
const stable = useStableCallback(handler);
|
|
228
|
+
const target = options?.target ?? null;
|
|
229
|
+
const whileTyping = options?.whileTyping ?? false;
|
|
230
|
+
const repeat = options?.repeat ?? false;
|
|
231
|
+
const preventDefault = options?.preventDefault ?? true;
|
|
232
|
+
const enabled = options?.enabled ?? true;
|
|
233
|
+
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
const win = browserWindow();
|
|
236
|
+
if (!enabled || win == null) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
// A ref that is given and empty means the element is not there yet, which
|
|
240
|
+
// is not the same as "no element was asked for": falling back to the
|
|
241
|
+
// document would bind the shortcut to the whole page by accident.
|
|
242
|
+
const node = target == null ? win.document : target.current;
|
|
243
|
+
if (node == null) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const chord = parseChord(combo);
|
|
248
|
+
const apple = onApple();
|
|
249
|
+
|
|
250
|
+
const listener = (event: Event) => {
|
|
251
|
+
const key = asKeyboardEvent(event);
|
|
252
|
+
if (key == null || (!repeat && key.repeat)) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (!whileTyping && typing(key.target)) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (!isChord(chord, key, apple)) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (preventDefault) {
|
|
262
|
+
key.preventDefault();
|
|
263
|
+
}
|
|
264
|
+
stable(key);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
node.addEventListener("keydown", listener);
|
|
268
|
+
return () => node.removeEventListener("keydown", listener);
|
|
269
|
+
}, [combo, enabled, target, whileTyping, repeat, preventDefault, stable]);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Whether a key is being held down.
|
|
274
|
+
*
|
|
275
|
+
* For the case a chord cannot express: a modifier that changes what a drag
|
|
276
|
+
* does while it is held, a space bar that pans a canvas. `key` is matched
|
|
277
|
+
* against `KeyboardEvent.key`, case-insensitively, so `"shift"`, `"escape"`
|
|
278
|
+
* and `" "` all work.
|
|
279
|
+
*
|
|
280
|
+
* The `blur` reset is the reason this is a hook rather than two listeners.
|
|
281
|
+
* Holding a key and switching windows sends the `keyup` to the other window,
|
|
282
|
+
* so a hand-written version leaves the key held forever — the canvas stays
|
|
283
|
+
* panning after the reader comes back. Losing focus releases everything.
|
|
284
|
+
*/
|
|
285
|
+
export hook useKeyHeld(
|
|
286
|
+
key: string,
|
|
287
|
+
options?: {| readonly target?: Ref<HTMLElement> | null |},
|
|
288
|
+
): boolean {
|
|
289
|
+
const [held, setHeld] = useState(false);
|
|
290
|
+
const target = options?.target ?? null;
|
|
291
|
+
const wanted = key.toLowerCase();
|
|
292
|
+
|
|
293
|
+
useEffect(() => {
|
|
294
|
+
const win = browserWindow();
|
|
295
|
+
if (win == null) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const node = target == null ? win.document : target.current;
|
|
299
|
+
if (node == null) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const down = (event: Event) => {
|
|
304
|
+
const pressed = asKeyboardEvent(event);
|
|
305
|
+
if (pressed != null && pressed.key.toLowerCase() === wanted) {
|
|
306
|
+
setHeld(true);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
const up = (event: Event) => {
|
|
310
|
+
const released = asKeyboardEvent(event);
|
|
311
|
+
if (released != null && released.key.toLowerCase() === wanted) {
|
|
312
|
+
setHeld(false);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
const release = () => setHeld(false);
|
|
316
|
+
|
|
317
|
+
node.addEventListener("keydown", down);
|
|
318
|
+
node.addEventListener("keyup", up);
|
|
319
|
+
win.addEventListener("blur", release);
|
|
320
|
+
return () => {
|
|
321
|
+
node.removeEventListener("keydown", down);
|
|
322
|
+
node.removeEventListener("keyup", up);
|
|
323
|
+
win.removeEventListener("blur", release);
|
|
324
|
+
};
|
|
325
|
+
}, [wanted, target]);
|
|
326
|
+
|
|
327
|
+
return held;
|
|
328
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
//
|
|
3
|
-
//
|
|
3
|
+
// `@uniflowed/hooks/lifecycle`: where a component is in its life.
|
|
4
4
|
//
|
|
5
5
|
// The one that matters most is `useStableCallback`. A callback recreated every
|
|
6
6
|
// render is the single most common cause of a React performance problem and of
|
|
@@ -8,6 +8,20 @@
|
|
|
8
8
|
// and the usual fix, listing the callback in a dependency array, spreads the
|
|
9
9
|
// problem to every hook that takes it. A stable identity that always calls the
|
|
10
10
|
// latest closure fixes it once.
|
|
11
|
+
//
|
|
12
|
+
// # What belongs in this module
|
|
13
|
+
//
|
|
14
|
+
// A hook whose subject is the component itself: has it mounted, is it still
|
|
15
|
+
// mounted, what did it render last time, run this once, run this on the way
|
|
16
|
+
// out, and the two effect-shaped primitives the rest of the package needs
|
|
17
|
+
// (`useIsomorphicLayoutEffect`, `useStableCallback`). None of them reads
|
|
18
|
+
// anything outside React.
|
|
19
|
+
//
|
|
20
|
+
// It is also the one module here the others import, which is a consequence and
|
|
21
|
+
// not the reason: `timing.js`, `state.js` and `dom.js` all need a stable
|
|
22
|
+
// callback. That does not make this a "utils" or a "common" — it has a subject
|
|
23
|
+
// of its own, and a hook lands here because it is about the component's life,
|
|
24
|
+
// never because two other files happened to want it.
|
|
11
25
|
|
|
12
26
|
import {
|
|
13
27
|
useCallback,
|
|
@@ -38,7 +52,7 @@ export const useIsomorphicLayoutEffect: typeof useLayoutEffect =
|
|
|
38
52
|
* — and it is written before any layout effect runs, so a subscription set up
|
|
39
53
|
* in one already sees the current body.
|
|
40
54
|
*/
|
|
41
|
-
export
|
|
55
|
+
export hook useStableCallback<TArgs extends $ReadOnlyArray<mixed>, TReturn>(
|
|
42
56
|
callback: (...args: TArgs) => TReturn,
|
|
43
57
|
): (...args: TArgs) => TReturn {
|
|
44
58
|
const latest = useRef(callback);
|
|
@@ -51,7 +65,7 @@ export function useStableCallback<TArgs extends $ReadOnlyArray<mixed>, TReturn>(
|
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
/** The value from the previous render, or `undefined` on the first. */
|
|
54
|
-
export
|
|
68
|
+
export hook usePrevious<T>(value: T): T | void {
|
|
55
69
|
const previous = useRef<T | void>(undefined);
|
|
56
70
|
useEffect(() => {
|
|
57
71
|
previous.current = value;
|
|
@@ -66,7 +80,7 @@ export function usePrevious<T>(value: T): T | void {
|
|
|
66
80
|
* the client's on the first pass would be a hydration mismatch: render the
|
|
67
81
|
* server's, then switch.
|
|
68
82
|
*/
|
|
69
|
-
export
|
|
83
|
+
export hook useMounted(): boolean {
|
|
70
84
|
const [mounted, setMounted] = useState(false);
|
|
71
85
|
useEffect(() => {
|
|
72
86
|
setMounted(true);
|
|
@@ -75,7 +89,7 @@ export function useMounted(): boolean {
|
|
|
75
89
|
}
|
|
76
90
|
|
|
77
91
|
/** Run `body` once, after mount. */
|
|
78
|
-
export
|
|
92
|
+
export hook useMount(body: () => mixed): void {
|
|
79
93
|
const stable = useStableCallback(body);
|
|
80
94
|
useEffect(() => {
|
|
81
95
|
stable();
|
|
@@ -83,7 +97,7 @@ export function useMount(body: () => mixed): void {
|
|
|
83
97
|
}
|
|
84
98
|
|
|
85
99
|
/** Run `body` once, at unmount. */
|
|
86
|
-
export
|
|
100
|
+
export hook useUnmount(body: () => mixed): void {
|
|
87
101
|
const stable = useStableCallback(body);
|
|
88
102
|
useEffect(() => () => void stable(), [stable]);
|
|
89
103
|
}
|
|
@@ -94,7 +108,7 @@ export function useUnmount(body: () => mixed): void {
|
|
|
94
108
|
* A counter rather than a boolean, because two renders in a row must both
|
|
95
109
|
* change the state or React drops the second.
|
|
96
110
|
*/
|
|
97
|
-
export
|
|
111
|
+
export hook useRerender(): () => void {
|
|
98
112
|
const [, setTick] = useState(0);
|
|
99
113
|
return useCallback(() => setTick((tick) => tick + 1), []);
|
|
100
114
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniflowed/hooks",
|
|
3
|
-
"version": "0.0.0-alpha.
|
|
3
|
+
"version": "0.0.0-alpha.21",
|
|
4
4
|
"description": "The React hooks an application writes anyway, prerender-safe, part of the Unified Toolchain for Flow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,14 +11,25 @@
|
|
|
11
11
|
"directory": "packages/hooks"
|
|
12
12
|
},
|
|
13
13
|
"exports": {
|
|
14
|
-
".": "./index.js"
|
|
14
|
+
".": "./index.js",
|
|
15
|
+
"./async": "./async.js",
|
|
16
|
+
"./browser": "./browser.js",
|
|
17
|
+
"./channels": "./channels.js",
|
|
18
|
+
"./dom": "./dom.js",
|
|
19
|
+
"./events": "./events.js",
|
|
20
|
+
"./keyboard": "./keyboard.js",
|
|
21
|
+
"./lifecycle": "./lifecycle.js",
|
|
22
|
+
"./render": "./render.js",
|
|
23
|
+
"./state": "./state.js",
|
|
24
|
+
"./timing": "./timing.js"
|
|
15
25
|
},
|
|
16
26
|
"files": [
|
|
17
|
-
"
|
|
18
|
-
"
|
|
27
|
+
"*.js",
|
|
28
|
+
"!*.test.js"
|
|
19
29
|
],
|
|
20
30
|
"dependencies": {
|
|
21
|
-
"@uniflowed/
|
|
31
|
+
"@uniflowed/core": "0.0.0-alpha.21",
|
|
32
|
+
"@uniflowed/react": "0.0.0-alpha.21"
|
|
22
33
|
},
|
|
23
34
|
"peerDependencies": {
|
|
24
35
|
"react": ">=19"
|