@textui/core 0.1.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/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/adapters/index.d.ts +12 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +11 -0
- package/dist/app/app.d.ts +200 -0
- package/dist/app/app.d.ts.map +1 -0
- package/dist/app/app.js +980 -0
- package/dist/core/animation.d.ts +34 -0
- package/dist/core/animation.d.ts.map +1 -0
- package/dist/core/animation.js +125 -0
- package/dist/core/clipboard.d.ts +29 -0
- package/dist/core/clipboard.d.ts.map +1 -0
- package/dist/core/clipboard.js +30 -0
- package/dist/core/commands.d.ts +42 -0
- package/dist/core/commands.d.ts.map +1 -0
- package/dist/core/commands.js +146 -0
- package/dist/core/components.d.ts +27 -0
- package/dist/core/components.d.ts.map +1 -0
- package/dist/core/components.js +78 -0
- package/dist/core/events.d.ts +23 -0
- package/dist/core/events.d.ts.map +1 -0
- package/dist/core/events.js +77 -0
- package/dist/core/focus.d.ts +69 -0
- package/dist/core/focus.d.ts.map +1 -0
- package/dist/core/focus.js +336 -0
- package/dist/core/i18n.d.ts +27 -0
- package/dist/core/i18n.d.ts.map +1 -0
- package/dist/core/i18n.js +88 -0
- package/dist/core/keybindings.d.ts +53 -0
- package/dist/core/keybindings.d.ts.map +1 -0
- package/dist/core/keybindings.js +163 -0
- package/dist/core/layers.d.ts +28 -0
- package/dist/core/layers.d.ts.map +1 -0
- package/dist/core/layers.js +84 -0
- package/dist/core/manifest.d.ts +22 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +85 -0
- package/dist/core/navigation.d.ts +47 -0
- package/dist/core/navigation.d.ts.map +1 -0
- package/dist/core/navigation.js +110 -0
- package/dist/core/resources.d.ts +115 -0
- package/dist/core/resources.d.ts.map +1 -0
- package/dist/core/resources.js +321 -0
- package/dist/core/services.d.ts +21 -0
- package/dist/core/services.d.ts.map +1 -0
- package/dist/core/services.js +63 -0
- package/dist/core/store.d.ts +60 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/store.js +593 -0
- package/dist/core/surfaces.d.ts +81 -0
- package/dist/core/surfaces.d.ts.map +1 -0
- package/dist/core/surfaces.js +237 -0
- package/dist/core/syntax.d.ts +49 -0
- package/dist/core/syntax.d.ts.map +1 -0
- package/dist/core/syntax.js +172 -0
- package/dist/core/when.d.ts +15 -0
- package/dist/core/when.d.ts.map +1 -0
- package/dist/core/when.js +224 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/jsx/factory.d.ts +27 -0
- package/dist/jsx/factory.d.ts.map +1 -0
- package/dist/jsx/factory.js +102 -0
- package/dist/jsx/intrinsics.d.ts +96 -0
- package/dist/jsx/intrinsics.d.ts.map +1 -0
- package/dist/jsx/intrinsics.js +1 -0
- package/dist/jsx/jsx-dev-runtime.d.ts +13 -0
- package/dist/jsx/jsx-dev-runtime.d.ts.map +1 -0
- package/dist/jsx/jsx-dev-runtime.js +12 -0
- package/dist/jsx/jsx-runtime.d.ts +42 -0
- package/dist/jsx/jsx-runtime.d.ts.map +1 -0
- package/dist/jsx/jsx-runtime.js +16 -0
- package/dist/render/buffer.d.ts +77 -0
- package/dist/render/buffer.d.ts.map +1 -0
- package/dist/render/buffer.js +275 -0
- package/dist/render/color.d.ts +35 -0
- package/dist/render/color.d.ts.map +1 -0
- package/dist/render/color.js +160 -0
- package/dist/render/diff.d.ts +37 -0
- package/dist/render/diff.d.ts.map +1 -0
- package/dist/render/diff.js +66 -0
- package/dist/render/layout.d.ts +72 -0
- package/dist/render/layout.d.ts.map +1 -0
- package/dist/render/layout.js +633 -0
- package/dist/render/static.d.ts +63 -0
- package/dist/render/static.d.ts.map +1 -0
- package/dist/render/static.js +209 -0
- package/dist/runtime/bindings.d.ts +46 -0
- package/dist/runtime/bindings.d.ts.map +1 -0
- package/dist/runtime/bindings.js +116 -0
- package/dist/runtime/hooks.d.ts +284 -0
- package/dist/runtime/hooks.d.ts.map +1 -0
- package/dist/runtime/hooks.js +846 -0
- package/dist/runtime/instance.d.ts +106 -0
- package/dist/runtime/instance.d.ts.map +1 -0
- package/dist/runtime/instance.js +181 -0
- package/dist/runtime/paint.d.ts +27 -0
- package/dist/runtime/paint.d.ts.map +1 -0
- package/dist/runtime/paint.js +567 -0
- package/dist/runtime/reconcile.d.ts +23 -0
- package/dist/runtime/reconcile.d.ts.map +1 -0
- package/dist/runtime/reconcile.js +260 -0
- package/dist/runtime/runtime.d.ts +42 -0
- package/dist/runtime/runtime.d.ts.map +1 -0
- package/dist/runtime/runtime.js +1 -0
- package/dist/runtime/style.d.ts +55 -0
- package/dist/runtime/style.d.ts.map +1 -0
- package/dist/runtime/style.js +143 -0
- package/dist/themes/borders.d.ts +10 -0
- package/dist/themes/borders.d.ts.map +1 -0
- package/dist/themes/borders.js +88 -0
- package/dist/themes/builtin.d.ts +31 -0
- package/dist/themes/builtin.d.ts.map +1 -0
- package/dist/themes/builtin.js +291 -0
- package/dist/themes/glyphs.d.ts +15 -0
- package/dist/themes/glyphs.d.ts.map +1 -0
- package/dist/themes/glyphs.js +97 -0
- package/dist/themes/index.d.ts +5 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +4 -0
- package/dist/themes/registry.d.ts +20 -0
- package/dist/themes/registry.d.ts.map +1 -0
- package/dist/themes/registry.js +204 -0
- package/dist/types/adapter.d.ts +38 -0
- package/dist/types/adapter.d.ts.map +1 -0
- package/dist/types/adapter.js +1 -0
- package/dist/types/animation.d.ts +31 -0
- package/dist/types/animation.d.ts.map +1 -0
- package/dist/types/animation.js +1 -0
- package/dist/types/app.d.ts +148 -0
- package/dist/types/app.d.ts.map +1 -0
- package/dist/types/app.js +1 -0
- package/dist/types/async.d.ts +30 -0
- package/dist/types/async.d.ts.map +1 -0
- package/dist/types/async.js +1 -0
- package/dist/types/capabilities.d.ts +44 -0
- package/dist/types/capabilities.d.ts.map +1 -0
- package/dist/types/capabilities.js +33 -0
- package/dist/types/cells.d.ts +72 -0
- package/dist/types/cells.d.ts.map +1 -0
- package/dist/types/cells.js +10 -0
- package/dist/types/command.d.ts +127 -0
- package/dist/types/command.d.ts.map +1 -0
- package/dist/types/command.js +1 -0
- package/dist/types/component-registry.d.ts +77 -0
- package/dist/types/component-registry.d.ts.map +1 -0
- package/dist/types/component-registry.js +1 -0
- package/dist/types/disposable.d.ts +8 -0
- package/dist/types/disposable.d.ts.map +1 -0
- package/dist/types/disposable.js +1 -0
- package/dist/types/focus.d.ts +72 -0
- package/dist/types/focus.d.ts.map +1 -0
- package/dist/types/focus.js +1 -0
- package/dist/types/geometry.d.ts +28 -0
- package/dist/types/geometry.d.ts.map +1 -0
- package/dist/types/geometry.js +14 -0
- package/dist/types/graph.d.ts +129 -0
- package/dist/types/graph.d.ts.map +1 -0
- package/dist/types/graph.js +31 -0
- package/dist/types/i18n.d.ts +23 -0
- package/dist/types/i18n.d.ts.map +1 -0
- package/dist/types/i18n.js +1 -0
- package/dist/types/index.d.ts +32 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +31 -0
- package/dist/types/input.d.ts +65 -0
- package/dist/types/input.d.ts.map +1 -0
- package/dist/types/input.js +1 -0
- package/dist/types/keybinding.d.ts +49 -0
- package/dist/types/keybinding.d.ts.map +1 -0
- package/dist/types/keybinding.js +1 -0
- package/dist/types/layer.d.ts +54 -0
- package/dist/types/layer.d.ts.map +1 -0
- package/dist/types/layer.js +1 -0
- package/dist/types/manifest.d.ts +114 -0
- package/dist/types/manifest.d.ts.map +1 -0
- package/dist/types/manifest.js +1 -0
- package/dist/types/markdown.d.ts +58 -0
- package/dist/types/markdown.d.ts.map +1 -0
- package/dist/types/markdown.js +1 -0
- package/dist/types/navigation.d.ts +40 -0
- package/dist/types/navigation.d.ts.map +1 -0
- package/dist/types/navigation.js +1 -0
- package/dist/types/render.d.ts +72 -0
- package/dist/types/render.d.ts.map +1 -0
- package/dist/types/render.js +1 -0
- package/dist/types/resource.d.ts +170 -0
- package/dist/types/resource.d.ts.map +1 -0
- package/dist/types/resource.js +1 -0
- package/dist/types/services.d.ts +24 -0
- package/dist/types/services.d.ts.map +1 -0
- package/dist/types/services.js +3 -0
- package/dist/types/shell.d.ts +32 -0
- package/dist/types/shell.d.ts.map +1 -0
- package/dist/types/shell.js +1 -0
- package/dist/types/store.d.ts +124 -0
- package/dist/types/store.d.ts.map +1 -0
- package/dist/types/store.js +1 -0
- package/dist/types/stream.d.ts +18 -0
- package/dist/types/stream.d.ts.map +1 -0
- package/dist/types/stream.js +1 -0
- package/dist/types/style.d.ts +166 -0
- package/dist/types/style.d.ts.map +1 -0
- package/dist/types/style.js +1 -0
- package/dist/types/surface.d.ts +97 -0
- package/dist/types/surface.d.ts.map +1 -0
- package/dist/types/surface.js +1 -0
- package/dist/types/syntax.d.ts +68 -0
- package/dist/types/syntax.d.ts.map +1 -0
- package/dist/types/syntax.js +5 -0
- package/dist/types/terminal.d.ts +52 -0
- package/dist/types/terminal.d.ts.map +1 -0
- package/dist/types/terminal.js +1 -0
- package/dist/types/theme.d.ts +117 -0
- package/dist/types/theme.d.ts.map +1 -0
- package/dist/types/theme.js +1 -0
- package/dist/types/when.d.ts +19 -0
- package/dist/types/when.d.ts.map +1 -0
- package/dist/types/when.js +1 -0
- package/dist/ui/primitives.d.ts +56 -0
- package/dist/ui/primitives.d.ts.map +1 -0
- package/dist/ui/primitives.js +108 -0
- package/dist/ui/screen.d.ts +23 -0
- package/dist/ui/screen.d.ts.map +1 -0
- package/dist/ui/screen.js +18 -0
- package/dist/util/disposable.d.ts +6 -0
- package/dist/util/disposable.d.ts.map +1 -0
- package/dist/util/disposable.js +49 -0
- package/dist/util/markdown.d.ts +24 -0
- package/dist/util/markdown.d.ts.map +1 -0
- package/dist/util/markdown.js +220 -0
- package/dist/util/paths.d.ts +47 -0
- package/dist/util/paths.d.ts.map +1 -0
- package/dist/util/paths.js +134 -0
- package/dist/util/stream.d.ts +32 -0
- package/dist/util/stream.d.ts.map +1 -0
- package/dist/util/stream.js +206 -0
- package/dist/util/text.d.ts +65 -0
- package/dist/util/text.d.ts.map +1 -0
- package/dist/util/text.js +419 -0
- package/package.json +70 -0
- package/src/adapters/index.ts +11 -0
- package/src/app/app.ts +1096 -0
- package/src/core/animation.ts +144 -0
- package/src/core/clipboard.ts +40 -0
- package/src/core/commands.ts +169 -0
- package/src/core/components.ts +91 -0
- package/src/core/events.ts +95 -0
- package/src/core/focus.ts +358 -0
- package/src/core/i18n.ts +107 -0
- package/src/core/keybindings.ts +184 -0
- package/src/core/layers.ts +94 -0
- package/src/core/manifest.ts +84 -0
- package/src/core/navigation.ts +135 -0
- package/src/core/resources.ts +362 -0
- package/src/core/services.ts +69 -0
- package/src/core/store.ts +640 -0
- package/src/core/surfaces.ts +292 -0
- package/src/core/syntax.ts +200 -0
- package/src/core/when.ts +238 -0
- package/src/index.ts +76 -0
- package/src/jsx/factory.ts +124 -0
- package/src/jsx/intrinsics.ts +99 -0
- package/src/jsx/jsx-dev-runtime.ts +23 -0
- package/src/jsx/jsx-runtime.ts +76 -0
- package/src/render/buffer.ts +318 -0
- package/src/render/color.ts +180 -0
- package/src/render/diff.ts +99 -0
- package/src/render/layout.ts +764 -0
- package/src/render/static.ts +290 -0
- package/src/runtime/bindings.ts +150 -0
- package/src/runtime/hooks.ts +1057 -0
- package/src/runtime/instance.ts +284 -0
- package/src/runtime/paint.ts +737 -0
- package/src/runtime/reconcile.ts +335 -0
- package/src/runtime/runtime.ts +47 -0
- package/src/runtime/style.ts +205 -0
- package/src/themes/borders.ts +95 -0
- package/src/themes/builtin.ts +301 -0
- package/src/themes/glyphs.ts +101 -0
- package/src/themes/index.ts +4 -0
- package/src/themes/registry.ts +224 -0
- package/src/types/adapter.ts +41 -0
- package/src/types/animation.ts +34 -0
- package/src/types/app.ts +149 -0
- package/src/types/async.ts +35 -0
- package/src/types/capabilities.ts +79 -0
- package/src/types/cells.ts +79 -0
- package/src/types/command.ts +139 -0
- package/src/types/component-registry.ts +82 -0
- package/src/types/disposable.ts +8 -0
- package/src/types/focus.ts +77 -0
- package/src/types/geometry.ts +50 -0
- package/src/types/graph.ts +174 -0
- package/src/types/i18n.ts +25 -0
- package/src/types/index.ts +31 -0
- package/src/types/input.ts +81 -0
- package/src/types/keybinding.ts +51 -0
- package/src/types/layer.ts +46 -0
- package/src/types/manifest.ts +101 -0
- package/src/types/markdown.ts +47 -0
- package/src/types/navigation.ts +42 -0
- package/src/types/render.ts +86 -0
- package/src/types/resource.ts +185 -0
- package/src/types/services.ts +28 -0
- package/src/types/shell.ts +30 -0
- package/src/types/store.ts +148 -0
- package/src/types/stream.ts +24 -0
- package/src/types/style.ts +205 -0
- package/src/types/surface.ts +119 -0
- package/src/types/syntax.ts +93 -0
- package/src/types/terminal.ts +58 -0
- package/src/types/theme.ts +121 -0
- package/src/types/when.ts +21 -0
- package/src/ui/primitives.ts +118 -0
- package/src/ui/screen.ts +41 -0
- package/src/util/disposable.ts +49 -0
- package/src/util/markdown.ts +225 -0
- package/src/util/paths.ts +138 -0
- package/src/util/stream.ts +213 -0
- package/src/util/text.ts +428 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Disposable } from './disposable.js';
|
|
2
|
+
|
|
3
|
+
export type Easing = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | ((t: number) => number);
|
|
4
|
+
|
|
5
|
+
export interface TweenOptions {
|
|
6
|
+
from: number;
|
|
7
|
+
to: number;
|
|
8
|
+
durationMs: number;
|
|
9
|
+
easing?: Easing;
|
|
10
|
+
onUpdate(value: number): void;
|
|
11
|
+
onComplete?(): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface TickerOptions {
|
|
15
|
+
/** Frames per second. Clamped by the driver's global cap. */
|
|
16
|
+
fps?: number;
|
|
17
|
+
onTick(frame: number, elapsedMs: number): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* One driver for every moving thing, so a global disable is one flag and a
|
|
22
|
+
* slow ssh link can drop the frame rate for all of it at once.
|
|
23
|
+
*/
|
|
24
|
+
export interface AnimationDriver extends Disposable {
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
/** Global ceiling; individual tickers may ask for less. */
|
|
27
|
+
maxFps: number;
|
|
28
|
+
ticker(options: TickerOptions): Disposable;
|
|
29
|
+
tween(options: TweenOptions): Disposable;
|
|
30
|
+
/** Advance manually. The testing harness drives time with this. */
|
|
31
|
+
advance(ms: number): void;
|
|
32
|
+
/** True when animations are off - components render their final state. */
|
|
33
|
+
readonly disabled: boolean;
|
|
34
|
+
}
|
package/src/types/app.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { Disposable } from './disposable.js';
|
|
2
|
+
import type { Size } from './geometry.js';
|
|
3
|
+
import type { ReactiveStore, EventBus, ScopeName } from './store.js';
|
|
4
|
+
import type { ComponentRegistry } from './component-registry.js';
|
|
5
|
+
import type { CommandRegistry } from './command.js';
|
|
6
|
+
import type { KeybindingRegistry } from './keybinding.js';
|
|
7
|
+
import type { ThemeRegistry, ResolvedTheme, ThemeDefinition } from './theme.js';
|
|
8
|
+
import type { ShellRegistry } from './shell.js';
|
|
9
|
+
import type { LayoutRegistry, SurfaceRegistry, SurfaceName } from './surface.js';
|
|
10
|
+
import type { ResourceRegistry } from './resource.js';
|
|
11
|
+
import type { SyntaxRegistry } from './syntax.js';
|
|
12
|
+
import type { ResourceAdapter } from './adapter.js';
|
|
13
|
+
import type { Navigator } from './navigation.js';
|
|
14
|
+
import type { LayerManager } from './layer.js';
|
|
15
|
+
import type { FocusManager } from './focus.js';
|
|
16
|
+
import type { WhenEngine } from './when.js';
|
|
17
|
+
import type { I18n } from './i18n.js';
|
|
18
|
+
import type { ServiceContainer } from './services.js';
|
|
19
|
+
import type { AnimationDriver } from './animation.js';
|
|
20
|
+
import type { ManifestAPI } from './manifest.js';
|
|
21
|
+
import type { TerminalAdapter, TerminalSessionOptions } from './terminal.js';
|
|
22
|
+
import type { TerminalCapabilities, CapabilityOverrides } from './capabilities.js';
|
|
23
|
+
import type { ComponentNode } from './graph.js';
|
|
24
|
+
import type { CellBuffer } from './cells.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The application. Every registry hangs off it, and every registry is late
|
|
28
|
+
* binding, which is what lets a screen be data and a shell be swappable at
|
|
29
|
+
* runtime.
|
|
30
|
+
*/
|
|
31
|
+
export interface TextUIApp extends Disposable {
|
|
32
|
+
readonly components: ComponentRegistry;
|
|
33
|
+
readonly commands: CommandRegistry;
|
|
34
|
+
readonly keybindings: KeybindingRegistry;
|
|
35
|
+
readonly themes: ThemeRegistry;
|
|
36
|
+
readonly shells: ShellRegistry;
|
|
37
|
+
readonly layouts: LayoutRegistry;
|
|
38
|
+
readonly surfaces: SurfaceRegistry;
|
|
39
|
+
readonly resources: ResourceRegistry;
|
|
40
|
+
readonly syntax: SyntaxRegistry;
|
|
41
|
+
readonly screens: Navigator;
|
|
42
|
+
readonly layers: LayerManager;
|
|
43
|
+
readonly focus: FocusManager;
|
|
44
|
+
readonly store: ReactiveStore;
|
|
45
|
+
readonly events: EventBus;
|
|
46
|
+
readonly when: WhenEngine;
|
|
47
|
+
readonly i18n: I18n;
|
|
48
|
+
readonly services: ServiceContainer;
|
|
49
|
+
readonly animation: AnimationDriver;
|
|
50
|
+
readonly manifest: ManifestAPI;
|
|
51
|
+
readonly terminal: TerminalAdapter;
|
|
52
|
+
|
|
53
|
+
readonly capabilities: TerminalCapabilities;
|
|
54
|
+
readonly theme: ResolvedTheme;
|
|
55
|
+
readonly size: Size;
|
|
56
|
+
readonly running: boolean;
|
|
57
|
+
|
|
58
|
+
setTheme(id: string): void;
|
|
59
|
+
setShell(id: string): void;
|
|
60
|
+
activeShell(): string;
|
|
61
|
+
setCapabilityOverrides(overrides: CapabilityOverrides): void;
|
|
62
|
+
|
|
63
|
+
/** Acquire the terminal and start the render loop. */
|
|
64
|
+
/** Swap the root node for another one, and redraw. */
|
|
65
|
+
setRoot(node: ComponentNode): void;
|
|
66
|
+
|
|
67
|
+
start(): Promise<void>;
|
|
68
|
+
/** Release exactly what was acquired, then stop. */
|
|
69
|
+
stop(): Promise<void>;
|
|
70
|
+
/** Force a frame now, outside the scheduler. Tests and screenshots use it. */
|
|
71
|
+
flush(): void;
|
|
72
|
+
/** The last painted frame. */
|
|
73
|
+
buffer(): CellBuffer;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Register a resource type whole: kinds, provider, viewers, highlighter,
|
|
77
|
+
* actions, commands. Disposing removes exactly what was added.
|
|
78
|
+
*/
|
|
79
|
+
registerAdapter(adapter: ResourceAdapter): Disposable;
|
|
80
|
+
|
|
81
|
+
open: SurfaceRegistry['open'];
|
|
82
|
+
openResource: SurfaceRegistry['openResource'];
|
|
83
|
+
execute: CommandRegistry['execute'];
|
|
84
|
+
|
|
85
|
+
/** Every mounted node's tree, for the inspector and the test harness. */
|
|
86
|
+
inspect(): InspectorNode | null;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Frame statistics. `runs` is how many terminal writes the last frame cost,
|
|
90
|
+
* which is the number that tells you whether the diff is doing its job.
|
|
91
|
+
*/
|
|
92
|
+
stats(): { renders: number; runs: number; instances: number };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface InspectorNode {
|
|
96
|
+
id: string;
|
|
97
|
+
component: string;
|
|
98
|
+
key?: string | number;
|
|
99
|
+
rect?: { x: number; y: number; width: number; height: number };
|
|
100
|
+
props: Record<string, unknown>;
|
|
101
|
+
role?: string;
|
|
102
|
+
label?: string;
|
|
103
|
+
text?: string;
|
|
104
|
+
focusable?: boolean;
|
|
105
|
+
focused?: boolean;
|
|
106
|
+
/** Why the last render happened, when diagnostics are on. */
|
|
107
|
+
renderReason?: string;
|
|
108
|
+
bindings?: string[];
|
|
109
|
+
children: InspectorNode[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface CreateAppOptions {
|
|
113
|
+
terminal?: TerminalAdapter;
|
|
114
|
+
session?: TerminalSessionOptions;
|
|
115
|
+
theme?: string;
|
|
116
|
+
themes?: ThemeDefinition[];
|
|
117
|
+
shell?: string;
|
|
118
|
+
/**
|
|
119
|
+
* The root node, when the app is not using screens.
|
|
120
|
+
*
|
|
121
|
+
* It is mounted into the `main` surface at boot, so the shell still frames
|
|
122
|
+
* it: the canvas is painted, the status surface and toast host exist, and
|
|
123
|
+
* `setShell` means something. It is an alternative to screens, not to the
|
|
124
|
+
* shell.
|
|
125
|
+
*/
|
|
126
|
+
root?: ComponentNode;
|
|
127
|
+
capabilityOverrides?: CapabilityOverrides;
|
|
128
|
+
locale?: string;
|
|
129
|
+
/**
|
|
130
|
+
* Register everything the app needs. Runs once at boot, before the first
|
|
131
|
+
* render.
|
|
132
|
+
*
|
|
133
|
+
* A `Disposable` may be returned and the app disposes it on `stop()`, which
|
|
134
|
+
* is what makes `onBoot: registerBuiltins` the whole call rather than
|
|
135
|
+
* `onBoot: (app) => void registerBuiltins(app)`. Everything that registers
|
|
136
|
+
* hands back a disposable already; requiring `void` meant the shortest
|
|
137
|
+
* correct form was a cast of one away.
|
|
138
|
+
*/
|
|
139
|
+
onBoot?(app: TextUIApp): void | Disposable | Promise<void | Disposable>;
|
|
140
|
+
/** Off disables every animation globally. */
|
|
141
|
+
animations?: boolean;
|
|
142
|
+
maxFps?: number;
|
|
143
|
+
/** Scopes cleared when `stop()` runs. */
|
|
144
|
+
clearOnStop?: ScopeName[];
|
|
145
|
+
/** Surfaces this app uses; others never mount. */
|
|
146
|
+
surfaces?: SurfaceName[];
|
|
147
|
+
/** Render diagnostics into `$/modus/diagnostics/*`. */
|
|
148
|
+
diagnostics?: boolean;
|
|
149
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Disposable } from './disposable.js';
|
|
2
|
+
|
|
3
|
+
export type TaskStatus = 'idle' | 'running' | 'success' | 'error' | 'cancelled';
|
|
4
|
+
|
|
5
|
+
export interface TaskState<T = unknown> {
|
|
6
|
+
status: TaskStatus;
|
|
7
|
+
data?: T;
|
|
8
|
+
error?: unknown;
|
|
9
|
+
/** 0..1 when the task reports it, undefined when indeterminate. */
|
|
10
|
+
progress?: number;
|
|
11
|
+
/** Free-text step description, for a progress line. */
|
|
12
|
+
step?: string;
|
|
13
|
+
startedAt?: number;
|
|
14
|
+
finishedAt?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface TaskController<T = unknown> {
|
|
18
|
+
readonly state: TaskState<T>;
|
|
19
|
+
run(...args: unknown[]): Promise<T | undefined>;
|
|
20
|
+
cancel(): void;
|
|
21
|
+
reset(): void;
|
|
22
|
+
subscribe(fn: (state: TaskState<T>) => void): Disposable;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TaskRunContext {
|
|
26
|
+
signal: AbortSignal;
|
|
27
|
+
progress(value: number, step?: string): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type TaskFn<T> = (ctx: TaskRunContext, ...args: any[]) => Promise<T> | T;
|
|
31
|
+
|
|
32
|
+
export interface ResourceState<T = unknown> extends TaskState<T> {
|
|
33
|
+
/** Refetching while previous data is still shown. */
|
|
34
|
+
refreshing: boolean;
|
|
35
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What this terminal can actually do. Detected where possible, overridable by
|
|
3
|
+
* the adapter, and readable from the store at `$/modus/capabilities/*` so a
|
|
4
|
+
* `when` clause and a layout can both branch on it.
|
|
5
|
+
*
|
|
6
|
+
* Components degrade against this; they never feature-detect on their own.
|
|
7
|
+
*/
|
|
8
|
+
export type ColorDepth = 0 | 4 | 8 | 24;
|
|
9
|
+
|
|
10
|
+
/** How much of the box-drawing / symbol repertoire is safe to emit. */
|
|
11
|
+
export type UnicodeLevel = 'ascii' | 'bmp' | 'full';
|
|
12
|
+
|
|
13
|
+
export interface TerminalCapabilities {
|
|
14
|
+
/** 0 = monochrome, 4 = 16 colors, 8 = 256, 24 = truecolor. */
|
|
15
|
+
colorDepth: ColorDepth;
|
|
16
|
+
unicode: UnicodeLevel;
|
|
17
|
+
/** True when the terminal reports (or is assumed to have) emoji/wide support. */
|
|
18
|
+
wideChars: boolean;
|
|
19
|
+
mouse: boolean;
|
|
20
|
+
wheel: boolean;
|
|
21
|
+
/** Terminal reports focus in/out (CSI ?1004h). */
|
|
22
|
+
focusEvents: boolean;
|
|
23
|
+
/** Bracketed paste (CSI ?2004h). */
|
|
24
|
+
paste: boolean;
|
|
25
|
+
/** OSC 8 hyperlinks. */
|
|
26
|
+
hyperlinks: boolean;
|
|
27
|
+
/** OSC 52 clipboard write. */
|
|
28
|
+
clipboard: boolean;
|
|
29
|
+
/** Alternate screen buffer (CSI ?1049h). */
|
|
30
|
+
altScreen: boolean;
|
|
31
|
+
/** Cursor show/hide/position. */
|
|
32
|
+
cursor: boolean;
|
|
33
|
+
/** Synchronized output (DEC 2026) - tear-free frames. */
|
|
34
|
+
synchronizedOutput: boolean;
|
|
35
|
+
/** Terminal supports setting the window/tab title (OSC 0/2). */
|
|
36
|
+
title: boolean;
|
|
37
|
+
/** Keyboard disambiguation (Kitty keyboard protocol). */
|
|
38
|
+
kittyKeyboard: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type CapabilityName = keyof TerminalCapabilities;
|
|
42
|
+
|
|
43
|
+
/** Partial override applied on top of detection. */
|
|
44
|
+
export type CapabilityOverrides = Partial<TerminalCapabilities>;
|
|
45
|
+
|
|
46
|
+
/** The most conservative terminal we still render correctly on. */
|
|
47
|
+
export const MINIMAL_CAPABILITIES: TerminalCapabilities = {
|
|
48
|
+
colorDepth: 0,
|
|
49
|
+
unicode: 'ascii',
|
|
50
|
+
wideChars: false,
|
|
51
|
+
mouse: false,
|
|
52
|
+
wheel: false,
|
|
53
|
+
focusEvents: false,
|
|
54
|
+
paste: false,
|
|
55
|
+
hyperlinks: false,
|
|
56
|
+
clipboard: false,
|
|
57
|
+
altScreen: false,
|
|
58
|
+
cursor: true,
|
|
59
|
+
synchronizedOutput: false,
|
|
60
|
+
title: false,
|
|
61
|
+
kittyKeyboard: false,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const FULL_CAPABILITIES: TerminalCapabilities = {
|
|
65
|
+
colorDepth: 24,
|
|
66
|
+
unicode: 'full',
|
|
67
|
+
wideChars: true,
|
|
68
|
+
mouse: true,
|
|
69
|
+
wheel: true,
|
|
70
|
+
focusEvents: true,
|
|
71
|
+
paste: true,
|
|
72
|
+
hyperlinks: true,
|
|
73
|
+
clipboard: true,
|
|
74
|
+
altScreen: true,
|
|
75
|
+
cursor: true,
|
|
76
|
+
synchronizedOutput: true,
|
|
77
|
+
title: true,
|
|
78
|
+
kittyKeyboard: true,
|
|
79
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Rect } from './geometry.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A color is either a token name resolved through the theme, a 24-bit RGB
|
|
5
|
+
* value, a 256-palette index, or one of the 16 ANSI names. The renderer
|
|
6
|
+
* downsamples to whatever `colorDepth` allows - a component never picks a
|
|
7
|
+
* fallback itself.
|
|
8
|
+
*/
|
|
9
|
+
export type AnsiColorName =
|
|
10
|
+
| 'black' | 'red' | 'green' | 'yellow' | 'blue' | 'magenta' | 'cyan' | 'white'
|
|
11
|
+
| 'brightBlack' | 'brightRed' | 'brightGreen' | 'brightYellow'
|
|
12
|
+
| 'brightBlue' | 'brightMagenta' | 'brightCyan' | 'brightWhite';
|
|
13
|
+
|
|
14
|
+
export type Color =
|
|
15
|
+
| AnsiColorName
|
|
16
|
+
| `#${string}`
|
|
17
|
+
| { rgb: [number, number, number] }
|
|
18
|
+
| { palette: number }
|
|
19
|
+
| 'default';
|
|
20
|
+
|
|
21
|
+
/** Text attributes as a bitfield - one number per cell, cheap to diff. */
|
|
22
|
+
export const ATTR_NONE = 0;
|
|
23
|
+
export const ATTR_BOLD = 1 << 0;
|
|
24
|
+
export const ATTR_DIM = 1 << 1;
|
|
25
|
+
export const ATTR_ITALIC = 1 << 2;
|
|
26
|
+
export const ATTR_UNDERLINE = 1 << 3;
|
|
27
|
+
export const ATTR_BLINK = 1 << 4;
|
|
28
|
+
export const ATTR_INVERSE = 1 << 5;
|
|
29
|
+
export const ATTR_HIDDEN = 1 << 6;
|
|
30
|
+
export const ATTR_STRIKE = 1 << 7;
|
|
31
|
+
|
|
32
|
+
export type Attrs = number;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* One terminal cell. `char` is a full grapheme cluster (may be several code
|
|
36
|
+
* points). A wide grapheme occupies this cell and marks the next one
|
|
37
|
+
* `continuation`, which the writer skips.
|
|
38
|
+
*/
|
|
39
|
+
export interface Cell {
|
|
40
|
+
char: string;
|
|
41
|
+
fg: Color;
|
|
42
|
+
bg: Color;
|
|
43
|
+
attrs: Attrs;
|
|
44
|
+
/** OSC 8 target, when the terminal supports hyperlinks. */
|
|
45
|
+
link?: string;
|
|
46
|
+
/** True for the right half of a double-width grapheme. */
|
|
47
|
+
continuation?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface CellBuffer {
|
|
51
|
+
readonly width: number;
|
|
52
|
+
readonly height: number;
|
|
53
|
+
get(x: number, y: number): Cell | undefined;
|
|
54
|
+
set(x: number, y: number, cell: Cell): void;
|
|
55
|
+
/** Rows that changed since `commit()`. Empty when nothing moved. */
|
|
56
|
+
dirtyRows(): number[];
|
|
57
|
+
commit(): void;
|
|
58
|
+
clear(rect?: Rect): void;
|
|
59
|
+
resize(width: number, height: number): void;
|
|
60
|
+
/** Plain text, no attributes. The testing harness reads this. */
|
|
61
|
+
toText(rect?: Rect): string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** One contiguous run of identical-style cells - what the ANSI writer emits. */
|
|
65
|
+
export interface CellRun {
|
|
66
|
+
x: number;
|
|
67
|
+
y: number;
|
|
68
|
+
text: string;
|
|
69
|
+
fg: Color;
|
|
70
|
+
bg: Color;
|
|
71
|
+
attrs: Attrs;
|
|
72
|
+
link?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface FrameDiff {
|
|
76
|
+
runs: CellRun[];
|
|
77
|
+
/** Where the hardware cursor should end up, or null to keep it hidden. */
|
|
78
|
+
cursor: { x: number; y: number; visible: boolean } | null;
|
|
79
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { Disposable } from './disposable.js';
|
|
2
|
+
import type { WhenClause } from './when.js';
|
|
3
|
+
import type { ReactiveStore } from './store.js';
|
|
4
|
+
import type { TextUIApp } from './app.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Command scope. A command resolves from the most specific scope outward:
|
|
8
|
+
* component -> region -> screen -> app
|
|
9
|
+
* The same id may be registered at several scopes; the innermost active one
|
|
10
|
+
* wins, which is how `table.search` means the focused table.
|
|
11
|
+
*/
|
|
12
|
+
export type CommandScope = 'app' | 'screen' | 'region' | 'component';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* One thing an argument may be answered with.
|
|
16
|
+
*
|
|
17
|
+
* A bare string is the short form and stays the common one - the value is the
|
|
18
|
+
* label and there is nothing else to say. The long form is for a choice that
|
|
19
|
+
* has to be *explained*: an agent's approval modes are five words that all
|
|
20
|
+
* sound alike ("Auto Mode", "Plan Mode") and the sentence under each one is
|
|
21
|
+
* what tells them apart, which is the difference between picking and guessing.
|
|
22
|
+
*
|
|
23
|
+
* The command is handed `value`, never the label. A host's ids are opaque and
|
|
24
|
+
* its labels are prose, and resolving one back to the other at the far end is
|
|
25
|
+
* a lookup that can be wrong.
|
|
26
|
+
*/
|
|
27
|
+
export interface ArgChoice {
|
|
28
|
+
value: string;
|
|
29
|
+
/** What a person reads. The value, when there is nothing better. */
|
|
30
|
+
label?: string;
|
|
31
|
+
icon?: string;
|
|
32
|
+
/** A line under it: what choosing this would mean. */
|
|
33
|
+
description?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type ArgChoices = (string | ArgChoice)[];
|
|
37
|
+
|
|
38
|
+
export interface ArgSpec {
|
|
39
|
+
name: string;
|
|
40
|
+
type: 'string' | 'number' | 'boolean' | 'unknown';
|
|
41
|
+
required?: boolean;
|
|
42
|
+
description?: string;
|
|
43
|
+
/** Fixed choices, or a resolver for a picker. */
|
|
44
|
+
choices?: ArgChoices | (() => Promise<ArgChoices> | ArgChoices);
|
|
45
|
+
default?: unknown;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Show what a choice would do, before it is chosen.
|
|
49
|
+
*
|
|
50
|
+
* Called as the highlight moves, and with `null` when the asking is
|
|
51
|
+
* abandoned - so a theme can be applied while you look at it and put back if
|
|
52
|
+
* you press escape. The command owns whatever it needs to remember to undo
|
|
53
|
+
* itself; the palette only reports what is happening.
|
|
54
|
+
*/
|
|
55
|
+
preview?(value: string | null): void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type ArgsSchema = ArgSpec[];
|
|
59
|
+
|
|
60
|
+
export interface CommandContext {
|
|
61
|
+
app: TextUIApp;
|
|
62
|
+
store: ReactiveStore;
|
|
63
|
+
/** The scope instance the command resolved from (a region id, a node id). */
|
|
64
|
+
scopeId: string | null;
|
|
65
|
+
/** How it was invoked, for commands that care. */
|
|
66
|
+
source: 'keybinding' | 'palette' | 'menu' | 'api' | 'mouse' | 'cli';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type CommandHandler = (
|
|
70
|
+
args: Record<string, unknown>,
|
|
71
|
+
ctx: CommandContext,
|
|
72
|
+
) => unknown | Promise<unknown>;
|
|
73
|
+
|
|
74
|
+
export interface CommandDefinition {
|
|
75
|
+
id: string;
|
|
76
|
+
title: string;
|
|
77
|
+
description?: string;
|
|
78
|
+
category?: string;
|
|
79
|
+
icon?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Leave the surface that ran this open.
|
|
82
|
+
*
|
|
83
|
+
* For a command whose whole effect is to flip something: closing the palette
|
|
84
|
+
* after each one means reopening it to reach the next, and a list of
|
|
85
|
+
* switches is meant to be walked.
|
|
86
|
+
*/
|
|
87
|
+
keepOpen?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* A short state word shown beside the row, in place of the category.
|
|
90
|
+
*
|
|
91
|
+
* The icon is the row's identity and should not move under the reader as
|
|
92
|
+
* state changes; this is where the state goes instead.
|
|
93
|
+
*/
|
|
94
|
+
badge?: string;
|
|
95
|
+
/**
|
|
96
|
+
* A key hint, when it is not this command's own keybinding.
|
|
97
|
+
*
|
|
98
|
+
* A row built for one list may stand for a command registered under another
|
|
99
|
+
* id; the binding a person would actually press belongs to that one.
|
|
100
|
+
*/
|
|
101
|
+
shortcut?: string;
|
|
102
|
+
keywords?: string[];
|
|
103
|
+
scope?: CommandScope;
|
|
104
|
+
/** The instance this registration belongs to, for non-app scopes. */
|
|
105
|
+
scopeId?: string;
|
|
106
|
+
when?: WhenClause;
|
|
107
|
+
/**
|
|
108
|
+
* The state of a command that is a switch, as a clause over the store.
|
|
109
|
+
*
|
|
110
|
+
* A toggle's row has to say what it is toggling *to*, and the definition is
|
|
111
|
+
* registered once while the state changes under it - so this is a clause
|
|
112
|
+
* evaluated per read, the same way `when` is, rather than a boolean nobody
|
|
113
|
+
* would remember to update. Absent means the command is not a switch, which
|
|
114
|
+
* a menu needs to tell apart from a switch that is off.
|
|
115
|
+
*/
|
|
116
|
+
checked?: WhenClause;
|
|
117
|
+
args?: ArgsSchema;
|
|
118
|
+
/** Slots this command publishes itself into: 'palette', 'menu:tab', ... */
|
|
119
|
+
slots?: string[];
|
|
120
|
+
run: CommandHandler;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface CommandRegistry {
|
|
124
|
+
register(def: CommandDefinition): Disposable;
|
|
125
|
+
unregister(id: string, scopeId?: string): void;
|
|
126
|
+
get(id: string): CommandDefinition | undefined;
|
|
127
|
+
list(options?: { scope?: CommandScope; slot?: string; enabledOnly?: boolean }): CommandDefinition[];
|
|
128
|
+
/** Resolve through the active scope chain, innermost first. */
|
|
129
|
+
resolve(id: string): CommandDefinition | undefined;
|
|
130
|
+
execute(
|
|
131
|
+
id: string,
|
|
132
|
+
args?: Record<string, unknown>,
|
|
133
|
+
source?: CommandContext['source'],
|
|
134
|
+
): Promise<unknown>;
|
|
135
|
+
/** True when the command exists and its `when` clause passes. */
|
|
136
|
+
enabled(id: string): boolean;
|
|
137
|
+
/** A switch's state, or undefined when the command is not a switch. */
|
|
138
|
+
isChecked(id: string): boolean | undefined;
|
|
139
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Disposable } from './disposable.js';
|
|
2
|
+
import type { ComponentNode, ErrorFallback } from './graph.js';
|
|
3
|
+
import type { WhenClause } from './when.js';
|
|
4
|
+
import type { CapabilityName } from './capabilities.js';
|
|
5
|
+
import type { FunctionComponent, HostComponent } from './render.js';
|
|
6
|
+
import type { Style } from './style.js';
|
|
7
|
+
|
|
8
|
+
export type ComponentCategory =
|
|
9
|
+
| 'layout' | 'display' | 'control' | 'form' | 'data' | 'navigation'
|
|
10
|
+
| 'overlay' | 'feedback' | 'chart' | 'chrome' | 'resource' | 'template';
|
|
11
|
+
|
|
12
|
+
/** Late binding: a name, a category, and something that renders. */
|
|
13
|
+
export type ComponentRenderer =
|
|
14
|
+
| { kind: 'function'; render: FunctionComponent }
|
|
15
|
+
| { kind: 'host'; host: HostComponent }
|
|
16
|
+
/** A component defined as data - the graph all the way down. */
|
|
17
|
+
| { kind: 'template'; template: ComponentNode }
|
|
18
|
+
/** Loaded on first mount. The catalog costs a name until something uses it. */
|
|
19
|
+
| { kind: 'lazy'; load: () => Promise<{ default: FunctionComponent } | FunctionComponent> };
|
|
20
|
+
|
|
21
|
+
export type PropsSchema = Record<string, string | readonly string[]>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Declares that this component can render a resource kind. `findOpeners`
|
|
25
|
+
* drives "Open with...", double-click in an explorer, and the resource picker.
|
|
26
|
+
*/
|
|
27
|
+
export interface ComponentOpensSpec {
|
|
28
|
+
resourceKinds?: string[];
|
|
29
|
+
selector?: WhenClause;
|
|
30
|
+
title?: string;
|
|
31
|
+
icon?: string;
|
|
32
|
+
priority?: number;
|
|
33
|
+
/** 'view' is read-only, 'edit' can write back. */
|
|
34
|
+
mode?: 'view' | 'edit';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ComponentDefinition {
|
|
38
|
+
component: string;
|
|
39
|
+
renderer: ComponentRenderer;
|
|
40
|
+
category?: ComponentCategory;
|
|
41
|
+
description?: string;
|
|
42
|
+
propsSchema?: PropsSchema;
|
|
43
|
+
/** Variants this component understands, for the theme and the CLI. */
|
|
44
|
+
variants?: string[];
|
|
45
|
+
/** Rendered instead when this subtree throws. */
|
|
46
|
+
fallback?: ErrorFallback;
|
|
47
|
+
/** Degrades or refuses to mount without these. */
|
|
48
|
+
requires?: CapabilityName[];
|
|
49
|
+
/** Uses these when present, works without them. */
|
|
50
|
+
enhancedBy?: CapabilityName[];
|
|
51
|
+
opens?: ComponentOpensSpec;
|
|
52
|
+
/** Default style, merged under the theme's entry for this component. */
|
|
53
|
+
defaultStyle?: Style;
|
|
54
|
+
/** Semantic role, for the testing harness and future a11y integrations. */
|
|
55
|
+
role?: SemanticRole;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type SemanticRole =
|
|
59
|
+
| 'button' | 'textbox' | 'checkbox' | 'radio' | 'slider' | 'switch'
|
|
60
|
+
| 'dialog' | 'alertdialog' | 'menu' | 'menuitem' | 'list' | 'listitem'
|
|
61
|
+
| 'tree' | 'treeitem' | 'table' | 'row' | 'cell' | 'columnheader'
|
|
62
|
+
| 'tab' | 'tablist' | 'tabpanel' | 'link' | 'heading' | 'label'
|
|
63
|
+
| 'progressbar' | 'status' | 'alert' | 'region' | 'group' | 'separator'
|
|
64
|
+
| 'toolbar' | 'searchbox' | 'combobox' | 'option' | 'presentation'
|
|
65
|
+
| 'log' | 'timer' | 'marquee' | 'tooltip' | 'banner' | 'main' | 'navigation'
|
|
66
|
+
| 'document'
|
|
67
|
+
| 'complementary' | 'contentinfo' | 'form' | 'grid' | 'gridcell' | 'meter';
|
|
68
|
+
|
|
69
|
+
export interface ComponentRegistry {
|
|
70
|
+
register(def: ComponentDefinition): Disposable;
|
|
71
|
+
registerMany(defs: ComponentDefinition[]): Disposable;
|
|
72
|
+
unregister(component: string): void;
|
|
73
|
+
get(component: string): ComponentDefinition | undefined;
|
|
74
|
+
has(component: string): boolean;
|
|
75
|
+
list(category?: ComponentCategory): ComponentDefinition[];
|
|
76
|
+
/** Resolve a lazy renderer. Idempotent, cached. */
|
|
77
|
+
resolve(component: string): Promise<ComponentDefinition>;
|
|
78
|
+
/** Names registered but not yet resolved. */
|
|
79
|
+
pending(): string[];
|
|
80
|
+
/** Components whose `opens` accepts this kind, best first. */
|
|
81
|
+
findOpeners(resourceKind: string): ComponentDefinition[];
|
|
82
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Disposable } from './disposable.js';
|
|
2
|
+
import type { Rect } from './geometry.js';
|
|
3
|
+
import type { KeyEvent } from './input.js';
|
|
4
|
+
|
|
5
|
+
export type FocusDirection = 'next' | 'previous' | 'up' | 'down' | 'left' | 'right';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A focus scope is a container that focus can be trapped inside or restored
|
|
9
|
+
* to. Modals trap; a sidebar does not. Scopes nest, and the innermost active
|
|
10
|
+
* scope answers navigation first.
|
|
11
|
+
*/
|
|
12
|
+
export interface FocusScopeOptions {
|
|
13
|
+
id: string;
|
|
14
|
+
/** Tab cannot leave this scope while it is active. Modals want this. */
|
|
15
|
+
trap?: boolean;
|
|
16
|
+
/** Restore focus to whatever was focused before, on deactivate. */
|
|
17
|
+
restore?: boolean;
|
|
18
|
+
/** Focus this scope's first focusable as soon as it activates. */
|
|
19
|
+
autoFocus?: boolean;
|
|
20
|
+
/** Higher wins when two scopes are active. */
|
|
21
|
+
priority?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface FocusableOptions {
|
|
25
|
+
id: string;
|
|
26
|
+
/** Explicit order within the scope. Unset = document order. */
|
|
27
|
+
order?: number;
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
/** Skipped by tab, still reachable by directional navigation and click. */
|
|
30
|
+
skipTab?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Reads keys whether or not it is focused.
|
|
33
|
+
*
|
|
34
|
+
* For handlers that are not about one control - a screen's shortcuts, a
|
|
35
|
+
* layer that must see escape. A focusable without this only reads keys while
|
|
36
|
+
* it holds focus, which is what focus means.
|
|
37
|
+
*/
|
|
38
|
+
global?: boolean;
|
|
39
|
+
/** Current bounds, for directional navigation and mouse hit-testing. */
|
|
40
|
+
rect?: Rect;
|
|
41
|
+
scopeId?: string;
|
|
42
|
+
onFocus?(): void;
|
|
43
|
+
onBlur?(): void;
|
|
44
|
+
/** Return true to consume. Runs before the scope's and the app's handlers. */
|
|
45
|
+
onKey?(event: KeyEvent): boolean | void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface FocusManager {
|
|
49
|
+
registerScope(options: FocusScopeOptions): Disposable;
|
|
50
|
+
activateScope(id: string): void;
|
|
51
|
+
deactivateScope(id: string): void;
|
|
52
|
+
activeScope(): string | null;
|
|
53
|
+
|
|
54
|
+
register(options: FocusableOptions): Disposable;
|
|
55
|
+
update(id: string, patch: Partial<FocusableOptions>): void;
|
|
56
|
+
|
|
57
|
+
/** True when a focusable with this id is registered. */
|
|
58
|
+
has(id: string): boolean;
|
|
59
|
+
/** Which scope a focusable belongs to. */
|
|
60
|
+
scopeOf(id: string): string;
|
|
61
|
+
focus(id: string): boolean;
|
|
62
|
+
blur(): void;
|
|
63
|
+
focused(): string | null;
|
|
64
|
+
/** Move focus; returns the id that took it. */
|
|
65
|
+
move(direction: FocusDirection): string | null;
|
|
66
|
+
/** The focused id and every ancestor scope, innermost first. */
|
|
67
|
+
chain(): string[];
|
|
68
|
+
/** Ids in tab order within a scope (or the active scope). */
|
|
69
|
+
order(scopeId?: string): string[];
|
|
70
|
+
/** Hit-test a cell; used for click-to-focus. */
|
|
71
|
+
at(x: number, y: number): string | null;
|
|
72
|
+
/**
|
|
73
|
+
* Feed a key to the focused node, then outward through its scopes, then to
|
|
74
|
+
* global handlers. Returns true when something consumed it.
|
|
75
|
+
*/
|
|
76
|
+
dispatch(event: KeyEvent): boolean;
|
|
77
|
+
}
|