stitchkit 0.113.3 → 0.114.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/CHANGELOG.md +42 -0
- package/README.md +1 -1
- package/dist/browser/client-url.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime/coding-tools.js +1 -1
- package/dist/entrypoints/agent-runtime/harness.js +4 -4
- package/dist/entrypoints/agent-runtime/realtime.js +2 -2
- package/dist/entrypoints/agent-runtime/sandbox.js +3 -3
- package/dist/entrypoints/agent-runtime/sqlite/bun.js +3 -3
- package/dist/entrypoints/agent-runtime/sqlite/node.js +3 -3
- package/dist/entrypoints/agent-runtime/testing.js +4 -4
- package/dist/entrypoints/agent-runtime.js +12 -12
- package/dist/entrypoints/application/directory-inbox.js +3 -3
- package/dist/entrypoints/application.js +4 -4
- package/dist/entrypoints/cli/publish.js +1 -1
- package/dist/entrypoints/cli.js +4 -4
- package/dist/entrypoints/contract.js +2 -2
- package/dist/entrypoints/http.js +2 -2
- package/dist/entrypoints/index.js +5 -5
- package/dist/entrypoints/node.js +3 -3
- package/dist/entrypoints/observability.js +3 -3
- package/dist/entrypoints/react/keyboard/testing.d.ts +2 -0
- package/dist/entrypoints/react/keyboard/testing.d.ts.map +1 -0
- package/dist/entrypoints/react/keyboard/testing.js +127 -0
- package/dist/entrypoints/react/keyboard.d.ts +4 -2
- package/dist/entrypoints/react/keyboard.d.ts.map +1 -1
- package/dist/entrypoints/react/keyboard.js +83 -319
- package/dist/entrypoints/react.js +15 -3
- package/dist/entrypoints/realtime.js +1 -1
- package/dist/entrypoints/remote.js +1 -1
- package/dist/entrypoints/server.js +18 -18
- package/dist/entrypoints/telegram/grammy.d.ts +2 -0
- package/dist/entrypoints/telegram/grammy.d.ts.map +1 -0
- package/dist/entrypoints/telegram/grammy.js +50 -0
- package/dist/entrypoints/telegram/screens.js +1 -1
- package/dist/entrypoints/telegram.d.ts +1 -0
- package/dist/entrypoints/telegram.d.ts.map +1 -1
- package/dist/entrypoints/telegram.js +254 -8
- package/dist/entrypoints/testing.js +3 -3
- package/dist/entrypoints/tools/connections.js +10 -10
- package/dist/entrypoints/tools.js +19 -19
- package/dist/entrypoints/tracking.js +1 -1
- package/dist/{index-xvj9h8yh.js → index-61hve0ez.js} +15 -28
- package/dist/index-6j725ez5.js +25 -0
- package/dist/{index-1csp1hwm.js → index-6p3frkv5.js} +1 -1
- package/dist/{index-fy4av0hq.js → index-9bwcthxw.js} +1 -1
- package/dist/{index-523tq7y4.js → index-a3462dqf.js} +1 -1
- package/dist/{index-506kd458.js → index-aw6zahv1.js} +1 -1
- package/dist/{index-tpwbct8j.js → index-dc9ere5p.js} +1 -1
- package/dist/{index-erv5shjq.js → index-kdnfe0sr.js} +1 -1
- package/dist/{index-k7z3j3hr.js → index-km2qw407.js} +4 -4
- package/dist/{index-7fc0c2np.js → index-n3v695sb.js} +1 -1
- package/dist/index-p02zgk8x.js +351 -0
- package/dist/{index-vtann8bz.js → index-p0rf7sq9.js} +8 -8
- package/dist/{index-aykhzcnf.js → index-qj1sy4ep.js} +1 -1
- package/dist/{index-cxpcdswt.js → index-t65erk4z.js} +1 -1
- package/dist/{index-c5at3ykd.js → index-tsngj2b5.js} +22 -10
- package/dist/react/entity-cache.d.ts +24 -2
- package/dist/react/entity-cache.d.ts.map +1 -1
- package/dist/react/keyboard/hooks.d.ts +41 -10
- package/dist/react/keyboard/hooks.d.ts.map +1 -1
- package/dist/react/keyboard/layers.d.ts +27 -8
- package/dist/react/keyboard/layers.d.ts.map +1 -1
- package/dist/react/keyboard/modality.d.ts +21 -0
- package/dist/react/keyboard/modality.d.ts.map +1 -0
- package/dist/react/keyboard/refs.d.ts +13 -0
- package/dist/react/keyboard/refs.d.ts.map +1 -0
- package/dist/react/keyboard/testing.d.ts +80 -0
- package/dist/react/keyboard/testing.d.ts.map +1 -0
- package/dist/react/keyboard/zone.d.ts +60 -10
- package/dist/react/keyboard/zone.d.ts.map +1 -1
- package/dist/telegram/budget-transformer.d.ts +35 -0
- package/dist/telegram/budget-transformer.d.ts.map +1 -0
- package/dist/telegram/outbound-budget.d.ts +101 -0
- package/dist/telegram/outbound-budget.d.ts.map +1 -0
- package/dist/telegram/send-failure.d.ts +6 -1
- package/dist/telegram/send-failure.d.ts.map +1 -1
- package/llms/react-keyboard-testing.txt +24 -0
- package/llms/react-keyboard.txt +264 -4
- package/llms/react.txt +1 -1
- package/llms/server.3.txt +31 -0
- package/llms/server.4.txt +5 -0
- package/llms/stitchkit.2.txt +118 -0
- package/llms/stitchkit.txt +8 -122
- package/llms/telegram-grammy.txt +22 -0
- package/llms/telegram.txt +76 -2
- package/llms/{upgrading-0.103.13-0.113.0.txt → upgrading-0.104.0-0.114.0.txt} +45 -84
- package/llms/upgrading-0.37.0-0.44.0.txt +444 -0
- package/llms/{upgrading-0.44.0-0.60.0.txt → upgrading-0.44.0-0.61.0.txt} +140 -271
- package/llms/{upgrading-0.61.0-0.74.0.txt → upgrading-0.62.0-0.78.0.txt} +137 -140
- package/llms/{upgrading-0.75.0-0.103.0.txt → upgrading-0.79.0-0.103.13.txt} +84 -137
- package/llms/upgrading.txt +5 -5
- package/llms-full.txt +453 -11
- package/llms.txt +12 -10
- package/package.json +9 -1
- package/llms/upgrading-0.37.0.txt +0 -176
- package/dist/{index-616xh7f2.js → index-4qnzda76.js} +0 -0
- package/dist/{index-fsw1ybf3.js → index-d9hq291n.js} +3 -3
- package/dist/{index-bjcjqj46.js → index-m8a44e64.js} +3 -3
- package/dist/{index-5efrpfmj.js → index-qndsr7f0.js} +3 -3
- package/dist/{index-zktz242w.js → index-s47ac8zt.js} +3 -3
- package/dist/{index-56yx8dxp.js → index-tvtacc9a.js} +3 -3
- /package/dist/{index-v1tswj0z.js → index-txg1npmg.js} +0 -0
|
@@ -29,8 +29,16 @@
|
|
|
29
29
|
* the key — a widget's own `onKeyDown`, a text field, a popover that closes on
|
|
30
30
|
* Escape — hears it first and keeps it by `preventDefault` or
|
|
31
31
|
* `stopPropagation`. A layer only gets what nobody nearer wanted.
|
|
32
|
+
*
|
|
33
|
+
* One kind of layer is the exception: a `local` layer may ask for the capture
|
|
34
|
+
* phase. A menu that hangs over a text field keeps focus in the field and wants
|
|
35
|
+
* the arrows and Enter *before* the field acts on them; the field cannot be
|
|
36
|
+
* made to wait, so the layer goes first and, when it takes a key, nothing nearer
|
|
37
|
+
* hears it at all.
|
|
32
38
|
*/
|
|
33
39
|
export type KeyLayerKind = 'overlay' | 'local' | 'route' | 'zone' | 'global';
|
|
40
|
+
/** Where in the event's path a layer listens: after everything nearer, or before it. */
|
|
41
|
+
export type KeyLayerPhase = 'bubble' | 'capture';
|
|
34
42
|
/** The part of a `keydown` event the stack reads; a DOM `KeyboardEvent` is one. */
|
|
35
43
|
export interface LayerKeyEvent {
|
|
36
44
|
readonly key: string;
|
|
@@ -39,6 +47,8 @@ export interface LayerKeyEvent {
|
|
|
39
47
|
readonly defaultPrevented: boolean;
|
|
40
48
|
readonly target: EventTarget | null;
|
|
41
49
|
preventDefault(): void;
|
|
50
|
+
/** A DOM event has it; a layer that takes a key in the capture phase uses it. */
|
|
51
|
+
stopPropagation?(): void;
|
|
42
52
|
}
|
|
43
53
|
/** The part of an element the stack reads to decide whether a layer is live and where it sits. */
|
|
44
54
|
export interface LayerScope<S> {
|
|
@@ -52,8 +62,8 @@ export interface LayerScope<S> {
|
|
|
52
62
|
* that move on arrows themselves, and any area marked `data-local-keys`.
|
|
53
63
|
*/
|
|
54
64
|
export declare const KEY_FIELD_SELECTOR: string;
|
|
55
|
-
/** The nearest element matching `selectors` from an event target, or `null`. */
|
|
56
|
-
export declare function closestFromTarget(target:
|
|
65
|
+
/** The nearest element matching `selectors` from an event target or an element, or `null`. */
|
|
66
|
+
export declare function closestFromTarget(target: unknown, selectors: string): unknown;
|
|
57
67
|
/** The key was pressed in a field or an area that keeps its keys. */
|
|
58
68
|
export declare function isKeyFieldTarget(target: EventTarget | null): boolean;
|
|
59
69
|
export interface KeyLayerState<E extends LayerKeyEvent, S extends LayerScope<S>> {
|
|
@@ -73,6 +83,11 @@ export interface KeyLayerState<E extends LayerKeyEvent, S extends LayerScope<S>>
|
|
|
73
83
|
* `preventDefault`. `'include'`: the layer hears them too.
|
|
74
84
|
*/
|
|
75
85
|
readonly fields?: 'skip' | 'include';
|
|
86
|
+
/**
|
|
87
|
+
* Default `'bubble'`. `'capture'` is for a `local` layer only: it hears the key
|
|
88
|
+
* before the element that has focus does, and a key it takes goes no further.
|
|
89
|
+
*/
|
|
90
|
+
readonly phase?: KeyLayerPhase;
|
|
76
91
|
/** Return `true` when the key was taken: its default is prevented and no layer below hears it. */
|
|
77
92
|
onKey(event: E): boolean;
|
|
78
93
|
}
|
|
@@ -82,17 +97,21 @@ export interface KeyLayerHandle<E extends LayerKeyEvent, S extends LayerScope<S>
|
|
|
82
97
|
}
|
|
83
98
|
/** Where the stack listens. A `Window` is one. */
|
|
84
99
|
export interface KeyListenerTarget<E> {
|
|
85
|
-
addEventListener(type: 'keydown', listener: (event: E) => void): void;
|
|
86
|
-
removeEventListener(type: 'keydown', listener: (event: E) => void): void;
|
|
100
|
+
addEventListener(type: 'keydown', listener: (event: E) => void, capture?: boolean): void;
|
|
101
|
+
removeEventListener(type: 'keydown', listener: (event: E) => void, capture?: boolean): void;
|
|
87
102
|
}
|
|
88
103
|
export interface KeyLayerStack<E extends LayerKeyEvent, S extends LayerScope<S>> {
|
|
89
104
|
add(state: KeyLayerState<E, S>): KeyLayerHandle<E, S>;
|
|
90
|
-
/**
|
|
91
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Offer one key to the layers that listen in `phase` (default `'bubble'`);
|
|
107
|
+
* `true` when one took it. The installed listeners call this.
|
|
108
|
+
*/
|
|
109
|
+
dispatch(event: E, phase?: KeyLayerPhase): boolean;
|
|
92
110
|
}
|
|
93
111
|
/**
|
|
94
|
-
* A stack listening on `target`.
|
|
95
|
-
*
|
|
112
|
+
* A stack listening on `target`. A listener is attached with the first layer of
|
|
113
|
+
* its phase and detached with the last, so a page without layers carries none,
|
|
114
|
+
* and a page with no capture layer carries no capture listener.
|
|
96
115
|
*/
|
|
97
116
|
export declare function createKeyLayerStack<E extends LayerKeyEvent, S extends LayerScope<S>>(target: KeyListenerTarget<E>): KeyLayerStack<E, S>;
|
|
98
117
|
//# sourceMappingURL=layers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../../src/react/keyboard/layers.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../../src/react/keyboard/layers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a menu, a popover or a panel was opened with the keyboard.
|
|
3
|
+
*
|
|
4
|
+
* A script's `focus()` shows a focus ring when the element that had focus
|
|
5
|
+
* before it was showing one, whatever opened the thing: a menu opened by a right
|
|
6
|
+
* click, with focus on `body`, would still draw its ring on the first item, and
|
|
7
|
+
* nobody pressed a key. The cure is to decide at the moment of opening: move
|
|
8
|
+
* focus to the first item only when this answers `true`, and otherwise leave it
|
|
9
|
+
* on the menu's container.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* `true` when the element that has focus right now shows a focus ring — the
|
|
13
|
+
* browser's own guess that the person is using the keyboard. Call it in the
|
|
14
|
+
* event handler that opens the menu, before anything moves focus: after the
|
|
15
|
+
* menu has taken focus the answer is about the menu.
|
|
16
|
+
*
|
|
17
|
+
* `false` on a server, with nothing focused, or in a browser without
|
|
18
|
+
* `:focus-visible`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function openedByKeyboard(): boolean;
|
|
21
|
+
//# sourceMappingURL=modality.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modality.d.ts","sourceRoot":"","sources":["../../../src/react/keyboard/modality.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAQ1C"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Ref } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* One ref callback for several refs: a zone's `ref` beside a drag handle's, a
|
|
4
|
+
* measuring hook's or the application's own. Spread the zone's props, then set
|
|
5
|
+
* `ref: mergeRefs(props.ref, other)` — a later `ref` replaces an earlier one,
|
|
6
|
+
* and a merged ref keeps both.
|
|
7
|
+
*
|
|
8
|
+
* The result is a new function on every call. React detaches and re-attaches a
|
|
9
|
+
* callback ref whose identity changes, so a component that renders often keeps
|
|
10
|
+
* it in `useMemo`/`useCallback`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function mergeRefs<T>(...refs: readonly (Ref<T> | undefined)[]): (node: T | null) => void;
|
|
13
|
+
//# sourceMappingURL=refs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../../../src/react/keyboard/refs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAEjC;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,CAAC,EACzB,GAAG,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,EAAE,GACvC,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,CAO1B"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard layers and zones without a DOM, a browser or React.
|
|
3
|
+
*
|
|
4
|
+
* The stack and the zone controller are plain objects that read a few members
|
|
5
|
+
* of an element and of a key event. This file is those members as fakes: a
|
|
6
|
+
* window that remembers its listener, elements with a parent, `inert` and
|
|
7
|
+
* visibility, key events with `preventDefault`. A test builds the layers a
|
|
8
|
+
* screen declares, presses keys on elements, and reads who heard what — the
|
|
9
|
+
* order of layers, `scope` and `inert`, the edges of a zone — in milliseconds.
|
|
10
|
+
*
|
|
11
|
+
* What it cannot show is a real focus ring, scrolling or a screen reader; that
|
|
12
|
+
* is a browser's.
|
|
13
|
+
*/
|
|
14
|
+
import { type KeyLayerStack, type LayerScope } from './layers.js';
|
|
15
|
+
import { type ZoneItemElement, type ZoneKeyEvent } from './zone.js';
|
|
16
|
+
export interface TestElementInit {
|
|
17
|
+
/** The element this one is inside. */
|
|
18
|
+
readonly parent?: TestElement;
|
|
19
|
+
/** An input, text area or combobox: it keeps its own keys. */
|
|
20
|
+
readonly field?: boolean;
|
|
21
|
+
/** An area marked `data-local-keys`. */
|
|
22
|
+
readonly localKeys?: boolean;
|
|
23
|
+
/** The element of a zone (`data-keyboard-zone`). */
|
|
24
|
+
readonly zone?: boolean;
|
|
25
|
+
/** Inside an `inert` subtree, or itself `inert`. Change it later with `element.inert`. */
|
|
26
|
+
readonly inert?: boolean;
|
|
27
|
+
/** Not visible (`checkVisibility()` is `false`). Change it later with `element.hidden`. */
|
|
28
|
+
readonly hidden?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** An element as the stack and the zone read it. Also a zone's item: it counts the focus it gets. */
|
|
31
|
+
export interface TestElement extends LayerScope<TestElement>, ZoneItemElement, EventTarget {
|
|
32
|
+
readonly parent: TestElement | null;
|
|
33
|
+
isConnected: boolean;
|
|
34
|
+
inert: boolean;
|
|
35
|
+
hidden: boolean;
|
|
36
|
+
/** How many times `focus()` was called on it. */
|
|
37
|
+
focusCount: number;
|
|
38
|
+
/** How many times it was scrolled into view. */
|
|
39
|
+
scrollCount: number;
|
|
40
|
+
/** A new element inside this one. */
|
|
41
|
+
child(init?: Omit<TestElementInit, 'parent'>): TestElement;
|
|
42
|
+
}
|
|
43
|
+
export interface TestKeyInit {
|
|
44
|
+
readonly target?: EventTarget | null;
|
|
45
|
+
readonly repeat?: boolean;
|
|
46
|
+
readonly isComposing?: boolean;
|
|
47
|
+
readonly altKey?: boolean;
|
|
48
|
+
readonly ctrlKey?: boolean;
|
|
49
|
+
readonly metaKey?: boolean;
|
|
50
|
+
readonly shiftKey?: boolean;
|
|
51
|
+
}
|
|
52
|
+
/** A `keydown` as the stack and the zone read it. */
|
|
53
|
+
export interface TestKeyEvent extends ZoneKeyEvent {
|
|
54
|
+
defaultPrevented: boolean;
|
|
55
|
+
/** A layer took the key in the capture phase, so nothing nearer — the field — would hear it. */
|
|
56
|
+
readonly propagationStopped: boolean;
|
|
57
|
+
stopPropagation(): void;
|
|
58
|
+
}
|
|
59
|
+
export interface KeyboardTestBed {
|
|
60
|
+
/** The stack, on a window that is not one: add layers to it as the hooks do. */
|
|
61
|
+
readonly stack: KeyLayerStack<TestKeyEvent, TestElement>;
|
|
62
|
+
/** Whether a window listener is attached: with the first layer, off with the last. */
|
|
63
|
+
readonly listening: boolean;
|
|
64
|
+
/** Whether the capture-phase listener is attached: only while a layer asks for that phase. */
|
|
65
|
+
readonly listeningInCapture: boolean;
|
|
66
|
+
element(init?: TestElementInit): TestElement;
|
|
67
|
+
/** A key event, not yet pressed. Hand it to `zone.keyDown(event, zoneElement)`. */
|
|
68
|
+
key(name: string, init?: TestKeyInit): TestKeyEvent;
|
|
69
|
+
/**
|
|
70
|
+
* Press a key on `init.target` through the window listeners: the capture
|
|
71
|
+
* phase first, then — unless a layer took the key there — the bubble phase.
|
|
72
|
+
* `event.defaultPrevented` is "a layer took it".
|
|
73
|
+
*/
|
|
74
|
+
press(name: string, init?: TestKeyInit): TestKeyEvent;
|
|
75
|
+
}
|
|
76
|
+
/** A fresh stack, window, element and key factory for one test. */
|
|
77
|
+
export declare function createKeyboardTestBed(): KeyboardTestBed;
|
|
78
|
+
export { createKeyLayerStack, type KeyLayerHandle, type KeyLayerStack, type KeyLayerState, type KeyListenerTarget, type LayerKeyEvent, type LayerScope, } from './layers.js';
|
|
79
|
+
export { createNavZone, type NavZoneController, type ZoneItemElement } from './zone.js';
|
|
80
|
+
//# sourceMappingURL=testing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../../src/react/keyboard/testing.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,12 +7,15 @@
|
|
|
7
7
|
* follows it, `:focus-visible` draws it and the browser scrolls to it; there is
|
|
8
8
|
* no second "highlighted" state beside focus for a page to keep in sync.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* Three modes, by what moving means:
|
|
11
11
|
*
|
|
12
12
|
* - **select** — moving is choosing: the arrow calls `onChange(id, 'arrow')`
|
|
13
13
|
* (a sidebar whose item opens as you move).
|
|
14
14
|
* - **highlight** — moving only points; Enter chooses with
|
|
15
15
|
* `onChange(id, 'confirm')` (a list where opening is expensive).
|
|
16
|
+
* - **focus** — moving only moves focus, and choosing belongs to the items: a
|
|
17
|
+
* list of links or buttons keeps its own Enter, so the zone reports nothing
|
|
18
|
+
* and takes no Enter (a menu, a row of toolbar buttons).
|
|
16
19
|
*
|
|
17
20
|
* Each zone keeps its own pointed item, so two zones on a page never share one.
|
|
18
21
|
* The cross axis leaves the zone on purpose: `onEnter` (→ in a vertical zone)
|
|
@@ -50,6 +53,7 @@ export interface NextZoneIndexInput {
|
|
|
50
53
|
* no index: every move runs past an end.
|
|
51
54
|
*/
|
|
52
55
|
export declare function nextZoneIndex({ length, index, move, loop, }: NextZoneIndexInput): number | 'before-start' | 'after-end';
|
|
56
|
+
/** A key and the modifiers that make a zone leave it alone. */
|
|
53
57
|
export interface ZoneKey {
|
|
54
58
|
readonly key: string;
|
|
55
59
|
readonly altKey: boolean;
|
|
@@ -64,11 +68,6 @@ export type ZoneChangeCause = 'arrow' | 'confirm';
|
|
|
64
68
|
export interface NavZoneOptions<Id extends ZoneItemId> {
|
|
65
69
|
/** The items in order. An id not listed here has no place in the zone. */
|
|
66
70
|
readonly items: readonly Id[];
|
|
67
|
-
/** The chosen item, or `null`. The zone never holds it: it reports changes. */
|
|
68
|
-
readonly value: Id | null;
|
|
69
|
-
onChange(id: Id, cause: ZoneChangeCause): void;
|
|
70
|
-
/** Default `'select'`. */
|
|
71
|
-
readonly mode?: 'select' | 'highlight';
|
|
72
71
|
/** Default `'vertical'`. */
|
|
73
72
|
readonly orientation?: ZoneOrientation;
|
|
74
73
|
/**
|
|
@@ -103,7 +102,46 @@ export interface NavZoneOptions<Id extends ZoneItemId> {
|
|
|
103
102
|
* its element mounts.
|
|
104
103
|
*/
|
|
105
104
|
onReveal?(id: Id): void;
|
|
105
|
+
/**
|
|
106
|
+
* What the first arrow does when focus is in no zone — right after a click on
|
|
107
|
+
* the page, or on the empty part of a screen. Default `'focus'`: it puts focus
|
|
108
|
+
* on the current item and nothing else, so the person sees where they are
|
|
109
|
+
* before anything changes. `'move'`: it acts as if focus were already on the
|
|
110
|
+
* current item — the arrow moves from it, and in select mode chooses the
|
|
111
|
+
* neighbour — for a list that answers the first arrow with a change.
|
|
112
|
+
*/
|
|
113
|
+
readonly outside?: 'focus' | 'move';
|
|
114
|
+
/**
|
|
115
|
+
* A key that is the application's own right now — an item being dragged with
|
|
116
|
+
* the keyboard uses the arrows itself. Returning `true` leaves the key
|
|
117
|
+
* entirely to the page: the zone neither moves, nor takes it, nor calls
|
|
118
|
+
* `preventDefault`. It is asked for every key the zone would act on, inside
|
|
119
|
+
* the zone and outside it.
|
|
120
|
+
*/
|
|
121
|
+
ignore?(event: ZoneKeyEvent): boolean;
|
|
122
|
+
/** The chosen item, or `null`. The zone never holds it: it reports changes. */
|
|
123
|
+
readonly value: Id | null;
|
|
124
|
+
onChange(id: Id, cause: ZoneChangeCause): void;
|
|
125
|
+
/** Default `'select'`. */
|
|
126
|
+
readonly mode?: 'select' | 'highlight';
|
|
106
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* A zone whose items choose for themselves: arrows move focus, Enter stays with
|
|
130
|
+
* the focused element, and nothing is reported. `value` only marks the item the
|
|
131
|
+
* page is on (`aria-current` for `role: 'none'`).
|
|
132
|
+
*/
|
|
133
|
+
export type FocusZoneOptions<Id extends ZoneItemId> = Omit<NavZoneOptions<Id>, 'mode' | 'onChange' | 'value'> & {
|
|
134
|
+
readonly mode: 'focus';
|
|
135
|
+
/** The item the page is on, if any. Optional: a menu has none. */
|
|
136
|
+
readonly value?: Id | null;
|
|
137
|
+
};
|
|
138
|
+
export type ZoneOptions<Id extends ZoneItemId> = NavZoneOptions<Id> | FocusZoneOptions<Id>;
|
|
139
|
+
/**
|
|
140
|
+
* Where the zone's tab stop is: the pointed item, else the chosen one, else the
|
|
141
|
+
* first — an index into `items`, `-1` for an empty zone. One scan when an item
|
|
142
|
+
* is pointed. Pure: a render may call it with the options it is rendering.
|
|
143
|
+
*/
|
|
144
|
+
export declare function resolveZoneIndex<Id extends ZoneItemId>(items: readonly Id[], value: Id | null, pointed: Id | null): number;
|
|
107
145
|
/** The part of an item element the zone drives. An `HTMLElement` is one. */
|
|
108
146
|
export interface ZoneItemElement {
|
|
109
147
|
focus(options?: {
|
|
@@ -114,11 +152,23 @@ export interface ZoneItemElement {
|
|
|
114
152
|
inline?: 'nearest';
|
|
115
153
|
}): void;
|
|
116
154
|
}
|
|
117
|
-
|
|
155
|
+
/** The key an `ignore` callback is asked about, and what a zone reads of a key it is handed. */
|
|
156
|
+
export interface ZoneKeyEvent extends LayerKeyEvent {
|
|
157
|
+
readonly altKey: boolean;
|
|
158
|
+
readonly ctrlKey: boolean;
|
|
159
|
+
readonly metaKey: boolean;
|
|
160
|
+
readonly shiftKey: boolean;
|
|
118
161
|
}
|
|
119
162
|
export interface NavZoneController<Id extends ZoneItemId> {
|
|
120
|
-
|
|
121
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Adopt the options of a committed render. A new `value` takes the pointed
|
|
165
|
+
* item back to the chosen one, and subscribers hear it. A render that never
|
|
166
|
+
* commits must not call this.
|
|
167
|
+
*/
|
|
168
|
+
update(options: ZoneOptions<Id>): void;
|
|
169
|
+
readonly options: ZoneOptions<Id>;
|
|
170
|
+
/** The item focus is on or was last on, if any — raw, before the chosen item and the list are consulted. */
|
|
171
|
+
pointedItem(): Id | null;
|
|
122
172
|
/** The tab stop: the pointed item, else the chosen one, else the first. */
|
|
123
173
|
current(): Id | null;
|
|
124
174
|
/** A key pressed inside the zone's element (`zone` is that element). `true` when taken. */
|
|
@@ -135,5 +185,5 @@ export interface NavZoneController<Id extends ZoneItemId> {
|
|
|
135
185
|
setItemElement(id: Id, element: ZoneItemElement | null): void;
|
|
136
186
|
subscribe(listener: () => void): () => void;
|
|
137
187
|
}
|
|
138
|
-
export declare function createNavZone<Id extends ZoneItemId>(initial:
|
|
188
|
+
export declare function createNavZone<Id extends ZoneItemId>(initial: ZoneOptions<Id>): NavZoneController<Id>;
|
|
139
189
|
//# sourceMappingURL=zone.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zone.d.ts","sourceRoot":"","sources":["../../../src/react/keyboard/zone.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"zone.d.ts","sourceRoot":"","sources":["../../../src/react/keyboard/zone.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The outbound budget as a grammY client transformer.
|
|
3
|
+
*
|
|
4
|
+
* `budget.run` is the narrow adapter; wiring it by hand means writing the same
|
|
5
|
+
* closure in every bot and deciding, again, what a cancelled wait is. This is
|
|
6
|
+
* that closure once: `bot.api.config.use(telegramBudgetTransformer(budget))`.
|
|
7
|
+
*
|
|
8
|
+
* grammY types the signal it hands a transformer as the `abort-controller`
|
|
9
|
+
* polyfill's, which is not the platform `AbortSignal` the budget waits on, so a
|
|
10
|
+
* hand-written adapter either casts it or passes `undefined` and loses
|
|
11
|
+
* cancellation. The transformer bridges it, and a wait that is cancelled before
|
|
12
|
+
* the request was handed to the client is reported as what it is —
|
|
13
|
+
* `TelegramNotDispatchedError`, safe to repeat — while a request cancelled in
|
|
14
|
+
* flight stays the client's own error, because it may have arrived.
|
|
15
|
+
*
|
|
16
|
+
* grammY is named in the declarations and never imported at run time.
|
|
17
|
+
*/
|
|
18
|
+
import type { Transformer } from 'grammy';
|
|
19
|
+
import type { TelegramOutboundBudget } from './outbound-budget.js';
|
|
20
|
+
/**
|
|
21
|
+
* A grammY client transformer that runs every Bot API call under `budget`.
|
|
22
|
+
*
|
|
23
|
+
* Install it as the bot's one transformer; it replaces any limiter written
|
|
24
|
+
* beside it. A refusal Telegram returns as a response, not only a thrown one,
|
|
25
|
+
* pauses the token (see `TelegramOutboundBudget.run`), and the call is never
|
|
26
|
+
* repeated here.
|
|
27
|
+
*
|
|
28
|
+
* A cancellation that arrives while the call still waits for its turn, or in
|
|
29
|
+
* the instant after it was granted and before the request was handed on, throws
|
|
30
|
+
* `TelegramNotDispatchedError`: nothing was sent. A cancellation after the
|
|
31
|
+
* request was handed on is the client's ordinary error and the outcome is
|
|
32
|
+
* unknown, not a definite failure.
|
|
33
|
+
*/
|
|
34
|
+
export declare function telegramBudgetTransformer(budget: TelegramOutboundBudget): Transformer;
|
|
35
|
+
//# sourceMappingURL=budget-transformer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget-transformer.d.ts","sourceRoot":"","sources":["../../src/telegram/budget-transformer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAE1C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AA4BhE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,sBAAsB,GAAG,WAAW,CAsBrF"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One outbound budget for one Telegram bot token.
|
|
3
|
+
*
|
|
4
|
+
* Telegram counts a bot's bulk messages per token, not per subsystem. A bot
|
|
5
|
+
* that sends a marketing campaign, answers a person and posts an operator note
|
|
6
|
+
* through three senders has one limit, and three separate pacemakers cannot
|
|
7
|
+
* keep it: each of them is below the limit on its own and the sum is not.
|
|
8
|
+
*
|
|
9
|
+
* This is the one place that counts. The token names the budget; every alias
|
|
10
|
+
* of the same token in the process gets the same object, and another token is
|
|
11
|
+
* another budget. The unit is a Telegram message: `sendMediaGroup` and
|
|
12
|
+
* `sendPaidMedia` cost their media length, `copyMessages` and
|
|
13
|
+
* `forwardMessages` cost their message ids, and the follow-up call to action
|
|
14
|
+
* after an album is one more message.
|
|
15
|
+
*
|
|
16
|
+
* **It paces; it does not retry.** A 429 pauses every sender of the token for
|
|
17
|
+
* the wait Telegram stated (or a declared fallback when it stated none). A
|
|
18
|
+
* network failure or a 5xx is returned to the caller unchanged: repeating a
|
|
19
|
+
* send whose external outcome is unknown is the caller's decision, and a
|
|
20
|
+
* primitive that retried it would turn one unknown message into two.
|
|
21
|
+
*
|
|
22
|
+
* The schedule is a strict token budget, not a bucket with a full burst: the
|
|
23
|
+
* first call after an idle period is immediate, and every later call occupies
|
|
24
|
+
* its own `cost / messagesPerSecond` interval. A full bucket would release a
|
|
25
|
+
* saved burst at start and again after a 429 pause, which is exactly the
|
|
26
|
+
* stampede a shared budget exists to prevent.
|
|
27
|
+
*/
|
|
28
|
+
export interface TelegramOutboundBudgetConfig {
|
|
29
|
+
/** The bot token whose sends share this budget. It is never returned or logged. */
|
|
30
|
+
readonly token: string;
|
|
31
|
+
/** Telegram messages per second. Default 24. */
|
|
32
|
+
readonly messagesPerSecond?: number;
|
|
33
|
+
/**
|
|
34
|
+
* How long a 429 without `retry_after` pauses the token. Default 1000 ms.
|
|
35
|
+
* Telegram's own `retry_after` is always used instead when present.
|
|
36
|
+
*/
|
|
37
|
+
readonly retryAfterFallbackMs?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Test seam: the monotonic clock. Default `performance.now`. A wall clock can
|
|
40
|
+
* jump backwards and turn a pause into a burst, so the default is monotonic.
|
|
41
|
+
*/
|
|
42
|
+
readonly now?: () => number;
|
|
43
|
+
/**
|
|
44
|
+
* Test seam: how a wait is taken. Default the shared abortable sleep. A
|
|
45
|
+
* custom sleeper must reject with `signal.reason` when the signal aborts.
|
|
46
|
+
*/
|
|
47
|
+
readonly sleep?: (milliseconds: number, signal?: AbortSignal) => Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
/** What the budget has done, with no token and no message ids. */
|
|
50
|
+
export interface TelegramOutboundBudgetStats {
|
|
51
|
+
/** Calls admitted by `acquire` or `run`. */
|
|
52
|
+
readonly granted: number;
|
|
53
|
+
/** Weighted messages admitted: an album of three counts as three. */
|
|
54
|
+
readonly messages: number;
|
|
55
|
+
/** Calls waiting for their turn. */
|
|
56
|
+
readonly queued: number;
|
|
57
|
+
readonly messagesPerSecond: number;
|
|
58
|
+
/** How much of the current 429 pause is still ahead, in milliseconds. */
|
|
59
|
+
readonly pausedForMs: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A token's outbound budget. `run` is the narrow adapter a client transformer
|
|
63
|
+
* calls around its own send; it gates, pauses on 429 and never retries.
|
|
64
|
+
*/
|
|
65
|
+
export interface TelegramOutboundBudget {
|
|
66
|
+
/**
|
|
67
|
+
* Run one Bot API call under the budget. A non-send method passes through
|
|
68
|
+
* without spending a message. A 429 pauses every sender of the token, whether
|
|
69
|
+
* `send` throws it or resolves with the Bot API's `{ ok: false, … }` body, as
|
|
70
|
+
* a client transformer's `send` does. The error is rethrown, or the response
|
|
71
|
+
* returned, unchanged and the call is not repeated.
|
|
72
|
+
*/
|
|
73
|
+
run<T>(method: string, payload: unknown, signal: AbortSignal | undefined, send: () => Promise<T>): Promise<T>;
|
|
74
|
+
/**
|
|
75
|
+
* Wait until `cost` messages fit. The wait rejects with `signal.reason`
|
|
76
|
+
* without spending a token; a granted call owns its slots even if its later
|
|
77
|
+
* send is aborted, which the caller must not repeat as a definite failure.
|
|
78
|
+
*/
|
|
79
|
+
acquire(cost: number, signal?: AbortSignal): Promise<void>;
|
|
80
|
+
/** Pause every sender of this token for at least `milliseconds`. Never shortens an existing pause. */
|
|
81
|
+
pause(milliseconds: number): void;
|
|
82
|
+
/** Pause the token after a rate-limited refusal; another failure does nothing. */
|
|
83
|
+
pauseAfter(error: unknown): void;
|
|
84
|
+
/**
|
|
85
|
+
* Wait out the current pause without spending a token. For a retry path that
|
|
86
|
+
* is not a send (edit, delete) and does not go through `acquire`.
|
|
87
|
+
*/
|
|
88
|
+
waitUntilResumed(signal?: AbortSignal): Promise<void>;
|
|
89
|
+
/** Measured counters. It never contains the token. */
|
|
90
|
+
stats(): TelegramOutboundBudgetStats;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The one budget for `config.token`, created on first use.
|
|
94
|
+
*
|
|
95
|
+
* Two aliases of one token share it; two tokens never do. A second call for a
|
|
96
|
+
* token already in use must agree on the options, otherwise the process has
|
|
97
|
+
* two incompatible policies for one credential and the call is refused. The
|
|
98
|
+
* refusal never contains the token.
|
|
99
|
+
*/
|
|
100
|
+
export declare function telegramOutboundBudget(config: TelegramOutboundBudgetConfig): TelegramOutboundBudget;
|
|
101
|
+
//# sourceMappingURL=outbound-budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound-budget.d.ts","sourceRoot":"","sources":["../../src/telegram/outbound-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAwEH,MAAM,WAAW,4BAA4B;IAC3C,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC;;;OAGG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC;;;OAGG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChF;AAED,kEAAkE;AAClE,MAAM,WAAW,2BAA2B;IAC1C,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oCAAoC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,yEAAyE;IACzE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;;OAMG;IACH,GAAG,CAAC,CAAC,EACH,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACrB,OAAO,CAAC,CAAC,CAAC,CAAC;IACd;;;;OAIG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,sGAAsG;IACtG,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,kFAAkF;IAClF,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACjC;;;OAGG;IACH,gBAAgB,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,sDAAsD;IACtD,KAAK,IAAI,2BAA2B,CAAC;CACtC;AAyMD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,4BAA4B,GACnC,sBAAsB,CAsCxB"}
|
|
@@ -30,8 +30,13 @@
|
|
|
30
30
|
* user who never pressed Start needs an invitation rather than a retry, and a
|
|
31
31
|
* chat Telegram does not know is usually a bad identifier on our side — a
|
|
32
32
|
* defect in our data, not a fact about a person.
|
|
33
|
+
*
|
|
34
|
+
* `bot-to-bot-disabled` is permanent for this send but does not accuse the
|
|
35
|
+
* recipient: Telegram disables bot-to-bot messaging when *either* bot has not
|
|
36
|
+
* enabled its Bot to Bot setting, so the missing setting may be the sender's.
|
|
37
|
+
* It is never retried, and it never marks the recipient unreachable.
|
|
33
38
|
*/
|
|
34
|
-
export type TelegramSendFailureReason = 'blocked-by-user' | 'user-deactivated' | 'chat-not-found' | 'not-started' | 'rate-limited' | 'message-invalid' | 'server-error' | 'not-dispatched' | 'unknown';
|
|
39
|
+
export type TelegramSendFailureReason = 'blocked-by-user' | 'user-deactivated' | 'chat-not-found' | 'not-started' | 'bot-to-bot-disabled' | 'rate-limited' | 'message-invalid' | 'server-error' | 'not-dispatched' | 'unknown';
|
|
35
40
|
export interface TelegramSendFailure {
|
|
36
41
|
reason: TelegramSendFailureReason;
|
|
37
42
|
/** Telegram's `error_code`, when the failure carried one. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send-failure.d.ts","sourceRoot":"","sources":["../../src/telegram/send-failure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAKH
|
|
1
|
+
{"version":3,"file":"send-failure.d.ts","sourceRoot":"","sources":["../../src/telegram/send-failure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAKH;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,yBAAyB,GACjC,iBAAiB,GACjB,kBAAkB,GAClB,gBAAgB,GAChB,aAAa,GACb,qBAAqB,GACrB,cAAc,GACd,iBAAiB,GACjB,cAAc,GACd,gBAAgB,GAChB,SAAS,CAAC;AAEd,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,yBAAyB,CAAC;IAClC,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wDAAwD;IACxD,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;;;;OAQG;IACH,oBAAoB,EAAE,OAAO,CAAC;IAC9B;;;;;OAKG;IACH,QAAQ,EAAE,YAAY,GAAG,QAAQ,GAAG,aAAa,GAAG,WAAW,GAAG,MAAM,CAAC;CAC1E;AAoDD;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,EAAE,SAAS,yBAAyB,EAC7B,CAAC;AA4ClD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CA4D/E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,mBAAmB,GAC3B,cAAc,GACd,cAAc,GACd,gBAAgB,GAChB,eAAe,GACf,OAAO,CAAC;AAeZ,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CAM/E"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# stitchkit/react/keyboard/testing — stitchkit docs slice
|
|
2
|
+
|
|
3
|
+
> Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
|
|
4
|
+
> `llms.txt` in this package.
|
|
5
|
+
|
|
6
|
+
Contains: API reference: `stitchkit/react/keyboard/testing` (docs/api/reference.md)
|
|
7
|
+
|
|
8
|
+
==============================================================================
|
|
9
|
+
# API reference: `stitchkit/react/keyboard/testing` (docs/api/reference.md)
|
|
10
|
+
==============================================================================
|
|
11
|
+
|
|
12
|
+
## `stitchkit/react/keyboard/testing`
|
|
13
|
+
|
|
14
|
+
Keyboard layers and zones without a DOM, a browser or React: fakes for the
|
|
15
|
+
window, elements and key events, and the two engines the hooks are made of.
|
|
16
|
+
Peer-free. A test builds the layers a screen declares, presses keys on elements
|
|
17
|
+
and reads who heard what — see the [keyboard guide](../guide/keyboard.md#testing-without-a-dom).
|
|
18
|
+
|
|
19
|
+
| Export | Kind | Summary |
|
|
20
|
+
|--------|------|---------|
|
|
21
|
+
| `createKeyboardTestBed` | function | a fresh stack, window, element factory and key factory: `stack.add(layer)`, `press(key, { target })`, `element({ parent, field, zone, inert, hidden })`, `key(name)` for a zone, and `listening` |
|
|
22
|
+
| `KeyboardTestBed` / `TestElement` / `TestElementInit` / `TestKeyEvent` / `TestKeyInit` | _type_ | the test bed, an element (also a zone item: it counts `focus()` and scrolling), what builds one, and a key event with `defaultPrevented` |
|
|
23
|
+
| `createKeyLayerStack` / `KeyLayerStack` / `KeyLayerHandle` / `KeyLayerState` / `KeyListenerTarget` / `LayerKeyEvent` / `LayerScope` | function / _type_ | the layer stack the hooks put on the window, over any listener target and elements that have `contains`, `closest`, `isConnected` |
|
|
24
|
+
| `createNavZone` / `NavZoneController` / `ZoneItemElement` | function / _type_ | a zone controller without React: `keyDown`, `keyFromOutside`, `focused`, `focus`, `update`, `current`, `pointedItem`, `subscribe` |
|