@promptctl/rich-js 0.17.0 → 0.18.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/README.md +13 -26
- package/package.json +7 -3
- package/dist/core/box.d.ts +0 -144
- package/dist/core/box.js +0 -391
- package/dist/core/cells.d.ts +0 -148
- package/dist/core/cells.js +0 -266
- package/dist/core/color.d.ts +0 -250
- package/dist/core/color.js +0 -1029
- package/dist/core/console.d.ts +0 -156
- package/dist/core/console.js +0 -472
- package/dist/core/emoji.d.ts +0 -24
- package/dist/core/emoji.js +0 -391
- package/dist/core/export-html.d.ts +0 -24
- package/dist/core/export-html.js +0 -106
- package/dist/core/export-lines.d.ts +0 -121
- package/dist/core/export-lines.js +0 -141
- package/dist/core/highlighter.d.ts +0 -33
- package/dist/core/highlighter.js +0 -124
- package/dist/core/json.d.ts +0 -27
- package/dist/core/json.js +0 -53
- package/dist/core/markup.d.ts +0 -97
- package/dist/core/markup.js +0 -619
- package/dist/core/measure.d.ts +0 -27
- package/dist/core/measure.js +0 -61
- package/dist/core/oklch.d.ts +0 -161
- package/dist/core/oklch.js +0 -312
- package/dist/core/osc8.d.ts +0 -68
- package/dist/core/osc8.js +0 -90
- package/dist/core/pretty.d.ts +0 -141
- package/dist/core/pretty.js +0 -472
- package/dist/core/protocol.d.ts +0 -137
- package/dist/core/protocol.js +0 -101
- package/dist/core/render.d.ts +0 -84
- package/dist/core/render.js +0 -145
- package/dist/core/segment.d.ts +0 -142
- package/dist/core/segment.js +0 -433
- package/dist/core/spinnerData.d.ts +0 -10
- package/dist/core/spinnerData.js +0 -115
- package/dist/core/strip.d.ts +0 -145
- package/dist/core/strip.js +0 -308
- package/dist/core/style.d.ts +0 -144
- package/dist/core/style.js +0 -723
- package/dist/core/subscription.d.ts +0 -12
- package/dist/core/subscription.js +0 -12
- package/dist/core/text.d.ts +0 -255
- package/dist/core/text.js +0 -990
- package/dist/core/wrap.d.ts +0 -41
- package/dist/core/wrap.js +0 -127
- package/dist/host/host-stream.d.ts +0 -30
- package/dist/host/host-stream.js +0 -35
- package/dist/host/index.d.ts +0 -3
- package/dist/host/index.js +0 -2
- package/dist/host/terminal-host.d.ts +0 -143
- package/dist/host/terminal-host.js +0 -132
- package/dist/index.d.ts +0 -87
- package/dist/index.js +0 -104
- package/dist/node/prompt.d.ts +0 -21
- package/dist/node/prompt.js +0 -30
- package/dist/node/save.d.ts +0 -17
- package/dist/node/save.js +0 -21
- package/dist/node/terminal-host.d.ts +0 -84
- package/dist/node/terminal-host.js +0 -160
- package/dist/node/traceback.d.ts +0 -27
- package/dist/node/traceback.js +0 -112
- package/dist/renderables/align.d.ts +0 -16
- package/dist/renderables/align.js +0 -46
- package/dist/renderables/columns.d.ts +0 -61
- package/dist/renderables/columns.js +0 -168
- package/dist/renderables/constrain.d.ts +0 -15
- package/dist/renderables/constrain.js +0 -37
- package/dist/renderables/flexStrip.d.ts +0 -41
- package/dist/renderables/flexStrip.js +0 -200
- package/dist/renderables/group.d.ts +0 -11
- package/dist/renderables/group.js +0 -15
- package/dist/renderables/layout.d.ts +0 -92
- package/dist/renderables/layout.js +0 -315
- package/dist/renderables/live.d.ts +0 -47
- package/dist/renderables/live.js +0 -131
- package/dist/renderables/markdown.d.ts +0 -24
- package/dist/renderables/markdown.js +0 -182
- package/dist/renderables/padding.d.ts +0 -38
- package/dist/renderables/padding.js +0 -140
- package/dist/renderables/panel.d.ts +0 -116
- package/dist/renderables/panel.js +0 -322
- package/dist/renderables/progress.d.ts +0 -90
- package/dist/renderables/progress.js +0 -241
- package/dist/renderables/progressBar.d.ts +0 -31
- package/dist/renderables/progressBar.js +0 -52
- package/dist/renderables/prompt.d.ts +0 -49
- package/dist/renderables/prompt.js +0 -125
- package/dist/renderables/rule.d.ts +0 -24
- package/dist/renderables/rule.js +0 -90
- package/dist/renderables/spinner.d.ts +0 -29
- package/dist/renderables/spinner.js +0 -64
- package/dist/renderables/status.d.ts +0 -23
- package/dist/renderables/status.js +0 -60
- package/dist/renderables/syntax.d.ts +0 -35
- package/dist/renderables/syntax.js +0 -114
- package/dist/renderables/table.d.ts +0 -182
- package/dist/renderables/table.js +0 -697
- package/dist/renderables/traceback.d.ts +0 -31
- package/dist/renderables/traceback.js +0 -109
- package/dist/renderables/tree.d.ts +0 -54
- package/dist/renderables/tree.js +0 -159
- package/dist/template-bindings/color-funcs.d.ts +0 -87
- package/dist/template-bindings/color-funcs.js +0 -219
- package/dist/template-bindings/helpers.d.ts +0 -24
- package/dist/template-bindings/helpers.js +0 -48
- package/dist/template-bindings/index.d.ts +0 -107
- package/dist/template-bindings/index.js +0 -144
- package/dist/template-bindings/palette-funcs.d.ts +0 -94
- package/dist/template-bindings/palette-funcs.js +0 -151
- package/dist/template-bindings/style-funcs.d.ts +0 -28
- package/dist/template-bindings/style-funcs.js +0 -151
- package/dist/themes/buildPalette.d.ts +0 -36
- package/dist/themes/buildPalette.js +0 -56
- package/dist/themes/colorMath.d.ts +0 -82
- package/dist/themes/colorMath.js +0 -254
- package/dist/themes/colorRef.d.ts +0 -51
- package/dist/themes/colorRef.js +0 -96
- package/dist/themes/data/atom-one-dark.d.ts +0 -3
- package/dist/themes/data/atom-one-dark.js +0 -158
- package/dist/themes/data/atom-one-light.d.ts +0 -3
- package/dist/themes/data/atom-one-light.js +0 -158
- package/dist/themes/data/catppuccin-frappe.d.ts +0 -3
- package/dist/themes/data/catppuccin-frappe.js +0 -158
- package/dist/themes/data/catppuccin-latte.d.ts +0 -3
- package/dist/themes/data/catppuccin-latte.js +0 -158
- package/dist/themes/data/catppuccin-macchiato.d.ts +0 -3
- package/dist/themes/data/catppuccin-macchiato.js +0 -158
- package/dist/themes/data/catppuccin-mocha.d.ts +0 -3
- package/dist/themes/data/catppuccin-mocha.js +0 -158
- package/dist/themes/data/cyberpunk.d.ts +0 -17
- package/dist/themes/data/cyberpunk.js +0 -191
- package/dist/themes/data/default.d.ts +0 -3
- package/dist/themes/data/default.js +0 -158
- package/dist/themes/data/dracula.d.ts +0 -3
- package/dist/themes/data/dracula.js +0 -158
- package/dist/themes/data/flexoki.d.ts +0 -3
- package/dist/themes/data/flexoki.js +0 -158
- package/dist/themes/data/gruvbox.d.ts +0 -3
- package/dist/themes/data/gruvbox.js +0 -158
- package/dist/themes/data/index.d.ts +0 -35
- package/dist/themes/data/index.js +0 -64
- package/dist/themes/data/monokai.d.ts +0 -3
- package/dist/themes/data/monokai.js +0 -158
- package/dist/themes/data/nord.d.ts +0 -3
- package/dist/themes/data/nord.js +0 -158
- package/dist/themes/data/rose-pine-dawn.d.ts +0 -3
- package/dist/themes/data/rose-pine-dawn.js +0 -158
- package/dist/themes/data/rose-pine-moon.d.ts +0 -3
- package/dist/themes/data/rose-pine-moon.js +0 -158
- package/dist/themes/data/rose-pine.d.ts +0 -3
- package/dist/themes/data/rose-pine.js +0 -158
- package/dist/themes/data/solarized-dark.d.ts +0 -3
- package/dist/themes/data/solarized-dark.js +0 -158
- package/dist/themes/data/solarized-light.d.ts +0 -3
- package/dist/themes/data/solarized-light.js +0 -158
- package/dist/themes/data/svg-export.d.ts +0 -3
- package/dist/themes/data/svg-export.js +0 -158
- package/dist/themes/data/textual-ansi.d.ts +0 -3
- package/dist/themes/data/textual-ansi.js +0 -158
- package/dist/themes/data/textual-dark.d.ts +0 -3
- package/dist/themes/data/textual-dark.js +0 -158
- package/dist/themes/data/textual-light.d.ts +0 -3
- package/dist/themes/data/textual-light.js +0 -158
- package/dist/themes/data/tokyo-night.d.ts +0 -3
- package/dist/themes/data/tokyo-night.js +0 -158
- package/dist/themes/data/types.d.ts +0 -15
- package/dist/themes/data/types.js +0 -1
- package/dist/themes/palette.d.ts +0 -21
- package/dist/themes/palette.js +0 -28
- package/dist/themes/ramp.d.ts +0 -83
- package/dist/themes/ramp.js +0 -122
- package/dist/themes/registry.d.ts +0 -43
- package/dist/themes/registry.js +0 -82
- package/dist/themes/terminalThemes.d.ts +0 -54
- package/dist/themes/terminalThemes.js +0 -68
- package/dist/themes/transpose.d.ts +0 -87
- package/dist/themes/transpose.js +0 -138
- package/dist/widgets/button.d.ts +0 -49
- package/dist/widgets/button.js +0 -206
- package/dist/widgets/checkbox.d.ts +0 -41
- package/dist/widgets/checkbox.js +0 -155
- package/dist/widgets/dropdown.d.ts +0 -67
- package/dist/widgets/dropdown.js +0 -453
- package/dist/widgets/event-router.d.ts +0 -78
- package/dist/widgets/event-router.js +0 -622
- package/dist/widgets/focus-manager.d.ts +0 -26
- package/dist/widgets/focus-manager.js +0 -193
- package/dist/widgets/index.d.ts +0 -22
- package/dist/widgets/index.js +0 -15
- package/dist/widgets/screen.d.ts +0 -98
- package/dist/widgets/screen.js +0 -530
- package/dist/widgets/slider.d.ts +0 -57
- package/dist/widgets/slider.js +0 -270
- package/dist/widgets/static-item.d.ts +0 -40
- package/dist/widgets/static-item.js +0 -60
- package/dist/widgets/text-input.d.ts +0 -274
- package/dist/widgets/text-input.js +0 -1162
- package/dist/widgets/toggle.d.ts +0 -44
- package/dist/widgets/toggle.js +0 -179
- package/dist/widgets/types.d.ts +0 -108
- package/dist/widgets/types.js +0 -31
- package/dist/widgets/widget-base.d.ts +0 -42
- package/dist/widgets/widget-base.js +0 -173
package/dist/widgets/dropdown.js
DELETED
|
@@ -1,453 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dropdown widget — single-select from option list, with built-in filter.
|
|
3
|
-
*
|
|
4
|
-
* [LAW:one-source-of-truth] Inline footprint (always 1 row, the header)
|
|
5
|
-
* is independent of rendered shape (1 row collapsed, 1 + M rows expanded
|
|
6
|
-
* where M = filteredOptions.length, or 1 when no matches). render() emits
|
|
7
|
-
* the header row only; renderOverlay() emits option rows or a
|
|
8
|
-
* "(no matches)" placeholder.
|
|
9
|
-
*
|
|
10
|
-
* [LAW:dataflow-not-control-flow] `expanded` and `filter` are data;
|
|
11
|
-
* layout never reserves space for the option list; the overlay pass
|
|
12
|
-
* paints over whatever is below.
|
|
13
|
-
*
|
|
14
|
-
* [LAW:one-source-of-truth] `options` and `selectedIndex` are canonical.
|
|
15
|
-
* `filter` is internal view state. `filteredOptions` is the derived
|
|
16
|
-
* subsequence (case-insensitive substring match). `highlightedIndex`
|
|
17
|
-
* indexes into `filteredOptions`. Commit maps the filtered position
|
|
18
|
-
* back to canonical idx by reference, so selection survives any filter
|
|
19
|
-
* mutation.
|
|
20
|
-
*
|
|
21
|
-
* Width invariant: measure() always returns maxLabelLen(options) + 4
|
|
22
|
-
* regardless of filter state — the query is right-clipped, never wider
|
|
23
|
-
* than the header. See docs/widgets.md → Dropdown.
|
|
24
|
-
*/
|
|
25
|
-
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
26
|
-
var useValue = arguments.length > 2;
|
|
27
|
-
for (var i = 0; i < initializers.length; i++) {
|
|
28
|
-
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
29
|
-
}
|
|
30
|
-
return useValue ? value : void 0;
|
|
31
|
-
};
|
|
32
|
-
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
33
|
-
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
34
|
-
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
35
|
-
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
36
|
-
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
37
|
-
var _, done = false;
|
|
38
|
-
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
39
|
-
var context = {};
|
|
40
|
-
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
41
|
-
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
42
|
-
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
43
|
-
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
44
|
-
if (kind === "accessor") {
|
|
45
|
-
if (result === void 0) continue;
|
|
46
|
-
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
47
|
-
if (_ = accept(result.get)) descriptor.get = _;
|
|
48
|
-
if (_ = accept(result.set)) descriptor.set = _;
|
|
49
|
-
if (_ = accept(result.init)) initializers.unshift(_);
|
|
50
|
-
}
|
|
51
|
-
else if (_ = accept(result)) {
|
|
52
|
-
if (kind === "field") initializers.unshift(_);
|
|
53
|
-
else descriptor[key] = _;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
57
|
-
done = true;
|
|
58
|
-
};
|
|
59
|
-
import { observable, action, observableRef, observableShallow } from "mobx";
|
|
60
|
-
import { Segment } from "../core/segment.js";
|
|
61
|
-
import { Style } from "../core/style.js";
|
|
62
|
-
import { ColorSpec } from "../core/color.js";
|
|
63
|
-
import { cellLen, setCellSize, splitText, asCellCol } from "../core/cells.js";
|
|
64
|
-
import { DEFAULT_TERMINAL_THEME } from "../themes/terminalThemes.js";
|
|
65
|
-
import { WidgetBase } from "./widget-base.js";
|
|
66
|
-
let Dropdown = (() => {
|
|
67
|
-
let _classSuper = WidgetBase;
|
|
68
|
-
let _instanceExtraInitializers = [];
|
|
69
|
-
let _options_decorators;
|
|
70
|
-
let _options_initializers = [];
|
|
71
|
-
let _options_extraInitializers = [];
|
|
72
|
-
let _selectedIndex_decorators;
|
|
73
|
-
let _selectedIndex_initializers = [];
|
|
74
|
-
let _selectedIndex_extraInitializers = [];
|
|
75
|
-
let _expanded_decorators;
|
|
76
|
-
let _expanded_initializers = [];
|
|
77
|
-
let _expanded_extraInitializers = [];
|
|
78
|
-
let _highlightedIndex_decorators;
|
|
79
|
-
let _highlightedIndex_initializers = [];
|
|
80
|
-
let _highlightedIndex_extraInitializers = [];
|
|
81
|
-
let _filter_decorators;
|
|
82
|
-
let _filter_initializers = [];
|
|
83
|
-
let _filter_extraInitializers = [];
|
|
84
|
-
let __theme_decorators;
|
|
85
|
-
let __theme_initializers = [];
|
|
86
|
-
let __theme_extraInitializers = [];
|
|
87
|
-
let _setTheme_decorators;
|
|
88
|
-
let _handleKey_decorators;
|
|
89
|
-
let _handleMouse_decorators;
|
|
90
|
-
let _handleFocus_decorators;
|
|
91
|
-
return class Dropdown extends _classSuper {
|
|
92
|
-
static {
|
|
93
|
-
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
94
|
-
_options_decorators = [observableShallow];
|
|
95
|
-
_selectedIndex_decorators = [observable];
|
|
96
|
-
_expanded_decorators = [observable];
|
|
97
|
-
_highlightedIndex_decorators = [observable];
|
|
98
|
-
_filter_decorators = [observable];
|
|
99
|
-
__theme_decorators = [observableRef];
|
|
100
|
-
_setTheme_decorators = [action];
|
|
101
|
-
_handleKey_decorators = [action];
|
|
102
|
-
_handleMouse_decorators = [action];
|
|
103
|
-
_handleFocus_decorators = [action];
|
|
104
|
-
__esDecorate(this, null, _options_decorators, { kind: "accessor", name: "options", static: false, private: false, access: { has: obj => "options" in obj, get: obj => obj.options, set: (obj, value) => { obj.options = value; } }, metadata: _metadata }, _options_initializers, _options_extraInitializers);
|
|
105
|
-
__esDecorate(this, null, _selectedIndex_decorators, { kind: "accessor", name: "selectedIndex", static: false, private: false, access: { has: obj => "selectedIndex" in obj, get: obj => obj.selectedIndex, set: (obj, value) => { obj.selectedIndex = value; } }, metadata: _metadata }, _selectedIndex_initializers, _selectedIndex_extraInitializers);
|
|
106
|
-
__esDecorate(this, null, _expanded_decorators, { kind: "accessor", name: "expanded", static: false, private: false, access: { has: obj => "expanded" in obj, get: obj => obj.expanded, set: (obj, value) => { obj.expanded = value; } }, metadata: _metadata }, _expanded_initializers, _expanded_extraInitializers);
|
|
107
|
-
__esDecorate(this, null, _highlightedIndex_decorators, { kind: "accessor", name: "highlightedIndex", static: false, private: false, access: { has: obj => "highlightedIndex" in obj, get: obj => obj.highlightedIndex, set: (obj, value) => { obj.highlightedIndex = value; } }, metadata: _metadata }, _highlightedIndex_initializers, _highlightedIndex_extraInitializers);
|
|
108
|
-
__esDecorate(this, null, _filter_decorators, { kind: "accessor", name: "filter", static: false, private: false, access: { has: obj => "filter" in obj, get: obj => obj.filter, set: (obj, value) => { obj.filter = value; } }, metadata: _metadata }, _filter_initializers, _filter_extraInitializers);
|
|
109
|
-
__esDecorate(this, null, __theme_decorators, { kind: "accessor", name: "_theme", static: false, private: false, access: { has: obj => "_theme" in obj, get: obj => obj._theme, set: (obj, value) => { obj._theme = value; } }, metadata: _metadata }, __theme_initializers, __theme_extraInitializers);
|
|
110
|
-
__esDecorate(this, null, _setTheme_decorators, { kind: "method", name: "setTheme", static: false, private: false, access: { has: obj => "setTheme" in obj, get: obj => obj.setTheme }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
111
|
-
__esDecorate(this, null, _handleKey_decorators, { kind: "method", name: "handleKey", static: false, private: false, access: { has: obj => "handleKey" in obj, get: obj => obj.handleKey }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
112
|
-
__esDecorate(this, null, _handleMouse_decorators, { kind: "method", name: "handleMouse", static: false, private: false, access: { has: obj => "handleMouse" in obj, get: obj => obj.handleMouse }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
113
|
-
__esDecorate(this, null, _handleFocus_decorators, { kind: "method", name: "handleFocus", static: false, private: false, access: { has: obj => "handleFocus" in obj, get: obj => obj.handleFocus }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
114
|
-
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
115
|
-
}
|
|
116
|
-
id = __runInitializers(this, _instanceExtraInitializers);
|
|
117
|
-
focusable = true;
|
|
118
|
-
#options_accessor_storage = __runInitializers(this, _options_initializers, void 0);
|
|
119
|
-
get options() { return this.#options_accessor_storage; }
|
|
120
|
-
set options(value) { this.#options_accessor_storage = value; }
|
|
121
|
-
#selectedIndex_accessor_storage = (__runInitializers(this, _options_extraInitializers), __runInitializers(this, _selectedIndex_initializers, void 0));
|
|
122
|
-
get selectedIndex() { return this.#selectedIndex_accessor_storage; }
|
|
123
|
-
set selectedIndex(value) { this.#selectedIndex_accessor_storage = value; }
|
|
124
|
-
#expanded_accessor_storage = (__runInitializers(this, _selectedIndex_extraInitializers), __runInitializers(this, _expanded_initializers, false));
|
|
125
|
-
get expanded() { return this.#expanded_accessor_storage; }
|
|
126
|
-
set expanded(value) { this.#expanded_accessor_storage = value; }
|
|
127
|
-
#highlightedIndex_accessor_storage = (__runInitializers(this, _expanded_extraInitializers), __runInitializers(this, _highlightedIndex_initializers, void 0));
|
|
128
|
-
get highlightedIndex() { return this.#highlightedIndex_accessor_storage; }
|
|
129
|
-
set highlightedIndex(value) { this.#highlightedIndex_accessor_storage = value; }
|
|
130
|
-
#filter_accessor_storage = (__runInitializers(this, _highlightedIndex_extraInitializers), __runInitializers(this, _filter_initializers, ""));
|
|
131
|
-
get filter() { return this.#filter_accessor_storage; }
|
|
132
|
-
set filter(value) { this.#filter_accessor_storage = value; }
|
|
133
|
-
// [LAW:dataflow-not-control-flow] Derived view; empty filter falls out
|
|
134
|
-
// naturally because "".includes("") is true for every label — no
|
|
135
|
-
// special-case branch.
|
|
136
|
-
get filteredOptions() {
|
|
137
|
-
const f = this.filter.toLowerCase();
|
|
138
|
-
const result = [];
|
|
139
|
-
for (let i = 0; i < this.options.length; i++) {
|
|
140
|
-
const label = this.options[i];
|
|
141
|
-
if (label.toLowerCase().includes(f))
|
|
142
|
-
result.push({ label, idx: i });
|
|
143
|
-
}
|
|
144
|
-
return result;
|
|
145
|
-
}
|
|
146
|
-
#_theme_accessor_storage = (__runInitializers(this, _filter_extraInitializers), __runInitializers(this, __theme_initializers, void 0));
|
|
147
|
-
// [LAW:types-are-the-program] @observableRef so setTheme() triggers a
|
|
148
|
-
// re-render — render() and resolvePalette() read _theme.palette, so the
|
|
149
|
-
// theme reference must participate in MobX reactivity.
|
|
150
|
-
get _theme() { return this.#_theme_accessor_storage; }
|
|
151
|
-
set _theme(value) { this.#_theme_accessor_storage = value; }
|
|
152
|
-
constructor(options) {
|
|
153
|
-
super();
|
|
154
|
-
__runInitializers(this, __theme_extraInitializers);
|
|
155
|
-
this.id = options.id ?? `dropdown-${Math.random().toString(36).slice(2, 8)}`;
|
|
156
|
-
this.options = [...options.options];
|
|
157
|
-
// [LAW:types-are-the-program] selectedIndex must point to a valid
|
|
158
|
-
// option (or be 0 for the empty-options case). Clamp at the trust
|
|
159
|
-
// boundary so headerText / render / commit logic can all assume
|
|
160
|
-
// validity. Empty options is allowed — render falls back to an
|
|
161
|
-
// empty label and commit is a no-op until options are populated.
|
|
162
|
-
this.selectedIndex = this.clampIndex(options.selectedIndex ?? 0);
|
|
163
|
-
this.highlightedIndex = this.selectedIndex;
|
|
164
|
-
this.disabled = options.disabled ?? false;
|
|
165
|
-
this._theme = options.theme ?? DEFAULT_TERMINAL_THEME;
|
|
166
|
-
}
|
|
167
|
-
// [LAW:single-enforcer] One place owns "is this index valid for the
|
|
168
|
-
// current options". selectedIndex and options are public observables —
|
|
169
|
-
// external code can move them out of range. Anywhere internal code
|
|
170
|
-
// seeds highlightedIndex from selectedIndex (constructor + every
|
|
171
|
-
// expand path), route through this clamp so the seeded value is
|
|
172
|
-
// always a legal option index (or 0 for empty options).
|
|
173
|
-
clampIndex(n) {
|
|
174
|
-
if (this.options.length === 0)
|
|
175
|
-
return 0;
|
|
176
|
-
return Math.max(0, Math.min(n, this.options.length - 1));
|
|
177
|
-
}
|
|
178
|
-
setTheme(theme) { this._theme = theme; }
|
|
179
|
-
// --- Event handlers ---
|
|
180
|
-
handleKey(event) {
|
|
181
|
-
if (this.disabled)
|
|
182
|
-
return;
|
|
183
|
-
const isPrintable = event.character.length === 1 &&
|
|
184
|
-
!event.ctrl &&
|
|
185
|
-
!event.meta &&
|
|
186
|
-
event.character >= " " &&
|
|
187
|
-
event.character !== "\x7f";
|
|
188
|
-
if (!this.expanded) {
|
|
189
|
-
// enter/space remain the explicit "open with selection seeded" gesture.
|
|
190
|
-
if (event.key === "enter" || event.key === "space") {
|
|
191
|
-
this.expanded = true;
|
|
192
|
-
this.highlightedIndex = this.clampIndex(this.selectedIndex);
|
|
193
|
-
event.stop();
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
// Any other printable char auto-expands and starts filtering.
|
|
197
|
-
if (isPrintable) {
|
|
198
|
-
this.expanded = true;
|
|
199
|
-
this.filter = this.filter + event.character;
|
|
200
|
-
this.highlightedIndex = 0;
|
|
201
|
-
event.stop();
|
|
202
|
-
}
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
// expanded
|
|
206
|
-
switch (event.key) {
|
|
207
|
-
case "up":
|
|
208
|
-
this.highlightedIndex = Math.max(0, this.highlightedIndex - 1);
|
|
209
|
-
event.stop();
|
|
210
|
-
return;
|
|
211
|
-
case "down": {
|
|
212
|
-
const max = Math.max(0, this.filteredOptions.length - 1);
|
|
213
|
-
this.highlightedIndex = Math.min(max, this.highlightedIndex + 1);
|
|
214
|
-
event.stop();
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
case "enter": {
|
|
218
|
-
// [LAW:one-source-of-truth] Map filtered position → canonical idx.
|
|
219
|
-
// No-op when filteredOptions is empty (highlighted is undefined).
|
|
220
|
-
const picked = this.filteredOptions[this.highlightedIndex];
|
|
221
|
-
event.stop();
|
|
222
|
-
if (picked === undefined)
|
|
223
|
-
return;
|
|
224
|
-
this.selectedIndex = picked.idx;
|
|
225
|
-
this.filter = "";
|
|
226
|
-
this.expanded = false;
|
|
227
|
-
this.emitChange();
|
|
228
|
-
this.emitSubmit();
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
case "escape":
|
|
232
|
-
// Single-step: clear filter + collapse.
|
|
233
|
-
this.filter = "";
|
|
234
|
-
this.expanded = false;
|
|
235
|
-
event.stop();
|
|
236
|
-
return;
|
|
237
|
-
case "backspace":
|
|
238
|
-
// [LAW:dataflow-not-control-flow] slice(0,-1) on "" is "" — no guard.
|
|
239
|
-
this.filter = this.filter.slice(0, -1);
|
|
240
|
-
this.highlightedIndex = 0;
|
|
241
|
-
event.stop();
|
|
242
|
-
return;
|
|
243
|
-
case "tab":
|
|
244
|
-
// First Tab while expanded cancels (clear filter + collapse) and
|
|
245
|
-
// KEEPS focus on the dropdown. event.stop() halts the chain so
|
|
246
|
-
// FocusManager's normal-priority Tab handler never runs — focus
|
|
247
|
-
// traversal only happens on the *next* Tab press, when this widget
|
|
248
|
-
// is collapsed and no longer stops the event. Shift direction is
|
|
249
|
-
// ignored: both Tab and Shift+Tab cancel.
|
|
250
|
-
this.filter = "";
|
|
251
|
-
this.expanded = false;
|
|
252
|
-
event.stop();
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
if (isPrintable) {
|
|
256
|
-
this.filter = this.filter + event.character;
|
|
257
|
-
this.highlightedIndex = 0;
|
|
258
|
-
event.stop();
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
handleMouse(event) {
|
|
262
|
-
if (this.disabled)
|
|
263
|
-
return;
|
|
264
|
-
if (event.type !== "mouse_up")
|
|
265
|
-
return;
|
|
266
|
-
const b = this.bounds;
|
|
267
|
-
if (!b)
|
|
268
|
-
return;
|
|
269
|
-
const inside = event.x >= b.x &&
|
|
270
|
-
event.x < b.x + b.width &&
|
|
271
|
-
event.y >= b.y &&
|
|
272
|
-
event.y < b.y + b.height;
|
|
273
|
-
if (!this.expanded) {
|
|
274
|
-
if (inside) {
|
|
275
|
-
this.expanded = true;
|
|
276
|
-
this.highlightedIndex = this.clampIndex(this.selectedIndex);
|
|
277
|
-
}
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
// expanded: row 0 is the header; rows 1..M are filtered options.
|
|
281
|
-
if (!inside) {
|
|
282
|
-
// Click outside cancels — clear filter + collapse.
|
|
283
|
-
this.filter = "";
|
|
284
|
-
this.expanded = false;
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
const rowOffset = event.y - b.y;
|
|
288
|
-
if (rowOffset === 0) {
|
|
289
|
-
// Click the header → collapse + clear filter, no selection change.
|
|
290
|
-
this.filter = "";
|
|
291
|
-
this.expanded = false;
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
// [LAW:one-source-of-truth] Filtered position → canonical idx.
|
|
295
|
-
const picked = this.filteredOptions[rowOffset - 1];
|
|
296
|
-
if (picked === undefined)
|
|
297
|
-
return;
|
|
298
|
-
this.selectedIndex = picked.idx;
|
|
299
|
-
this.filter = "";
|
|
300
|
-
this.expanded = false;
|
|
301
|
-
this.emitChange();
|
|
302
|
-
this.emitSubmit();
|
|
303
|
-
}
|
|
304
|
-
// [LAW:single-enforcer] FocusManager is the single dispatcher of focus
|
|
305
|
-
// transitions; overriding handleFocus here catches every blur path (Tab
|
|
306
|
-
// cycling, programmatic focus, unregister) so the overlay cannot outlive
|
|
307
|
-
// the widget's focus. Collapse is the data; no separate "should I close"
|
|
308
|
-
// branch lives at the router or elsewhere.
|
|
309
|
-
handleFocus(event) {
|
|
310
|
-
super.handleFocus(event);
|
|
311
|
-
if (event.type === "blur") {
|
|
312
|
-
this.filter = "";
|
|
313
|
-
this.expanded = false;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
// --- Rendering ---
|
|
317
|
-
render(options) {
|
|
318
|
-
// Header only — the inline footprint that flow layout sees. Always
|
|
319
|
-
// 1 row regardless of `expanded`/`filter`. Option rows live in
|
|
320
|
-
// renderOverlay. Width invariant: maxLabelLen + 4.
|
|
321
|
-
const arrowChar = options.asciiOnly ? "v" : "▾";
|
|
322
|
-
// [LAW:one-source-of-truth] render() owns the asciiOnly switch; the
|
|
323
|
-
// caret character flows into headerText as data rather than headerText
|
|
324
|
-
// re-reading the option flag. Same arrow / caret resolution path.
|
|
325
|
-
const caret = this.focused ? (options.asciiOnly ? "|" : "│") : "";
|
|
326
|
-
const maxLabelLen = this.maxLabelLen();
|
|
327
|
-
const baseStyle = this.disabled
|
|
328
|
-
? new Style({ color: "#666666", bgcolor: "#333333", dim: true })
|
|
329
|
-
: new Style({
|
|
330
|
-
color: this.resolvePalette("foreground"),
|
|
331
|
-
bgcolor: this.resolvePalette("surface"),
|
|
332
|
-
underline: this.focused,
|
|
333
|
-
});
|
|
334
|
-
const headerLabel = this.headerText(maxLabelLen, caret);
|
|
335
|
-
return [
|
|
336
|
-
new Segment("[", baseStyle),
|
|
337
|
-
new Segment(`${headerLabel} ${arrowChar}`, baseStyle),
|
|
338
|
-
new Segment("]", baseStyle),
|
|
339
|
-
];
|
|
340
|
-
}
|
|
341
|
-
// [LAW:dataflow-not-control-flow] One header text function; the data
|
|
342
|
-
// (filter empty vs not) chooses between selected-label and query+cursor.
|
|
343
|
-
// Both branches yield exactly maxLabelLen cells — width invariant.
|
|
344
|
-
// Selected label is centered; filter input is left-aligned so the caret
|
|
345
|
-
// doesn't jitter horizontally while typing.
|
|
346
|
-
//
|
|
347
|
-
// [LAW:one-source-of-truth] All width math is in terminal cells via
|
|
348
|
-
// cellLen / setCellSize / splitText — plain `.length` would miscount
|
|
349
|
-
// wide / emoji characters and break the width invariant.
|
|
350
|
-
headerText(maxLabelLen, caret) {
|
|
351
|
-
if (this.filter === "") {
|
|
352
|
-
const raw = this.options[this.selectedIndex] ?? "";
|
|
353
|
-
const w = cellLen(raw);
|
|
354
|
-
if (w >= maxLabelLen)
|
|
355
|
-
return setCellSize(raw, asCellCol(maxLabelLen));
|
|
356
|
-
const pad = maxLabelLen - w;
|
|
357
|
-
const left = Math.floor(pad / 2);
|
|
358
|
-
const right = pad - left;
|
|
359
|
-
return " ".repeat(left) + raw + " ".repeat(right);
|
|
360
|
-
}
|
|
361
|
-
// Leading space gives the filter input a small gutter from the [ chrome,
|
|
362
|
-
// matching the visual breathing room of the centered label.
|
|
363
|
-
const filterRoom = Math.max(0, maxLabelLen - cellLen(caret) - 1);
|
|
364
|
-
const [filterText] = splitText(this.filter, asCellCol(filterRoom));
|
|
365
|
-
return setCellSize(" " + filterText + caret, asCellCol(maxLabelLen));
|
|
366
|
-
}
|
|
367
|
-
renderOverlay(_options) {
|
|
368
|
-
if (!this.expanded)
|
|
369
|
-
return null;
|
|
370
|
-
const maxLabelLen = this.maxLabelLen();
|
|
371
|
-
const fopts = this.filteredOptions;
|
|
372
|
-
if (fopts.length === 0)
|
|
373
|
-
return this.renderNoMatchRow(maxLabelLen);
|
|
374
|
-
const segments = [];
|
|
375
|
-
for (let i = 0; i < fopts.length; i++) {
|
|
376
|
-
if (i > 0)
|
|
377
|
-
segments.push(new Segment("\n"));
|
|
378
|
-
segments.push(...this.renderOptionRow(fopts[i], i, maxLabelLen));
|
|
379
|
-
}
|
|
380
|
-
return segments;
|
|
381
|
-
}
|
|
382
|
-
renderNoMatchRow(maxLabelLen) {
|
|
383
|
-
// Right-clipped + padded so width matches a normal option row.
|
|
384
|
-
const text = setCellSize("(no matches)", asCellCol(maxLabelLen));
|
|
385
|
-
const inner = ` ${text} `;
|
|
386
|
-
const style = this.disabled
|
|
387
|
-
? new Style({ color: "#666666", bgcolor: "#333333", dim: true })
|
|
388
|
-
: new Style({
|
|
389
|
-
color: this.resolvePalette("foreground"),
|
|
390
|
-
bgcolor: this.resolvePalette("surface"),
|
|
391
|
-
dim: true,
|
|
392
|
-
});
|
|
393
|
-
return [
|
|
394
|
-
new Segment(" ", style),
|
|
395
|
-
new Segment(inner, style),
|
|
396
|
-
new Segment(" ", style),
|
|
397
|
-
];
|
|
398
|
-
}
|
|
399
|
-
renderOptionRow(entry, fpos, maxLabelLen) {
|
|
400
|
-
// Row: " " + label.padEnd(maxLabelLen) + " "
|
|
401
|
-
// Width = 1 + 1 + maxLabelLen + 1 + 1 = maxLabelLen + 4.
|
|
402
|
-
// Overlay rows are unbracketed — selection/highlight are conveyed by bg
|
|
403
|
-
// color; the header keeps [ ] as its focus-indicator chrome.
|
|
404
|
-
const label = setCellSize(entry.label, asCellCol(maxLabelLen));
|
|
405
|
-
const inner = ` ${label} `;
|
|
406
|
-
const isSelected = entry.idx === this.selectedIndex;
|
|
407
|
-
const isHighlighted = fpos === this.highlightedIndex;
|
|
408
|
-
// Highlighted: muted bg → text-primary (mostly-accent fg) is readable.
|
|
409
|
-
// Selected: full primary bg → on-primary (WCAG contrast) for fg.
|
|
410
|
-
const rowStyle = this.disabled
|
|
411
|
-
? new Style({ color: "#666666", bgcolor: "#333333", dim: true })
|
|
412
|
-
: isHighlighted
|
|
413
|
-
? new Style({
|
|
414
|
-
color: this.resolvePalette("text-primary"),
|
|
415
|
-
bgcolor: this.resolvePalette("primary-muted"),
|
|
416
|
-
})
|
|
417
|
-
: isSelected
|
|
418
|
-
? new Style({
|
|
419
|
-
color: this.resolvePalette("on-primary"),
|
|
420
|
-
bgcolor: this.resolvePalette("primary"),
|
|
421
|
-
})
|
|
422
|
-
: new Style({
|
|
423
|
-
color: this.resolvePalette("foreground"),
|
|
424
|
-
bgcolor: this.resolvePalette("surface"),
|
|
425
|
-
});
|
|
426
|
-
return [
|
|
427
|
-
new Segment(" ", rowStyle),
|
|
428
|
-
new Segment(inner, rowStyle),
|
|
429
|
-
new Segment(" ", rowStyle),
|
|
430
|
-
];
|
|
431
|
-
}
|
|
432
|
-
maxLabelLen() {
|
|
433
|
-
let m = 0;
|
|
434
|
-
for (const label of this.options) {
|
|
435
|
-
const w = cellLen(label);
|
|
436
|
-
if (w > m)
|
|
437
|
-
m = w;
|
|
438
|
-
}
|
|
439
|
-
return m;
|
|
440
|
-
}
|
|
441
|
-
measure(_options) {
|
|
442
|
-
const width = this.maxLabelLen() + 4;
|
|
443
|
-
return { minimum: width, maximum: width };
|
|
444
|
-
}
|
|
445
|
-
// --- Palette resolution ---
|
|
446
|
-
resolvePalette(key) {
|
|
447
|
-
const rgba = this._theme.palette.get(key);
|
|
448
|
-
// [LAW:no-defensive-null-guards] palette must contain all keys.
|
|
449
|
-
return ColorSpec.fromRgba(rgba);
|
|
450
|
-
}
|
|
451
|
-
};
|
|
452
|
-
})();
|
|
453
|
-
export { Dropdown };
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EventRouter — parses raw stdin into KeyEvent / WidgetMouseEvent values
|
|
3
|
-
* and dispatches them to widgets via the Screen's FocusManager and bounds.
|
|
4
|
-
*
|
|
5
|
-
* [LAW:single-enforcer] ANSI escape parsing lives only here — widgets never
|
|
6
|
-
* see raw bytes. One parser, one dispatch surface.
|
|
7
|
-
*
|
|
8
|
-
* [LAW:dataflow-not-control-flow] The same parse pipeline runs for every
|
|
9
|
-
* input chunk: append → consume one event at a time until the buffer can't
|
|
10
|
-
* yield more. The byte's value, not a side-mode, decides which event is
|
|
11
|
-
* emitted. There is no "are we mid-sequence" mode flag — the buffer head
|
|
12
|
-
* carries all the state we need.
|
|
13
|
-
*
|
|
14
|
-
* Lone-ESC handling: a bare `\x1b` is ambiguous (escape key vs. start of a
|
|
15
|
-
* CSI sequence). When the buffer is drained but ends with a lone ESC, the
|
|
16
|
-
* router defers emission via setTimeout(0). The next chunk cancels the timer
|
|
17
|
-
* if it extends the sequence. Tests can call `flush()` to drain synchronously.
|
|
18
|
-
*/
|
|
19
|
-
import { KeyEvent } from "./types.js";
|
|
20
|
-
import type { Screen, FocusManager, InteractiveWidget, WidgetMouseEvent, KeyHandlerOptions } from "./types.js";
|
|
21
|
-
import type { Unsubscribe } from "../core/subscription.js";
|
|
22
|
-
import type { TerminalHost } from "../host/terminal-host.js";
|
|
23
|
-
type KeyHandler = (event: KeyEvent) => void;
|
|
24
|
-
type WidgetSource = {
|
|
25
|
-
focusManager: FocusManager;
|
|
26
|
-
getWidgets: () => readonly InteractiveWidget[];
|
|
27
|
-
};
|
|
28
|
-
export interface EventRouterOptions {
|
|
29
|
-
screen: Screen | WidgetSource;
|
|
30
|
-
/**
|
|
31
|
-
* The I/O capability the router reads input from and writes
|
|
32
|
-
* mouse-tracking sequences to. Construct a `NodeTerminalHost` for
|
|
33
|
-
* production node demos; tests pass a host that wraps mock streams.
|
|
34
|
-
*/
|
|
35
|
-
host: TerminalHost;
|
|
36
|
-
/**
|
|
37
|
-
* When true (default when the host reports `isTTY`), enable mouse
|
|
38
|
-
* tracking on `start()` and disable on `stop()`. Disable in tests /
|
|
39
|
-
* non-TTY environments.
|
|
40
|
-
*/
|
|
41
|
-
manageMouse?: boolean;
|
|
42
|
-
/**
|
|
43
|
-
* When true (default when the host reports `isTTY`), switch the host
|
|
44
|
-
* into raw mode on `start()` and restore on `stop()`.
|
|
45
|
-
*/
|
|
46
|
-
manageRawMode?: boolean;
|
|
47
|
-
}
|
|
48
|
-
export declare class EventRouter {
|
|
49
|
-
private readonly source;
|
|
50
|
-
private readonly host;
|
|
51
|
-
private readonly manageMouse;
|
|
52
|
-
private readonly manageRawMode;
|
|
53
|
-
private buffer;
|
|
54
|
-
private running;
|
|
55
|
-
private dataUnsubscribe;
|
|
56
|
-
private escTimer;
|
|
57
|
-
private capturedWidget;
|
|
58
|
-
private readonly keyChain;
|
|
59
|
-
private readonly mouseHandlers;
|
|
60
|
-
constructor(options: EventRouterOptions);
|
|
61
|
-
start(): void;
|
|
62
|
-
stop(): void;
|
|
63
|
-
onKey(handler: KeyHandler, options?: KeyHandlerOptions): Unsubscribe;
|
|
64
|
-
onMouse(handler: (event: WidgetMouseEvent) => void): Unsubscribe;
|
|
65
|
-
/** Feed a chunk of bytes (or a string of bytes) into the parser. */
|
|
66
|
-
feed(chunk: Uint8Array | string): void;
|
|
67
|
-
/** Force any pending lone ESC out of the buffer. */
|
|
68
|
-
flush(): void;
|
|
69
|
-
private drain;
|
|
70
|
-
private consumeOne;
|
|
71
|
-
private consumePrintable;
|
|
72
|
-
private consumeEscape;
|
|
73
|
-
private consumeSS3;
|
|
74
|
-
private consumeCSI;
|
|
75
|
-
private dispatchKey;
|
|
76
|
-
private dispatchMouse;
|
|
77
|
-
}
|
|
78
|
-
export {};
|