pi-libtui 0.3.2
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 +205 -0
- package/package.json +55 -0
- package/src/activity-presentation.ts +75 -0
- package/src/appearance.ts +632 -0
- package/src/background-surface.ts +125 -0
- package/src/color/palette.ts +186 -0
- package/src/color/preview.ts +157 -0
- package/src/color/resolver.ts +75 -0
- package/src/color/theme.ts +531 -0
- package/src/component-stack.ts +247 -0
- package/src/content/markdown-code.ts +60 -0
- package/src/content/terminal-text.ts +135 -0
- package/src/content/text.ts +105 -0
- package/src/controls/action-panel.ts +420 -0
- package/src/controls/dialog-button-bar.ts +287 -0
- package/src/controls/multi-select.ts +467 -0
- package/src/controls/picker-panel.ts +447 -0
- package/src/controls/screen-icon-actions.ts +134 -0
- package/src/controls/searchable-select.ts +394 -0
- package/src/controls/select-box.ts +224 -0
- package/src/controls/selectable-list.ts +433 -0
- package/src/controls/selection-action-bar.ts +382 -0
- package/src/controls/semantic-input.ts +23 -0
- package/src/controls/tab.ts +189 -0
- package/src/cursor.ts +288 -0
- package/src/decoration/editor-pills.ts +156 -0
- package/src/decoration/glyphs.ts +326 -0
- package/src/decoration/pointer-interaction.ts +123 -0
- package/src/decoration/powerline-pill.ts +104 -0
- package/src/decoration/status.ts +185 -0
- package/src/decoration/transient-pill.ts +176 -0
- package/src/diff/index.ts +21 -0
- package/src/diff/model.ts +76 -0
- package/src/diff/parse.ts +310 -0
- package/src/diff/pierre-syntax.ts +86 -0
- package/src/diff/render.ts +504 -0
- package/src/diff/structured.ts +264 -0
- package/src/diff/view.ts +75 -0
- package/src/editor/chrome.ts +341 -0
- package/src/editor/composition.ts +53 -0
- package/src/editor/layout.ts +81 -0
- package/src/editor/presentation.ts +35 -0
- package/src/editor/protocol.ts +188 -0
- package/src/editor.ts +41 -0
- package/src/extension.ts +30 -0
- package/src/folding.ts +192 -0
- package/src/host/cursor-bridge.ts +210 -0
- package/src/host/editor-bridge.ts +148 -0
- package/src/host/extension-host.ts +231 -0
- package/src/host/mouse-bridge.ts +212 -0
- package/src/host/mouse-dispatch.ts +400 -0
- package/src/host/mouse-input.ts +85 -0
- package/src/host/mouse-targets.ts +238 -0
- package/src/host/pi-layout-adapter.ts +298 -0
- package/src/host/pi-native-selection.ts +227 -0
- package/src/host/split-pane-bridge.ts +720 -0
- package/src/host/transcript-bridge.ts +236 -0
- package/src/index.ts +334 -0
- package/src/line-layout.ts +6 -0
- package/src/motion.ts +1409 -0
- package/src/mouse/events.ts +55 -0
- package/src/mouse/fullscreen-layout.ts +260 -0
- package/src/mouse/registry.ts +348 -0
- package/src/mouse.ts +37 -0
- package/src/native-binary.ts +189 -0
- package/src/overlay/anchored.ts +91 -0
- package/src/overlay/detail-card.ts +158 -0
- package/src/overlay/dialog.ts +266 -0
- package/src/overlay/floating.ts +106 -0
- package/src/overlay/framed-editor.ts +236 -0
- package/src/overlay/fullscreen.ts +170 -0
- package/src/overlay/hover-tooltip.ts +109 -0
- package/src/overlay/modal-mount.ts +118 -0
- package/src/panels.ts +207 -0
- package/src/render-cache.ts +80 -0
- package/src/render-epoch.ts +48 -0
- package/src/request-animation.ts +160 -0
- package/src/scrollbar.ts +27 -0
- package/src/selection.ts +148 -0
- package/src/split-pane.ts +232 -0
- package/src/status-presentation.ts +411 -0
- package/src/stream.ts +543 -0
- package/src/syntax.ts +316 -0
- package/src/terminal/bridge-client.ts +243 -0
- package/src/terminal/embedding.ts +6 -0
- package/src/terminal/output.ts +151 -0
- package/src/terminal/projection.ts +362 -0
- package/src/terminal/pty-host.ts +263 -0
- package/src/terminal/pty-pane.ts +308 -0
- package/src/terminal-colors.ts +334 -0
- package/src/tool/action.ts +170 -0
- package/src/tool/activity.ts +500 -0
- package/src/tool/call-preview.ts +44 -0
- package/src/tool/disclosure-action.ts +89 -0
- package/src/tool/expanded-region-viewport.ts +103 -0
- package/src/tool/index.ts +26 -0
- package/src/tool/output.ts +271 -0
- package/src/tool/transcript.ts +150 -0
- package/src/tool/view-region.ts +581 -0
- package/test/appearance.test.ts +183 -0
- package/test/bounded-stream.test.ts +150 -0
- package/test/bridge-client.test.ts +42 -0
- package/test/color-preview.test.ts +23 -0
- package/test/component-stack.test.ts +206 -0
- package/test/components.test.ts +1165 -0
- package/test/content-text.test.ts +21 -0
- package/test/cursor-bridge.test.ts +151 -0
- package/test/cursor.test.ts +147 -0
- package/test/editor-chrome.test.ts +119 -0
- package/test/editor-layout.test.ts +30 -0
- package/test/editor-registry.test.ts +203 -0
- package/test/editor-theme.test.ts +33 -0
- package/test/editor-token-pill.test.ts +72 -0
- package/test/floating-overlay.test.ts +57 -0
- package/test/folding.test.ts +147 -0
- package/test/harmonious-theme.test.ts +23 -0
- package/test/interaction.test.ts +104 -0
- package/test/key-icons.test.ts +62 -0
- package/test/markdown-code.test.ts +24 -0
- package/test/modal-overlay-mount.test.ts +94 -0
- package/test/motion.test.ts +649 -0
- package/test/mouse/bridge.test.ts +1302 -0
- package/test/mouse/extension.test.ts +205 -0
- package/test/mouse/registry.test.ts +140 -0
- package/test/mouse/screen-decoration.test.ts +167 -0
- package/test/native-binary.test.ts +52 -0
- package/test/palette.test.ts +32 -0
- package/test/panels.test.ts +74 -0
- package/test/pointer-components.test.ts +151 -0
- package/test/progress.test.ts +54 -0
- package/test/pty-host.test.ts +126 -0
- package/test/pty-pane.test.ts +62 -0
- package/test/public-surface.test.ts +75 -0
- package/test/render-cache.test.ts +25 -0
- package/test/render-epoch.test.ts +119 -0
- package/test/request-animation.test.ts +193 -0
- package/test/screen-icon-actions.test.ts +69 -0
- package/test/select-box.test.ts +86 -0
- package/test/select-pointer.test.ts +369 -0
- package/test/selectable-list.test.ts +258 -0
- package/test/selection/registry.test.ts +44 -0
- package/test/selection-action-bar.test.ts +204 -0
- package/test/split-pane-bridge.test.ts +944 -0
- package/test/split-pane.test.ts +134 -0
- package/test/status-presentation.test.ts +39 -0
- package/test/syntax.test.ts +59 -0
- package/test/terminal-colors.test.ts +115 -0
- package/test/terminal-embedding.test.ts +12 -0
- package/test/terminal-projection.test.ts +312 -0
- package/test/theme-colors.test.ts +175 -0
- package/test/tool-output.test.ts +160 -0
- package/test/tool-transcript.test.ts +1190 -0
- package/test/unified-diff.test.ts +691 -0
- package/themes/harmonious.json +61 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
import { bumpTuiRenderEpoch } from "./render-epoch.ts";
|
|
2
|
+
|
|
3
|
+
/** Glyph family used to resolve every semantic icon token. */
|
|
4
|
+
export type TuiIconPack = "nerd-fonts" | "unicode" | "emoji";
|
|
5
|
+
|
|
6
|
+
/** Compact glyph animation shown before an activity message. */
|
|
7
|
+
export type TuiActivityIndicatorStyle =
|
|
8
|
+
| "off"
|
|
9
|
+
| "spinner"
|
|
10
|
+
| "static"
|
|
11
|
+
| "line"
|
|
12
|
+
| "arc"
|
|
13
|
+
| "pipe"
|
|
14
|
+
| "grow-vertical"
|
|
15
|
+
| "grow-horizontal"
|
|
16
|
+
| "triangle"
|
|
17
|
+
| "circle-quarters"
|
|
18
|
+
| "circle-halves"
|
|
19
|
+
| "bracket-spin"
|
|
20
|
+
| "dots"
|
|
21
|
+
| "quadrants"
|
|
22
|
+
| "sparkle"
|
|
23
|
+
| "braille-wave"
|
|
24
|
+
| "braille-dna"
|
|
25
|
+
| "braille-scan"
|
|
26
|
+
| "braille-rain"
|
|
27
|
+
| "braille-scanline"
|
|
28
|
+
| "braille-pulse"
|
|
29
|
+
| "braille-sparkle"
|
|
30
|
+
| "braille-cascade"
|
|
31
|
+
| "braille-columns"
|
|
32
|
+
| "braille-orbit"
|
|
33
|
+
| "braille-breathe"
|
|
34
|
+
| "braille-wave-rows"
|
|
35
|
+
| "braille-checkerboard"
|
|
36
|
+
| "braille-helix"
|
|
37
|
+
| "scanline"
|
|
38
|
+
| "snake"
|
|
39
|
+
| "fill-sweep"
|
|
40
|
+
| "diagonal-swipe"
|
|
41
|
+
| "dna"
|
|
42
|
+
| "radar"
|
|
43
|
+
| "bounce"
|
|
44
|
+
| "orbit"
|
|
45
|
+
| "conveyor"
|
|
46
|
+
| "heartbeat"
|
|
47
|
+
| "nerd-progress"
|
|
48
|
+
| "nerd-morph"
|
|
49
|
+
| "nerd-pipeline"
|
|
50
|
+
| "nerd-pi-orbit";
|
|
51
|
+
|
|
52
|
+
/** Stable indicator catalog for settings hosts and feature-owned overrides. */
|
|
53
|
+
export const TUI_ACTIVITY_INDICATOR_OPTIONS = Object.freeze([
|
|
54
|
+
{ value: "off", label: "Off", description: "Do not show an activity indicator." },
|
|
55
|
+
{ value: "spinner", label: "Spinner", description: "Rotate a compact Braille spinner." },
|
|
56
|
+
{ value: "static", label: "Static", description: "Show a fixed activity dot." },
|
|
57
|
+
{ value: "line", label: "Line", description: "Rotate a compact ASCII line." },
|
|
58
|
+
{ value: "arc", label: "Arc", description: "Rotate through rounded Unicode arc positions." },
|
|
59
|
+
{ value: "pipe", label: "Pipe", description: "Rotate through box-drawing junctions." },
|
|
60
|
+
{ value: "grow-vertical", label: "Vertical grow", description: "Grow and contract a vertical block." },
|
|
61
|
+
{ value: "grow-horizontal", label: "Horizontal grow", description: "Grow and contract a horizontal block." },
|
|
62
|
+
{ value: "triangle", label: "Triangle", description: "Rotate through filled triangle corners." },
|
|
63
|
+
{ value: "circle-quarters", label: "Circle quarters", description: "Rotate a filled quarter around a circle." },
|
|
64
|
+
{ value: "circle-halves", label: "Circle halves", description: "Rotate a filled half around a circle." },
|
|
65
|
+
{ value: "bracket-spin", label: "Bracket spin", description: "Rotate an open bracket through four sides." },
|
|
66
|
+
{ value: "dots", label: "Dots", description: "Move one Braille dot around its cell." },
|
|
67
|
+
{ value: "quadrants", label: "Quadrants", description: "Move through the corners of one block cell." },
|
|
68
|
+
{ value: "sparkle", label: "Sparkle", description: "Alternate a slow Unicode sparkle." },
|
|
69
|
+
{ value: "braille-wave", label: "Braille wave", description: "Send a Braille dot wave across four cells." },
|
|
70
|
+
{ value: "braille-dna", label: "Braille DNA", description: "Twist a Braille strand across four cells." },
|
|
71
|
+
{ value: "braille-scan", label: "Braille scan", description: "Sweep a Braille bar across four cells." },
|
|
72
|
+
{ value: "braille-rain", label: "Braille rain", description: "Drift a Braille rain pattern across four cells." },
|
|
73
|
+
{ value: "braille-scanline", label: "Braille scanline", description: "Move a Braille scanline through three cells." },
|
|
74
|
+
{ value: "braille-pulse", label: "Braille pulse", description: "Expand a Braille pulse across three cells." },
|
|
75
|
+
{ value: "braille-sparkle", label: "Braille sparkle", description: "Scatter Braille sparks across four cells." },
|
|
76
|
+
{ value: "braille-cascade", label: "Braille cascade", description: "Cascade a Braille wave across four cells." },
|
|
77
|
+
{ value: "braille-columns", label: "Braille columns", description: "Fill three Braille columns in sequence." },
|
|
78
|
+
{ value: "braille-orbit", label: "Braille orbit", description: "Orbit dots inside one Braille cell." },
|
|
79
|
+
{ value: "braille-breathe", label: "Braille breathe", description: "Fill and empty one Braille cell." },
|
|
80
|
+
{ value: "braille-wave-rows", label: "Braille wave rows", description: "Roll Braille rows across four cells." },
|
|
81
|
+
{
|
|
82
|
+
value: "braille-checkerboard",
|
|
83
|
+
label: "Braille checkerboard",
|
|
84
|
+
description: "Shift a three-cell Braille checkerboard.",
|
|
85
|
+
},
|
|
86
|
+
{ value: "braille-helix", label: "Braille helix", description: "Rotate a four-cell Braille double helix." },
|
|
87
|
+
{ value: "scanline", label: "Scanline", description: "Fill and clear a three-cell Braille scanline." },
|
|
88
|
+
{ value: "snake", label: "Snake", description: "Run a two-cell Braille snake around its track." },
|
|
89
|
+
{ value: "fill-sweep", label: "Fill sweep", description: "Fill and clear two Braille cells vertically." },
|
|
90
|
+
{ value: "diagonal-swipe", label: "Diagonal swipe", description: "Sweep a diagonal fill through two Braille cells." },
|
|
91
|
+
{ value: "dna", label: "DNA", description: "Twist a compact three-cell Braille helix." },
|
|
92
|
+
{ value: "radar", label: "Radar", description: "Sweep a Braille return across three cells." },
|
|
93
|
+
{ value: "bounce", label: "Bounce", description: "Bounce a sparkle across three cells." },
|
|
94
|
+
{ value: "orbit", label: "Orbit", description: "Orbit an arc around a compact sparkle." },
|
|
95
|
+
{ value: "conveyor", label: "Conveyor", description: "Move a block along a three-cell track." },
|
|
96
|
+
{ value: "heartbeat", label: "Heartbeat", description: "Pulse a compact ASCII heartbeat." },
|
|
97
|
+
{
|
|
98
|
+
value: "nerd-progress",
|
|
99
|
+
label: "Progress spinner (Nerd Font)",
|
|
100
|
+
description: "Rotate through the Fira Code progress-spinner glyphs.",
|
|
101
|
+
},
|
|
102
|
+
{ value: "nerd-morph", label: "Icon morph (Nerd Font)", description: "Morph through developer icons." },
|
|
103
|
+
{
|
|
104
|
+
value: "nerd-pipeline",
|
|
105
|
+
label: "Pipeline (Nerd Font)",
|
|
106
|
+
description: "Move build icons through a compact pipeline.",
|
|
107
|
+
},
|
|
108
|
+
{ value: "nerd-pi-orbit", label: "Pi orbit (Nerd Font)", description: "Orbit a spark around the Pi logo." },
|
|
109
|
+
] as const satisfies readonly { value: TuiActivityIndicatorStyle; label: string; description: string }[]);
|
|
110
|
+
|
|
111
|
+
/** Message source used by the standard inline activity composition. */
|
|
112
|
+
export type TuiActivityMessageStyle = "phase" | "typewriter";
|
|
113
|
+
|
|
114
|
+
/** Status presentations that replace the standard inline composition. */
|
|
115
|
+
export type TuiStatusPresentationStyle =
|
|
116
|
+
| "standard"
|
|
117
|
+
| "neural-pulse"
|
|
118
|
+
| "plasma-wave"
|
|
119
|
+
| "pacman"
|
|
120
|
+
| "matrix"
|
|
121
|
+
| "pipeline"
|
|
122
|
+
| "starfield"
|
|
123
|
+
| "fire"
|
|
124
|
+
| "icon-morph"
|
|
125
|
+
| "brainstorm"
|
|
126
|
+
| "dev-constellation"
|
|
127
|
+
| "pi-pulse"
|
|
128
|
+
| "orbit-dots"
|
|
129
|
+
| "neon-bounce"
|
|
130
|
+
| "block-wave"
|
|
131
|
+
| "conveyor"
|
|
132
|
+
| "accordion";
|
|
133
|
+
|
|
134
|
+
/** Stable status-presentation catalog for settings hosts and phase overrides. */
|
|
135
|
+
export const TUI_STATUS_PRESENTATION_OPTIONS = Object.freeze([
|
|
136
|
+
{ value: "standard", label: "Standard", description: "Compose the configured indicator, message, and text effect." },
|
|
137
|
+
{ value: "neural-pulse", label: "Neural pulse", description: "Send an energy pulse through connected nodes." },
|
|
138
|
+
{ value: "plasma-wave", label: "Plasma wave", description: "Flow a colorful character wave across the row." },
|
|
139
|
+
{ value: "pacman", label: "Pac-Man", description: "Chase a trail of dots across the row." },
|
|
140
|
+
{ value: "matrix", label: "Matrix", description: "Stream a deterministic Matrix-style code rain." },
|
|
141
|
+
{ value: "pipeline", label: "Pipeline (Nerd Font)", description: "Move energy through a developer tool pipeline." },
|
|
142
|
+
{ value: "starfield", label: "Starfield", description: "Move layered stars across the row." },
|
|
143
|
+
{ value: "fire", label: "Fire", description: "Animate a one-line demoscene fire field." },
|
|
144
|
+
{ value: "icon-morph", label: "Icon morph (Nerd Font)", description: "Morph through developer icons and sparks." },
|
|
145
|
+
{ value: "brainstorm", label: "Brainstorm (Nerd Font)", description: "Cycle weather states through a brainstorm." },
|
|
146
|
+
{
|
|
147
|
+
value: "dev-constellation",
|
|
148
|
+
label: "Dev constellation (Nerd Font)",
|
|
149
|
+
description: "Pulse across a constellation of developer icons.",
|
|
150
|
+
},
|
|
151
|
+
{ value: "pi-pulse", label: "Pi pulse (Nerd Font)", description: "Pulse the Pi logo through an orbiting gradient." },
|
|
152
|
+
{ value: "orbit-dots", label: "Orbit dots", description: "Pulse five gradient dots beside the phase label." },
|
|
153
|
+
{ value: "neon-bounce", label: "Neon bounce", description: "Bounce a bright block with a fading trail." },
|
|
154
|
+
{ value: "block-wave", label: "Block wave", description: "Reflect a shaded block wave across its track." },
|
|
155
|
+
{ value: "conveyor", label: "Conveyor", description: "Carry a shaded block along a fixed track." },
|
|
156
|
+
{ value: "accordion", label: "Accordion", description: "Ease three shaded blocks back and forth." },
|
|
157
|
+
] as const satisfies readonly { value: TuiStatusPresentationStyle; label: string; description: string }[]);
|
|
158
|
+
|
|
159
|
+
/** Effect painted across the activity message and, optionally, its indicator. */
|
|
160
|
+
export type TuiTextEffectStyle =
|
|
161
|
+
| "off"
|
|
162
|
+
| "sweep"
|
|
163
|
+
| "glow"
|
|
164
|
+
| "rainbow"
|
|
165
|
+
| "rainbow-glow"
|
|
166
|
+
| "lightning"
|
|
167
|
+
| "aurora"
|
|
168
|
+
| "glitch"
|
|
169
|
+
| "crush";
|
|
170
|
+
|
|
171
|
+
/** Part of the inline composition painted by the configured text effect. */
|
|
172
|
+
export type TuiTextEffectScope = "message" | "inline";
|
|
173
|
+
|
|
174
|
+
/** Pulse composed over an indicator and text effect. */
|
|
175
|
+
export type TuiPulseEffectStyle = "off" | "pulse" | "color";
|
|
176
|
+
|
|
177
|
+
export type TuiActivityPresentation =
|
|
178
|
+
| {
|
|
179
|
+
kind: "inline";
|
|
180
|
+
indicatorStyle: TuiActivityIndicatorStyle;
|
|
181
|
+
textEffectStyle: TuiTextEffectStyle;
|
|
182
|
+
messageStyle: TuiActivityMessageStyle;
|
|
183
|
+
textEffectScope: TuiTextEffectScope;
|
|
184
|
+
pulseEffectStyle: TuiPulseEffectStyle;
|
|
185
|
+
}
|
|
186
|
+
| { kind: "composition"; style: "brainstorm" | "orbit-dots" }
|
|
187
|
+
| {
|
|
188
|
+
kind: "scene";
|
|
189
|
+
style: Exclude<TuiStatusPresentationStyle, "standard" | "brainstorm" | "orbit-dots">;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/** Global pace applied to every configured activity animation. */
|
|
193
|
+
export type TuiAnimationSpeed = "slow" | "relaxed" | "normal" | "fast" | "very-fast";
|
|
194
|
+
|
|
195
|
+
/** Global repaint frequency used by every configured activity animation. */
|
|
196
|
+
export type TuiAnimationSmoothness = "economy" | "balanced" | "smooth" | "ultra";
|
|
197
|
+
|
|
198
|
+
/** Request lifecycle phase shown by Pi's streaming status row. */
|
|
199
|
+
export type TuiRequestPhase = "thinking" | "working" | "tool";
|
|
200
|
+
|
|
201
|
+
/** One phase may inherit the general animation or replace it. */
|
|
202
|
+
export type TuiAnimationOverride<Value extends string> = "inherit" | Value;
|
|
203
|
+
|
|
204
|
+
/** Cursor presentation policy for one semantic cursor role. */
|
|
205
|
+
export type TuiCursorStyle =
|
|
206
|
+
| "virtual"
|
|
207
|
+
| "terminal-default"
|
|
208
|
+
| "blinking-block"
|
|
209
|
+
| "steady-block"
|
|
210
|
+
| "blinking-underline"
|
|
211
|
+
| "steady-underline"
|
|
212
|
+
| "blinking-bar"
|
|
213
|
+
| "steady-bar";
|
|
214
|
+
|
|
215
|
+
/** Process-wide rendering choices consumed by pi-libtui components. */
|
|
216
|
+
export interface TuiAppearanceSettings {
|
|
217
|
+
iconPack: TuiIconPack;
|
|
218
|
+
activityIndicator: TuiActivityIndicatorStyle;
|
|
219
|
+
activityMessage: TuiActivityMessageStyle;
|
|
220
|
+
textEffect: TuiTextEffectStyle;
|
|
221
|
+
textEffectScope: TuiTextEffectScope;
|
|
222
|
+
pulseEffect: TuiPulseEffectStyle;
|
|
223
|
+
statusPresentation: TuiStatusPresentationStyle;
|
|
224
|
+
animationSpeed: TuiAnimationSpeed;
|
|
225
|
+
animationSmoothness: TuiAnimationSmoothness;
|
|
226
|
+
thinkingIndicator: TuiAnimationOverride<TuiActivityIndicatorStyle>;
|
|
227
|
+
thinkingMessage: TuiAnimationOverride<TuiActivityMessageStyle>;
|
|
228
|
+
thinkingTextEffect: TuiAnimationOverride<TuiTextEffectStyle>;
|
|
229
|
+
thinkingPulseEffect: TuiAnimationOverride<TuiPulseEffectStyle>;
|
|
230
|
+
thinkingPresentation: TuiAnimationOverride<TuiStatusPresentationStyle>;
|
|
231
|
+
workingIndicator: TuiAnimationOverride<TuiActivityIndicatorStyle>;
|
|
232
|
+
workingMessage: TuiAnimationOverride<TuiActivityMessageStyle>;
|
|
233
|
+
workingTextEffect: TuiAnimationOverride<TuiTextEffectStyle>;
|
|
234
|
+
workingPulseEffect: TuiAnimationOverride<TuiPulseEffectStyle>;
|
|
235
|
+
workingPresentation: TuiAnimationOverride<TuiStatusPresentationStyle>;
|
|
236
|
+
toolIndicator: TuiAnimationOverride<TuiActivityIndicatorStyle>;
|
|
237
|
+
toolMessage: TuiAnimationOverride<TuiActivityMessageStyle>;
|
|
238
|
+
toolTextEffect: TuiAnimationOverride<TuiTextEffectStyle>;
|
|
239
|
+
toolPulseEffect: TuiAnimationOverride<TuiPulseEffectStyle>;
|
|
240
|
+
toolPresentation: TuiAnimationOverride<TuiStatusPresentationStyle>;
|
|
241
|
+
powerline: boolean;
|
|
242
|
+
powerlineButtons: boolean;
|
|
243
|
+
softCursor: boolean;
|
|
244
|
+
insertionCursor: TuiCursorStyle;
|
|
245
|
+
navigationCursor: TuiCursorStyle;
|
|
246
|
+
selectionCursor: TuiCursorStyle;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Appearance used when no xsettings host has published an override. */
|
|
250
|
+
export const DEFAULT_TUI_APPEARANCE: Readonly<TuiAppearanceSettings> = Object.freeze({
|
|
251
|
+
iconPack: "unicode",
|
|
252
|
+
activityIndicator: "spinner",
|
|
253
|
+
activityMessage: "phase",
|
|
254
|
+
textEffect: "off",
|
|
255
|
+
textEffectScope: "message",
|
|
256
|
+
pulseEffect: "off",
|
|
257
|
+
statusPresentation: "standard",
|
|
258
|
+
animationSpeed: "normal",
|
|
259
|
+
animationSmoothness: "balanced",
|
|
260
|
+
thinkingIndicator: "inherit",
|
|
261
|
+
thinkingMessage: "inherit",
|
|
262
|
+
thinkingTextEffect: "inherit",
|
|
263
|
+
thinkingPulseEffect: "inherit",
|
|
264
|
+
thinkingPresentation: "inherit",
|
|
265
|
+
workingIndicator: "inherit",
|
|
266
|
+
workingMessage: "inherit",
|
|
267
|
+
workingTextEffect: "inherit",
|
|
268
|
+
workingPulseEffect: "inherit",
|
|
269
|
+
workingPresentation: "inherit",
|
|
270
|
+
toolIndicator: "inherit",
|
|
271
|
+
toolMessage: "inherit",
|
|
272
|
+
toolTextEffect: "inherit",
|
|
273
|
+
toolPulseEffect: "inherit",
|
|
274
|
+
toolPresentation: "inherit",
|
|
275
|
+
powerline: false,
|
|
276
|
+
powerlineButtons: false,
|
|
277
|
+
softCursor: false,
|
|
278
|
+
insertionCursor: "virtual",
|
|
279
|
+
navigationCursor: "virtual",
|
|
280
|
+
selectionCursor: "virtual",
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const APPEARANCE_PROTOCOL = "pi-libtui/appearance/v7" as const;
|
|
284
|
+
const APPEARANCE_KEY = Symbol.for(APPEARANCE_PROTOCOL);
|
|
285
|
+
|
|
286
|
+
interface AppearanceRegistry {
|
|
287
|
+
readonly protocol: typeof APPEARANCE_PROTOCOL;
|
|
288
|
+
configure(next: Partial<TuiAppearanceSettings>): void;
|
|
289
|
+
get(): Readonly<TuiAppearanceSettings>;
|
|
290
|
+
subscribe(listener: () => void): () => void;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// type-boundary: Symbol.for state may come from another extension realm; appearanceRegistry validates it immediately.
|
|
294
|
+
type UntrustedAppearanceValue = unknown;
|
|
295
|
+
|
|
296
|
+
function isIconPack(value: UntrustedAppearanceValue): value is TuiIconPack {
|
|
297
|
+
return value === "nerd-fonts" || value === "unicode" || value === "emoji";
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Narrow an external value to one supported activity marker. */
|
|
301
|
+
export function isTuiActivityIndicatorStyle(value: UntrustedAppearanceValue): value is TuiActivityIndicatorStyle {
|
|
302
|
+
return (
|
|
303
|
+
value === "off" ||
|
|
304
|
+
value === "spinner" ||
|
|
305
|
+
value === "static" ||
|
|
306
|
+
value === "line" ||
|
|
307
|
+
value === "arc" ||
|
|
308
|
+
value === "pipe" ||
|
|
309
|
+
value === "grow-vertical" ||
|
|
310
|
+
value === "grow-horizontal" ||
|
|
311
|
+
value === "triangle" ||
|
|
312
|
+
value === "circle-quarters" ||
|
|
313
|
+
value === "circle-halves" ||
|
|
314
|
+
value === "bracket-spin" ||
|
|
315
|
+
value === "dots" ||
|
|
316
|
+
value === "quadrants" ||
|
|
317
|
+
value === "sparkle" ||
|
|
318
|
+
value === "braille-wave" ||
|
|
319
|
+
value === "braille-dna" ||
|
|
320
|
+
value === "braille-scan" ||
|
|
321
|
+
value === "braille-rain" ||
|
|
322
|
+
value === "braille-scanline" ||
|
|
323
|
+
value === "braille-pulse" ||
|
|
324
|
+
value === "braille-sparkle" ||
|
|
325
|
+
value === "braille-cascade" ||
|
|
326
|
+
value === "braille-columns" ||
|
|
327
|
+
value === "braille-orbit" ||
|
|
328
|
+
value === "braille-breathe" ||
|
|
329
|
+
value === "braille-wave-rows" ||
|
|
330
|
+
value === "braille-checkerboard" ||
|
|
331
|
+
value === "braille-helix" ||
|
|
332
|
+
value === "scanline" ||
|
|
333
|
+
value === "snake" ||
|
|
334
|
+
value === "fill-sweep" ||
|
|
335
|
+
value === "diagonal-swipe" ||
|
|
336
|
+
value === "dna" ||
|
|
337
|
+
value === "radar" ||
|
|
338
|
+
value === "bounce" ||
|
|
339
|
+
value === "orbit" ||
|
|
340
|
+
value === "conveyor" ||
|
|
341
|
+
value === "heartbeat" ||
|
|
342
|
+
value === "nerd-progress" ||
|
|
343
|
+
value === "nerd-morph" ||
|
|
344
|
+
value === "nerd-pipeline" ||
|
|
345
|
+
value === "nerd-pi-orbit"
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Narrow an external value to one supported status presentation. */
|
|
350
|
+
export function isTuiStatusPresentationStyle(value: UntrustedAppearanceValue): value is TuiStatusPresentationStyle {
|
|
351
|
+
return (
|
|
352
|
+
value === "standard" ||
|
|
353
|
+
value === "neural-pulse" ||
|
|
354
|
+
value === "plasma-wave" ||
|
|
355
|
+
value === "pacman" ||
|
|
356
|
+
value === "matrix" ||
|
|
357
|
+
value === "pipeline" ||
|
|
358
|
+
value === "starfield" ||
|
|
359
|
+
value === "fire" ||
|
|
360
|
+
value === "icon-morph" ||
|
|
361
|
+
value === "brainstorm" ||
|
|
362
|
+
value === "dev-constellation" ||
|
|
363
|
+
value === "pi-pulse" ||
|
|
364
|
+
value === "orbit-dots" ||
|
|
365
|
+
value === "neon-bounce" ||
|
|
366
|
+
value === "block-wave" ||
|
|
367
|
+
value === "conveyor" ||
|
|
368
|
+
value === "accordion"
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** Narrow an external value to one supported activity message source. */
|
|
373
|
+
export function isTuiActivityMessageStyle(value: UntrustedAppearanceValue): value is TuiActivityMessageStyle {
|
|
374
|
+
return value === "phase" || value === "typewriter";
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Narrow an external value to one supported text effect. */
|
|
378
|
+
export function isTuiTextEffectStyle(value: UntrustedAppearanceValue): value is TuiTextEffectStyle {
|
|
379
|
+
return (
|
|
380
|
+
value === "off" ||
|
|
381
|
+
value === "sweep" ||
|
|
382
|
+
value === "glow" ||
|
|
383
|
+
value === "rainbow" ||
|
|
384
|
+
value === "rainbow-glow" ||
|
|
385
|
+
value === "lightning" ||
|
|
386
|
+
value === "aurora" ||
|
|
387
|
+
value === "glitch" ||
|
|
388
|
+
value === "crush"
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Narrow an external value to one supported text-effect scope. */
|
|
393
|
+
export function isTuiTextEffectScope(value: UntrustedAppearanceValue): value is TuiTextEffectScope {
|
|
394
|
+
return value === "message" || value === "inline";
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Narrow an external value to one supported pulse effect. */
|
|
398
|
+
export function isTuiPulseEffectStyle(value: UntrustedAppearanceValue): value is TuiPulseEffectStyle {
|
|
399
|
+
return value === "off" || value === "pulse" || value === "color";
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function isMarkerOverride(value: UntrustedAppearanceValue): value is TuiAnimationOverride<TuiActivityIndicatorStyle> {
|
|
403
|
+
return value === "inherit" || isTuiActivityIndicatorStyle(value);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function isTextEffectOverride(value: UntrustedAppearanceValue): value is TuiAnimationOverride<TuiTextEffectStyle> {
|
|
407
|
+
return value === "inherit" || isTuiTextEffectStyle(value);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function isPulseEffectOverride(value: UntrustedAppearanceValue): value is TuiAnimationOverride<TuiPulseEffectStyle> {
|
|
411
|
+
return value === "inherit" || isTuiPulseEffectStyle(value);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function isMessageOverride(value: UntrustedAppearanceValue): value is TuiAnimationOverride<TuiActivityMessageStyle> {
|
|
415
|
+
return value === "inherit" || isTuiActivityMessageStyle(value);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function isPresentationOverride(
|
|
419
|
+
value: UntrustedAppearanceValue,
|
|
420
|
+
): value is TuiAnimationOverride<TuiStatusPresentationStyle> {
|
|
421
|
+
return value === "inherit" || isTuiStatusPresentationStyle(value);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** Narrow an external value to one supported animation speed. */
|
|
425
|
+
export function isTuiAnimationSpeed(value: UntrustedAppearanceValue): value is TuiAnimationSpeed {
|
|
426
|
+
return value === "slow" || value === "relaxed" || value === "normal" || value === "fast" || value === "very-fast";
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** Narrow an external value to one supported animation smoothness. */
|
|
430
|
+
export function isTuiAnimationSmoothness(value: UntrustedAppearanceValue): value is TuiAnimationSmoothness {
|
|
431
|
+
return value === "economy" || value === "balanced" || value === "smooth" || value === "ultra";
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** Resolve one request phase against the general activity animation. */
|
|
435
|
+
export function requestPhaseAnimation(
|
|
436
|
+
phase: TuiRequestPhase,
|
|
437
|
+
appearance: Readonly<TuiAppearanceSettings> = getTuiAppearance(),
|
|
438
|
+
): TuiActivityPresentation {
|
|
439
|
+
const indicator = appearance[`${phase}Indicator`];
|
|
440
|
+
const message = appearance[`${phase}Message`];
|
|
441
|
+
const effect = appearance[`${phase}TextEffect`];
|
|
442
|
+
const pulse = appearance[`${phase}PulseEffect`];
|
|
443
|
+
const presentation = appearance[`${phase}Presentation`];
|
|
444
|
+
return resolveActivityPresentation(
|
|
445
|
+
indicator === "inherit" ? appearance.activityIndicator : indicator,
|
|
446
|
+
message === "inherit" ? appearance.activityMessage : message,
|
|
447
|
+
effect === "inherit" ? appearance.textEffect : effect,
|
|
448
|
+
appearance.textEffectScope,
|
|
449
|
+
pulse === "inherit" ? appearance.pulseEffect : pulse,
|
|
450
|
+
presentation === "inherit" ? appearance.statusPresentation : presentation,
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** Build the valid activity topology for one inline composition or exclusive presentation. */
|
|
455
|
+
export function resolveActivityPresentation(
|
|
456
|
+
indicatorStyle: TuiActivityIndicatorStyle,
|
|
457
|
+
messageStyle: TuiActivityMessageStyle,
|
|
458
|
+
textEffectStyle: TuiTextEffectStyle,
|
|
459
|
+
textEffectScope: TuiTextEffectScope,
|
|
460
|
+
pulseEffectStyle: TuiPulseEffectStyle,
|
|
461
|
+
presentationStyle: TuiStatusPresentationStyle,
|
|
462
|
+
): TuiActivityPresentation {
|
|
463
|
+
if (presentationStyle === "standard")
|
|
464
|
+
return { kind: "inline", indicatorStyle, messageStyle, textEffectStyle, textEffectScope, pulseEffectStyle };
|
|
465
|
+
if (presentationStyle === "brainstorm" || presentationStyle === "orbit-dots")
|
|
466
|
+
return { kind: "composition", style: presentationStyle };
|
|
467
|
+
return { kind: "scene", style: presentationStyle };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function isCursorStyle(value: UntrustedAppearanceValue): value is TuiCursorStyle {
|
|
471
|
+
return (
|
|
472
|
+
value === "virtual" ||
|
|
473
|
+
value === "terminal-default" ||
|
|
474
|
+
value === "blinking-block" ||
|
|
475
|
+
value === "steady-block" ||
|
|
476
|
+
value === "blinking-underline" ||
|
|
477
|
+
value === "steady-underline" ||
|
|
478
|
+
value === "blinking-bar" ||
|
|
479
|
+
value === "steady-bar"
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function isAppearanceRegistry(value: UntrustedAppearanceValue): value is AppearanceRegistry {
|
|
484
|
+
if (!value || typeof value !== "object") return false;
|
|
485
|
+
const candidate = value as Partial<AppearanceRegistry>;
|
|
486
|
+
return (
|
|
487
|
+
candidate.protocol === APPEARANCE_PROTOCOL &&
|
|
488
|
+
typeof candidate.configure === "function" &&
|
|
489
|
+
typeof candidate.get === "function" &&
|
|
490
|
+
typeof candidate.subscribe === "function"
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function mergeAppearance(
|
|
495
|
+
current: Readonly<TuiAppearanceSettings>,
|
|
496
|
+
next: Partial<TuiAppearanceSettings>,
|
|
497
|
+
): TuiAppearanceSettings {
|
|
498
|
+
return {
|
|
499
|
+
iconPack: isIconPack(next.iconPack) ? next.iconPack : current.iconPack,
|
|
500
|
+
activityIndicator: isTuiActivityIndicatorStyle(next.activityIndicator)
|
|
501
|
+
? next.activityIndicator
|
|
502
|
+
: current.activityIndicator,
|
|
503
|
+
activityMessage: isTuiActivityMessageStyle(next.activityMessage) ? next.activityMessage : current.activityMessage,
|
|
504
|
+
textEffect: isTuiTextEffectStyle(next.textEffect) ? next.textEffect : current.textEffect,
|
|
505
|
+
textEffectScope: isTuiTextEffectScope(next.textEffectScope) ? next.textEffectScope : current.textEffectScope,
|
|
506
|
+
pulseEffect: isTuiPulseEffectStyle(next.pulseEffect) ? next.pulseEffect : current.pulseEffect,
|
|
507
|
+
statusPresentation: isTuiStatusPresentationStyle(next.statusPresentation)
|
|
508
|
+
? next.statusPresentation
|
|
509
|
+
: current.statusPresentation,
|
|
510
|
+
animationSpeed: isTuiAnimationSpeed(next.animationSpeed) ? next.animationSpeed : current.animationSpeed,
|
|
511
|
+
animationSmoothness: isTuiAnimationSmoothness(next.animationSmoothness)
|
|
512
|
+
? next.animationSmoothness
|
|
513
|
+
: current.animationSmoothness,
|
|
514
|
+
thinkingIndicator: isMarkerOverride(next.thinkingIndicator) ? next.thinkingIndicator : current.thinkingIndicator,
|
|
515
|
+
thinkingMessage: isMessageOverride(next.thinkingMessage) ? next.thinkingMessage : current.thinkingMessage,
|
|
516
|
+
thinkingTextEffect: isTextEffectOverride(next.thinkingTextEffect)
|
|
517
|
+
? next.thinkingTextEffect
|
|
518
|
+
: current.thinkingTextEffect,
|
|
519
|
+
thinkingPulseEffect: isPulseEffectOverride(next.thinkingPulseEffect)
|
|
520
|
+
? next.thinkingPulseEffect
|
|
521
|
+
: current.thinkingPulseEffect,
|
|
522
|
+
thinkingPresentation: isPresentationOverride(next.thinkingPresentation)
|
|
523
|
+
? next.thinkingPresentation
|
|
524
|
+
: current.thinkingPresentation,
|
|
525
|
+
workingIndicator: isMarkerOverride(next.workingIndicator) ? next.workingIndicator : current.workingIndicator,
|
|
526
|
+
workingMessage: isMessageOverride(next.workingMessage) ? next.workingMessage : current.workingMessage,
|
|
527
|
+
workingTextEffect: isTextEffectOverride(next.workingTextEffect)
|
|
528
|
+
? next.workingTextEffect
|
|
529
|
+
: current.workingTextEffect,
|
|
530
|
+
workingPulseEffect: isPulseEffectOverride(next.workingPulseEffect)
|
|
531
|
+
? next.workingPulseEffect
|
|
532
|
+
: current.workingPulseEffect,
|
|
533
|
+
workingPresentation: isPresentationOverride(next.workingPresentation)
|
|
534
|
+
? next.workingPresentation
|
|
535
|
+
: current.workingPresentation,
|
|
536
|
+
toolIndicator: isMarkerOverride(next.toolIndicator) ? next.toolIndicator : current.toolIndicator,
|
|
537
|
+
toolMessage: isMessageOverride(next.toolMessage) ? next.toolMessage : current.toolMessage,
|
|
538
|
+
toolTextEffect: isTextEffectOverride(next.toolTextEffect) ? next.toolTextEffect : current.toolTextEffect,
|
|
539
|
+
toolPulseEffect: isPulseEffectOverride(next.toolPulseEffect) ? next.toolPulseEffect : current.toolPulseEffect,
|
|
540
|
+
toolPresentation: isPresentationOverride(next.toolPresentation) ? next.toolPresentation : current.toolPresentation,
|
|
541
|
+
powerline: typeof next.powerline === "boolean" ? next.powerline : current.powerline,
|
|
542
|
+
powerlineButtons: typeof next.powerlineButtons === "boolean" ? next.powerlineButtons : current.powerlineButtons,
|
|
543
|
+
softCursor: typeof next.softCursor === "boolean" ? next.softCursor : current.softCursor,
|
|
544
|
+
insertionCursor: isCursorStyle(next.insertionCursor) ? next.insertionCursor : current.insertionCursor,
|
|
545
|
+
navigationCursor: isCursorStyle(next.navigationCursor) ? next.navigationCursor : current.navigationCursor,
|
|
546
|
+
selectionCursor: isCursorStyle(next.selectionCursor) ? next.selectionCursor : current.selectionCursor,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function sameAppearance(left: Readonly<TuiAppearanceSettings>, right: Readonly<TuiAppearanceSettings>): boolean {
|
|
551
|
+
return (
|
|
552
|
+
left.iconPack === right.iconPack &&
|
|
553
|
+
left.activityIndicator === right.activityIndicator &&
|
|
554
|
+
left.activityMessage === right.activityMessage &&
|
|
555
|
+
left.textEffect === right.textEffect &&
|
|
556
|
+
left.textEffectScope === right.textEffectScope &&
|
|
557
|
+
left.pulseEffect === right.pulseEffect &&
|
|
558
|
+
left.statusPresentation === right.statusPresentation &&
|
|
559
|
+
left.animationSpeed === right.animationSpeed &&
|
|
560
|
+
left.animationSmoothness === right.animationSmoothness &&
|
|
561
|
+
left.thinkingIndicator === right.thinkingIndicator &&
|
|
562
|
+
left.thinkingMessage === right.thinkingMessage &&
|
|
563
|
+
left.thinkingTextEffect === right.thinkingTextEffect &&
|
|
564
|
+
left.thinkingPulseEffect === right.thinkingPulseEffect &&
|
|
565
|
+
left.thinkingPresentation === right.thinkingPresentation &&
|
|
566
|
+
left.workingIndicator === right.workingIndicator &&
|
|
567
|
+
left.workingMessage === right.workingMessage &&
|
|
568
|
+
left.workingTextEffect === right.workingTextEffect &&
|
|
569
|
+
left.workingPulseEffect === right.workingPulseEffect &&
|
|
570
|
+
left.workingPresentation === right.workingPresentation &&
|
|
571
|
+
left.toolIndicator === right.toolIndicator &&
|
|
572
|
+
left.toolMessage === right.toolMessage &&
|
|
573
|
+
left.toolTextEffect === right.toolTextEffect &&
|
|
574
|
+
left.toolPulseEffect === right.toolPulseEffect &&
|
|
575
|
+
left.toolPresentation === right.toolPresentation &&
|
|
576
|
+
left.powerline === right.powerline &&
|
|
577
|
+
left.powerlineButtons === right.powerlineButtons &&
|
|
578
|
+
left.softCursor === right.softCursor &&
|
|
579
|
+
left.insertionCursor === right.insertionCursor &&
|
|
580
|
+
left.navigationCursor === right.navigationCursor &&
|
|
581
|
+
left.selectionCursor === right.selectionCursor
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function appearanceRegistry(): AppearanceRegistry {
|
|
586
|
+
const slots = globalThis as Record<PropertyKey, UntrustedAppearanceValue>;
|
|
587
|
+
const existing = slots[APPEARANCE_KEY];
|
|
588
|
+
if (isAppearanceRegistry(existing)) return existing;
|
|
589
|
+
|
|
590
|
+
let settings = DEFAULT_TUI_APPEARANCE;
|
|
591
|
+
const listeners = new Set<() => void>();
|
|
592
|
+
const registry: AppearanceRegistry = {
|
|
593
|
+
protocol: APPEARANCE_PROTOCOL,
|
|
594
|
+
configure(next) {
|
|
595
|
+
const updated = mergeAppearance(settings, next);
|
|
596
|
+
if (sameAppearance(updated, settings)) return;
|
|
597
|
+
|
|
598
|
+
settings = Object.freeze(updated);
|
|
599
|
+
bumpTuiRenderEpoch();
|
|
600
|
+
for (const listener of [...listeners]) {
|
|
601
|
+
try {
|
|
602
|
+
listener();
|
|
603
|
+
} catch {
|
|
604
|
+
// Rendering subscriptions must not prevent other components from updating.
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
get: () => settings,
|
|
609
|
+
subscribe(listener) {
|
|
610
|
+
listeners.add(listener);
|
|
611
|
+
return () => listeners.delete(listener);
|
|
612
|
+
},
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
slots[APPEARANCE_KEY] = registry;
|
|
616
|
+
return registry;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/** Merge valid fields into the process-wide appearance. */
|
|
620
|
+
export function configureTuiAppearance(next: Partial<TuiAppearanceSettings>): void {
|
|
621
|
+
appearanceRegistry().configure(next);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/** Read the immutable current process-wide appearance. */
|
|
625
|
+
export function getTuiAppearance(): Readonly<TuiAppearanceSettings> {
|
|
626
|
+
return appearanceRegistry().get();
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/** Subscribe to effective appearance changes. */
|
|
630
|
+
export function subscribeTuiAppearance(listener: () => void): () => void {
|
|
631
|
+
return appearanceRegistry().subscribe(listener);
|
|
632
|
+
}
|