@silvery/term 0.3.0
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/package.json +54 -0
- package/src/adapters/canvas-adapter.ts +356 -0
- package/src/adapters/dom-adapter.ts +452 -0
- package/src/adapters/flexily-zero-adapter.ts +368 -0
- package/src/adapters/terminal-adapter.ts +305 -0
- package/src/adapters/yoga-adapter.ts +370 -0
- package/src/ansi/ansi.ts +251 -0
- package/src/ansi/constants.ts +76 -0
- package/src/ansi/detection.ts +441 -0
- package/src/ansi/hyperlink.ts +38 -0
- package/src/ansi/index.ts +201 -0
- package/src/ansi/patch-console.ts +159 -0
- package/src/ansi/sgr-codes.ts +34 -0
- package/src/ansi/storybook.ts +209 -0
- package/src/ansi/term.ts +724 -0
- package/src/ansi/types.ts +202 -0
- package/src/ansi/underline.ts +156 -0
- package/src/ansi/utils.ts +65 -0
- package/src/ansi-sanitize.ts +509 -0
- package/src/app.ts +571 -0
- package/src/bound-term.ts +94 -0
- package/src/bracketed-paste.ts +75 -0
- package/src/browser-renderer.ts +174 -0
- package/src/buffer.ts +1984 -0
- package/src/clipboard.ts +74 -0
- package/src/cursor-query.ts +85 -0
- package/src/device-attrs.ts +228 -0
- package/src/devtools.ts +123 -0
- package/src/dom/index.ts +194 -0
- package/src/errors.ts +39 -0
- package/src/focus-reporting.ts +48 -0
- package/src/hit-registry-core.ts +228 -0
- package/src/hit-registry.ts +176 -0
- package/src/index.ts +458 -0
- package/src/input.ts +119 -0
- package/src/inspector.ts +155 -0
- package/src/kitty-detect.ts +95 -0
- package/src/kitty-manager.ts +160 -0
- package/src/layout-engine.ts +296 -0
- package/src/layout.ts +26 -0
- package/src/measurer.ts +74 -0
- package/src/mode-query.ts +106 -0
- package/src/mouse-events.ts +419 -0
- package/src/mouse.ts +83 -0
- package/src/non-tty.ts +223 -0
- package/src/osc-markers.ts +32 -0
- package/src/osc-palette.ts +169 -0
- package/src/output.ts +406 -0
- package/src/pane-manager.ts +248 -0
- package/src/pipeline/CLAUDE.md +587 -0
- package/src/pipeline/content-phase-adapter.ts +976 -0
- package/src/pipeline/content-phase.ts +1765 -0
- package/src/pipeline/helpers.ts +42 -0
- package/src/pipeline/index.ts +416 -0
- package/src/pipeline/layout-phase.ts +686 -0
- package/src/pipeline/measure-phase.ts +198 -0
- package/src/pipeline/measure-stats.ts +21 -0
- package/src/pipeline/output-phase.ts +2593 -0
- package/src/pipeline/render-box.ts +343 -0
- package/src/pipeline/render-helpers.ts +243 -0
- package/src/pipeline/render-text.ts +1255 -0
- package/src/pipeline/types.ts +161 -0
- package/src/pipeline.ts +29 -0
- package/src/pixel-size.ts +119 -0
- package/src/render-adapter.ts +179 -0
- package/src/renderer.ts +1330 -0
- package/src/runtime/create-app.tsx +1845 -0
- package/src/runtime/create-buffer.ts +18 -0
- package/src/runtime/create-runtime.ts +325 -0
- package/src/runtime/diff.ts +56 -0
- package/src/runtime/event-handlers.ts +254 -0
- package/src/runtime/index.ts +119 -0
- package/src/runtime/keys.ts +8 -0
- package/src/runtime/layout.ts +164 -0
- package/src/runtime/run.tsx +318 -0
- package/src/runtime/term-provider.ts +399 -0
- package/src/runtime/terminal-lifecycle.ts +246 -0
- package/src/runtime/tick.ts +219 -0
- package/src/runtime/types.ts +210 -0
- package/src/scheduler.ts +723 -0
- package/src/screenshot.ts +57 -0
- package/src/scroll-region.ts +69 -0
- package/src/scroll-utils.ts +97 -0
- package/src/term-def.ts +267 -0
- package/src/terminal-caps.ts +5 -0
- package/src/terminal-colors.ts +216 -0
- package/src/termtest.ts +224 -0
- package/src/text-sizing.ts +109 -0
- package/src/toolbelt/index.ts +72 -0
- package/src/unicode.ts +1763 -0
- package/src/xterm/index.ts +491 -0
- package/src/xterm/xterm-provider.ts +204 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time/tick source for silvery-loop.
|
|
3
|
+
*
|
|
4
|
+
* Creates an AsyncIterable that yields at regular intervals.
|
|
5
|
+
* Used for animations, spinners, progress bars, etc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create a tick source that yields at regular intervals.
|
|
10
|
+
*
|
|
11
|
+
* The tick source respects AbortSignal for cleanup and stops
|
|
12
|
+
* when the signal is aborted.
|
|
13
|
+
*
|
|
14
|
+
* @param intervalMs Interval between ticks in milliseconds
|
|
15
|
+
* @param signal Optional AbortSignal to stop the tick source
|
|
16
|
+
* @returns AsyncIterable that yields tick numbers (0, 1, 2, ...)
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const controller = new AbortController()
|
|
21
|
+
* const ticks = createTick(100, controller.signal)
|
|
22
|
+
*
|
|
23
|
+
* for await (const tick of ticks) {
|
|
24
|
+
* console.log(`Tick ${tick}`)
|
|
25
|
+
* if (tick >= 10) controller.abort()
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function createTick(intervalMs: number, signal?: AbortSignal): AsyncIterable<number> {
|
|
30
|
+
return {
|
|
31
|
+
[Symbol.asyncIterator]: () => createTickIterator(intervalMs, signal),
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create the actual tick iterator.
|
|
37
|
+
*/
|
|
38
|
+
function createTickIterator(intervalMs: number, signal?: AbortSignal): AsyncIterator<number> {
|
|
39
|
+
let count = 0
|
|
40
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
41
|
+
let pendingResolve: ((result: IteratorResult<number>) => void) | undefined
|
|
42
|
+
let done = false
|
|
43
|
+
|
|
44
|
+
// Handle abort signal
|
|
45
|
+
const onAbort = () => {
|
|
46
|
+
done = true
|
|
47
|
+
if (timer) {
|
|
48
|
+
clearTimeout(timer)
|
|
49
|
+
timer = undefined
|
|
50
|
+
}
|
|
51
|
+
if (pendingResolve) {
|
|
52
|
+
pendingResolve({ done: true, value: undefined })
|
|
53
|
+
pendingResolve = undefined
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (signal) {
|
|
58
|
+
if (signal.aborted) {
|
|
59
|
+
done = true
|
|
60
|
+
} else {
|
|
61
|
+
signal.addEventListener("abort", onAbort, { once: true })
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
async next(): Promise<IteratorResult<number>> {
|
|
67
|
+
if (done) {
|
|
68
|
+
return { done: true, value: undefined }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return new Promise<IteratorResult<number>>((resolve, _reject) => {
|
|
72
|
+
pendingResolve = resolve
|
|
73
|
+
|
|
74
|
+
timer = setTimeout(() => {
|
|
75
|
+
if (!done) {
|
|
76
|
+
const value = count++
|
|
77
|
+
pendingResolve = undefined
|
|
78
|
+
resolve({ done: false, value })
|
|
79
|
+
}
|
|
80
|
+
}, intervalMs)
|
|
81
|
+
})
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
async return(): Promise<IteratorResult<number>> {
|
|
85
|
+
done = true
|
|
86
|
+
if (timer) {
|
|
87
|
+
clearTimeout(timer)
|
|
88
|
+
timer = undefined
|
|
89
|
+
}
|
|
90
|
+
if (signal) {
|
|
91
|
+
signal.removeEventListener("abort", onAbort)
|
|
92
|
+
}
|
|
93
|
+
if (pendingResolve) {
|
|
94
|
+
pendingResolve({ done: true, value: undefined })
|
|
95
|
+
pendingResolve = undefined
|
|
96
|
+
}
|
|
97
|
+
return { done: true, value: undefined }
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Create a tick source that yields at approximately 60fps (~16ms).
|
|
104
|
+
*
|
|
105
|
+
* @param signal Optional AbortSignal to stop the tick source
|
|
106
|
+
* @returns AsyncIterable that yields frame numbers
|
|
107
|
+
*/
|
|
108
|
+
export function createFrameTick(signal?: AbortSignal): AsyncIterable<number> {
|
|
109
|
+
return createTick(16, signal)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Create a tick source that yields once per second.
|
|
114
|
+
*
|
|
115
|
+
* @param signal Optional AbortSignal to stop the tick source
|
|
116
|
+
* @returns AsyncIterable that yields second counts
|
|
117
|
+
*/
|
|
118
|
+
export function createSecondTick(signal?: AbortSignal): AsyncIterable<number> {
|
|
119
|
+
return createTick(1000, signal)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Create a tick source with adaptive timing based on render performance.
|
|
124
|
+
*
|
|
125
|
+
* This is useful for maintaining a target frame rate while allowing
|
|
126
|
+
* for slower frames when needed.
|
|
127
|
+
*
|
|
128
|
+
* @param targetFps Target frames per second (default: 60)
|
|
129
|
+
* @param signal Optional AbortSignal to stop the tick source
|
|
130
|
+
* @returns AsyncIterable with timing information
|
|
131
|
+
*/
|
|
132
|
+
export function createAdaptiveTick(
|
|
133
|
+
targetFps = 60,
|
|
134
|
+
signal?: AbortSignal,
|
|
135
|
+
): AsyncIterable<{ tick: number; elapsed: number; delta: number }> {
|
|
136
|
+
const targetMs = 1000 / targetFps
|
|
137
|
+
let lastTime = Date.now()
|
|
138
|
+
let tick = 0
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
[Symbol.asyncIterator]: () => {
|
|
142
|
+
let done = false
|
|
143
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
144
|
+
let pendingResolve:
|
|
145
|
+
| ((
|
|
146
|
+
result: IteratorResult<{
|
|
147
|
+
tick: number
|
|
148
|
+
elapsed: number
|
|
149
|
+
delta: number
|
|
150
|
+
}>,
|
|
151
|
+
) => void)
|
|
152
|
+
| undefined
|
|
153
|
+
|
|
154
|
+
const onAbort = () => {
|
|
155
|
+
done = true
|
|
156
|
+
if (timer) {
|
|
157
|
+
clearTimeout(timer)
|
|
158
|
+
timer = undefined
|
|
159
|
+
}
|
|
160
|
+
if (pendingResolve) {
|
|
161
|
+
pendingResolve({ done: true, value: undefined })
|
|
162
|
+
pendingResolve = undefined
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (signal) {
|
|
167
|
+
if (signal.aborted) {
|
|
168
|
+
done = true
|
|
169
|
+
} else {
|
|
170
|
+
signal.addEventListener("abort", onAbort, { once: true })
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
async next(): Promise<IteratorResult<{ tick: number; elapsed: number; delta: number }>> {
|
|
176
|
+
if (done) {
|
|
177
|
+
return { done: true, value: undefined }
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return new Promise((resolve) => {
|
|
181
|
+
pendingResolve = resolve
|
|
182
|
+
const now = Date.now()
|
|
183
|
+
const elapsed = now - lastTime
|
|
184
|
+
const delay = Math.max(0, targetMs - elapsed)
|
|
185
|
+
|
|
186
|
+
timer = setTimeout(() => {
|
|
187
|
+
if (!done) {
|
|
188
|
+
const currentTime = Date.now()
|
|
189
|
+
const delta = currentTime - lastTime
|
|
190
|
+
lastTime = currentTime
|
|
191
|
+
pendingResolve = undefined
|
|
192
|
+
resolve({
|
|
193
|
+
done: false,
|
|
194
|
+
value: { tick: tick++, elapsed: currentTime, delta },
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
}, delay)
|
|
198
|
+
})
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
async return(): Promise<IteratorResult<{ tick: number; elapsed: number; delta: number }>> {
|
|
202
|
+
done = true
|
|
203
|
+
if (timer) {
|
|
204
|
+
clearTimeout(timer)
|
|
205
|
+
timer = undefined
|
|
206
|
+
}
|
|
207
|
+
if (signal) {
|
|
208
|
+
signal.removeEventListener("abort", onAbort)
|
|
209
|
+
}
|
|
210
|
+
if (pendingResolve) {
|
|
211
|
+
pendingResolve({ done: true, value: undefined })
|
|
212
|
+
pendingResolve = undefined
|
|
213
|
+
}
|
|
214
|
+
return { done: true, value: undefined }
|
|
215
|
+
},
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for the silvery-loop runtime.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { TerminalBuffer } from "../buffer"
|
|
6
|
+
import type { TeaNode } from "@silvery/tea/types"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Dimensions for rendering.
|
|
10
|
+
*/
|
|
11
|
+
export interface Dims {
|
|
12
|
+
cols: number
|
|
13
|
+
rows: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Immutable render output buffer.
|
|
18
|
+
*
|
|
19
|
+
* Contains:
|
|
20
|
+
* - text: Plain text without ANSI codes (for assertions)
|
|
21
|
+
* - ansi: Styled output with ANSI escape codes
|
|
22
|
+
* - nodes: Internal node tree for locator queries
|
|
23
|
+
*/
|
|
24
|
+
export interface Buffer {
|
|
25
|
+
/** Plain text without ANSI codes */
|
|
26
|
+
readonly text: string
|
|
27
|
+
/** Styled output with ANSI escape codes */
|
|
28
|
+
readonly ansi: string
|
|
29
|
+
/** Internal node tree for locator queries */
|
|
30
|
+
readonly nodes: TeaNode
|
|
31
|
+
/** Raw terminal buffer for diffing */
|
|
32
|
+
readonly _buffer: TerminalBuffer
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Event types from the runtime.
|
|
37
|
+
*/
|
|
38
|
+
export type Event =
|
|
39
|
+
| {
|
|
40
|
+
type: "key"
|
|
41
|
+
key: string
|
|
42
|
+
ctrl?: boolean
|
|
43
|
+
meta?: boolean
|
|
44
|
+
shift?: boolean
|
|
45
|
+
}
|
|
46
|
+
| {
|
|
47
|
+
type: "mouse"
|
|
48
|
+
button: number
|
|
49
|
+
x: number
|
|
50
|
+
y: number
|
|
51
|
+
action: "down" | "up" | "move" | "wheel"
|
|
52
|
+
delta?: number
|
|
53
|
+
shift: boolean
|
|
54
|
+
meta: boolean
|
|
55
|
+
ctrl: boolean
|
|
56
|
+
}
|
|
57
|
+
| { type: "paste"; content: string }
|
|
58
|
+
| { type: "resize"; cols: number; rows: number }
|
|
59
|
+
| { type: "tick"; time: number }
|
|
60
|
+
| { type: "effect"; id: string; result: unknown }
|
|
61
|
+
| { type: "error"; error: Error }
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Render target interface - abstracts terminal output.
|
|
65
|
+
*/
|
|
66
|
+
export interface RenderTarget {
|
|
67
|
+
/** Write rendered frame to output */
|
|
68
|
+
write(frame: string): void
|
|
69
|
+
/** Get current dimensions */
|
|
70
|
+
getDims(): Dims
|
|
71
|
+
/** Subscribe to resize events */
|
|
72
|
+
onResize?(handler: (dims: Dims) => void): () => void
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Runtime options for createRuntime().
|
|
77
|
+
*/
|
|
78
|
+
export interface RuntimeOptions {
|
|
79
|
+
/** Render target (terminal, test mock, etc.) */
|
|
80
|
+
target: RenderTarget
|
|
81
|
+
/** Abort signal for cleanup */
|
|
82
|
+
signal?: AbortSignal
|
|
83
|
+
/** Render mode: fullscreen (alt screen) or inline (scrollback-compatible) */
|
|
84
|
+
mode?: "fullscreen" | "inline"
|
|
85
|
+
/** Scoped output phase function (from createOutputPhase/createPipeline). When provided,
|
|
86
|
+
* runtime.render() uses this instead of the raw outputPhase — ensures measurer/caps are threaded. */
|
|
87
|
+
outputPhaseFn?: (
|
|
88
|
+
prev: import("../buffer.js").TerminalBuffer | null,
|
|
89
|
+
next: import("../buffer.js").TerminalBuffer,
|
|
90
|
+
mode?: "fullscreen" | "inline",
|
|
91
|
+
scrollbackOffset?: number,
|
|
92
|
+
termRows?: number,
|
|
93
|
+
) => string
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The runtime kernel interface.
|
|
98
|
+
*/
|
|
99
|
+
export interface Runtime {
|
|
100
|
+
/** Event stream - yields until disposed */
|
|
101
|
+
events(): AsyncIterable<Event>
|
|
102
|
+
|
|
103
|
+
/** Schedule an effect with optional cancellation */
|
|
104
|
+
schedule<T>(effect: () => Promise<T>, opts?: { signal?: AbortSignal }): void
|
|
105
|
+
|
|
106
|
+
/** Render a buffer to the target */
|
|
107
|
+
render(buffer: Buffer): void
|
|
108
|
+
|
|
109
|
+
/** Report lines written to stdout between renders (inline mode only) */
|
|
110
|
+
addScrollbackLines(lines: number): void
|
|
111
|
+
|
|
112
|
+
/** Reset diff state so next render outputs a full frame */
|
|
113
|
+
invalidate(): void
|
|
114
|
+
|
|
115
|
+
/** Reset inline cursor tracking state (inline mode only).
|
|
116
|
+
* Called by useScrollback before re-emitting frozen items on resize. */
|
|
117
|
+
resetInlineCursor(): void
|
|
118
|
+
|
|
119
|
+
/** Get inline cursor row relative to render region start. -1 if unknown. */
|
|
120
|
+
getInlineCursorRow(): number
|
|
121
|
+
|
|
122
|
+
/** Promote frozen content to scrollback via the output phase.
|
|
123
|
+
* Content is written in a single frame with the live render — no flicker. */
|
|
124
|
+
promoteScrollback(content: string, lines: number): void
|
|
125
|
+
|
|
126
|
+
/** Get current dimensions */
|
|
127
|
+
getDims(): Dims
|
|
128
|
+
|
|
129
|
+
/** Dispose and cleanup - idempotent */
|
|
130
|
+
[Symbol.dispose](): void
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// Provider Types
|
|
135
|
+
// ============================================================================
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Event emitted by a provider, tagged with event type.
|
|
139
|
+
*/
|
|
140
|
+
export type ProviderEvent<Events extends Record<string, unknown>> = {
|
|
141
|
+
[K in keyof Events]: { type: K; data: Events[K] }
|
|
142
|
+
}[keyof Events]
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Provider interface - unified store + event source.
|
|
146
|
+
*
|
|
147
|
+
* Providers are the building blocks of silvery-loop applications.
|
|
148
|
+
* They encapsulate:
|
|
149
|
+
* - State (Zustand-compatible: getState/subscribe)
|
|
150
|
+
* - Events (AsyncIterable of typed events)
|
|
151
|
+
* - Cleanup (Symbol.dispose)
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```typescript
|
|
155
|
+
* type TermProvider = Provider<
|
|
156
|
+
* { cols: number; rows: number },
|
|
157
|
+
* { key: { input: string; key: Key }; resize: Dims }
|
|
158
|
+
* >;
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export interface Provider<State = unknown, Events extends Record<string, unknown> = Record<string, never>> {
|
|
162
|
+
/** Get current state (Zustand-compatible) */
|
|
163
|
+
getState(): State
|
|
164
|
+
|
|
165
|
+
/** Subscribe to state changes (Zustand-compatible) */
|
|
166
|
+
subscribe(listener: (state: State) => void): () => void
|
|
167
|
+
|
|
168
|
+
/** Event stream - yields typed events until disposed */
|
|
169
|
+
events(): AsyncIterable<ProviderEvent<Events>>
|
|
170
|
+
|
|
171
|
+
/** Cleanup resources */
|
|
172
|
+
[Symbol.dispose](): void
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Extract the namespaced event type from a providers map.
|
|
177
|
+
*
|
|
178
|
+
* Given { term: TermProvider, sync: SyncProvider }, produces:
|
|
179
|
+
* | { type: 'term:key'; data: { input: string; key: Key } }
|
|
180
|
+
* | { type: 'term:resize'; data: Dims }
|
|
181
|
+
* | { type: 'sync:data'; data: Item[] }
|
|
182
|
+
* | ...
|
|
183
|
+
*/
|
|
184
|
+
export type NamespacedEvent<Providers extends Record<string, Provider<unknown, Record<string, unknown>>>> = {
|
|
185
|
+
[P in keyof Providers]: Providers[P] extends Provider<unknown, infer E>
|
|
186
|
+
? {
|
|
187
|
+
[K in keyof E & string]: {
|
|
188
|
+
type: `${P & string}:${K}`
|
|
189
|
+
data: E[K]
|
|
190
|
+
}
|
|
191
|
+
}[keyof E & string]
|
|
192
|
+
: never
|
|
193
|
+
}[keyof Providers]
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Extract all event keys from a providers map.
|
|
197
|
+
*
|
|
198
|
+
* Given { term: TermProvider, sync: SyncProvider }, produces:
|
|
199
|
+
* 'term:key' | 'term:resize' | 'sync:data' | ...
|
|
200
|
+
*/
|
|
201
|
+
export type ProviderEventKey<Providers extends Record<string, Provider<unknown, Record<string, unknown>>>> =
|
|
202
|
+
NamespacedEvent<Providers>["type"]
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get the data type for a specific event key.
|
|
206
|
+
*/
|
|
207
|
+
export type EventData<
|
|
208
|
+
Providers extends Record<string, Provider<unknown, Record<string, unknown>>>,
|
|
209
|
+
K extends ProviderEventKey<Providers>,
|
|
210
|
+
> = Extract<NamespacedEvent<Providers>, { type: K }>["data"]
|