@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,260 @@
|
|
|
1
|
+
import { createInstance, expandTemplate, findFunction, markDirty, normalizeChildren, unmountInstance, updateProps, walkInstances, } from './instance.js';
|
|
2
|
+
import { isTemplateChildren } from './bindings.js';
|
|
3
|
+
import { setCurrentInstance } from './hooks.js';
|
|
4
|
+
const MAX_DEPTH = 200;
|
|
5
|
+
export function renderTree(runtime, root, node, options = {}) {
|
|
6
|
+
const instance = reconcile(runtime, null, root ?? undefined, node, options, 0, undefined);
|
|
7
|
+
return instance;
|
|
8
|
+
}
|
|
9
|
+
/** Match by component name and key; anything else is a fresh mount. */
|
|
10
|
+
function canReuse(existing, node) {
|
|
11
|
+
if (!existing)
|
|
12
|
+
return false;
|
|
13
|
+
if (existing.component !== node.component)
|
|
14
|
+
return false;
|
|
15
|
+
return existing.key === node.key;
|
|
16
|
+
}
|
|
17
|
+
export function reconcile(runtime, parent, existing, node, options, depth, dataContext) {
|
|
18
|
+
if (depth > MAX_DEPTH) {
|
|
19
|
+
throw new Error(`[textui] component tree exceeded ${MAX_DEPTH} levels at <${node.component}> - ` +
|
|
20
|
+
'a component is almost certainly rendering itself');
|
|
21
|
+
}
|
|
22
|
+
let instance;
|
|
23
|
+
if (canReuse(existing, node)) {
|
|
24
|
+
instance = existing;
|
|
25
|
+
instance.parent = parent;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
if (existing)
|
|
29
|
+
unmountInstance(existing);
|
|
30
|
+
instance = createInstance(runtime, node, parent);
|
|
31
|
+
}
|
|
32
|
+
// The data context has to be in place before props resolve, or a relative
|
|
33
|
+
// path in a templated row has nothing to resolve against.
|
|
34
|
+
instance.dataContext = dataContext ?? parent?.dataContext;
|
|
35
|
+
const resolved = findFunction(runtime, node);
|
|
36
|
+
instance.kind = resolved.kind;
|
|
37
|
+
instance.fn = resolved.fn;
|
|
38
|
+
instance.host = resolved.host;
|
|
39
|
+
instance.definition = resolved.definition;
|
|
40
|
+
const previousProps = instance.props;
|
|
41
|
+
updateProps(instance, node);
|
|
42
|
+
const propsChanged = !shallowEqual(previousProps, instance.props);
|
|
43
|
+
const mustRender = options.force || instance.dirty || !instance.mounted || propsChanged;
|
|
44
|
+
if (!mustRender) {
|
|
45
|
+
// Clean itself, but something below changed: reconcile the children it
|
|
46
|
+
// produced last time rather than re-running this component.
|
|
47
|
+
if (instance.childDirty) {
|
|
48
|
+
instance.childDirty = false;
|
|
49
|
+
reconcileChildren(runtime, instance, instance.lastChildren, options, depth);
|
|
50
|
+
}
|
|
51
|
+
return instance;
|
|
52
|
+
}
|
|
53
|
+
if (options.diagnostics && !instance.renderReason) {
|
|
54
|
+
instance.renderReason = !instance.mounted ? 'mount' : propsChanged ? 'props' : 'dirty';
|
|
55
|
+
}
|
|
56
|
+
instance.dirty = false;
|
|
57
|
+
instance.childDirty = false;
|
|
58
|
+
instance.mounted = true;
|
|
59
|
+
instance.error = undefined;
|
|
60
|
+
subscribeToReads(instance);
|
|
61
|
+
const childNodes = renderToNodes(runtime, instance, options);
|
|
62
|
+
instance.lastChildren = childNodes;
|
|
63
|
+
reconcileChildren(runtime, instance, childNodes, options, depth);
|
|
64
|
+
if (!options.diagnostics)
|
|
65
|
+
instance.renderReason = undefined;
|
|
66
|
+
return instance;
|
|
67
|
+
}
|
|
68
|
+
/** Produce this instance's child nodes, whatever kind it is. */
|
|
69
|
+
function renderToNodes(runtime, instance, options) {
|
|
70
|
+
const childrenProp = instance.props.children;
|
|
71
|
+
const wrap = (nodes) => nodes.map((node) => ({ node }));
|
|
72
|
+
switch (instance.kind) {
|
|
73
|
+
case 'function': {
|
|
74
|
+
const fn = instance.fn;
|
|
75
|
+
if (!fn)
|
|
76
|
+
return [];
|
|
77
|
+
setCurrentInstance(instance);
|
|
78
|
+
try {
|
|
79
|
+
const output = fn(instance.props);
|
|
80
|
+
return wrap(normalizeChildren(output));
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
instance.error = err;
|
|
84
|
+
runtime.onError(err, `render of <${instance.component}>`);
|
|
85
|
+
return wrap(normalizeChildren(fallbackFor(instance, err)));
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
setCurrentInstance(null);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
case 'template': {
|
|
92
|
+
const template = instance.definition?.renderer.kind === 'template'
|
|
93
|
+
? instance.definition.renderer.template
|
|
94
|
+
: null;
|
|
95
|
+
if (!template)
|
|
96
|
+
return [];
|
|
97
|
+
// The template receives this node's props as its data context.
|
|
98
|
+
return wrap([{ ...template, ...stripStructural(instance.props) }]);
|
|
99
|
+
}
|
|
100
|
+
case 'host': {
|
|
101
|
+
if (isTemplateChildren(childrenProp)) {
|
|
102
|
+
const expanded = expandTemplate(runtime, childrenProp, instance.dataContext);
|
|
103
|
+
return expanded.nodes.map((node, i) => ({
|
|
104
|
+
node,
|
|
105
|
+
dataContext: expanded.contexts[i],
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
return wrap(normalizeChildren(childrenProp));
|
|
109
|
+
}
|
|
110
|
+
case 'missing': {
|
|
111
|
+
resolveLazy(runtime, instance);
|
|
112
|
+
return wrap(normalizeChildren(missingNode(instance, options)));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function stripStructural(props) {
|
|
117
|
+
const { children: _children, ...rest } = props;
|
|
118
|
+
return rest;
|
|
119
|
+
}
|
|
120
|
+
/** A lazily-registered component resolves in the background and re-renders. */
|
|
121
|
+
function resolveLazy(runtime, instance) {
|
|
122
|
+
const def = instance.definition;
|
|
123
|
+
if (!def || def.renderer.kind !== 'lazy')
|
|
124
|
+
return;
|
|
125
|
+
void runtime.components
|
|
126
|
+
.resolve(instance.component)
|
|
127
|
+
.then(() => {
|
|
128
|
+
markDirty(instance, 'lazy component resolved');
|
|
129
|
+
runtime.requestRender();
|
|
130
|
+
})
|
|
131
|
+
.catch((err) => runtime.onError(err, `resolving <${instance.component}>`));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A name that was never registered is a runtime miss, not a compile error -
|
|
135
|
+
* that is the price of the graph being data, so the miss has to be visible
|
|
136
|
+
* rather than silent.
|
|
137
|
+
*/
|
|
138
|
+
function missingNode(instance, options) {
|
|
139
|
+
if (instance.definition?.renderer.kind === 'lazy') {
|
|
140
|
+
return { component: 'text', content: '', dim: true };
|
|
141
|
+
}
|
|
142
|
+
const fallback = instance.node.$meta?.fallback;
|
|
143
|
+
if (fallback) {
|
|
144
|
+
// A miss is not a throw, but it is the same question - what does this
|
|
145
|
+
// render instead - so a fallback answers both.
|
|
146
|
+
const message = `no component registered as "${instance.component}"`;
|
|
147
|
+
return applyFallback(fallback, {
|
|
148
|
+
error: new Error(message), message, component: instance.component,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
component: 'text',
|
|
153
|
+
content: options.diagnostics
|
|
154
|
+
? `<${instance.component}?>`
|
|
155
|
+
: `<${instance.component}>`,
|
|
156
|
+
fg: 'danger',
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Render a declared fallback.
|
|
161
|
+
*
|
|
162
|
+
* A function fallback is handed the failure. A node fallback is handed it as
|
|
163
|
+
* the `error` and `errorMessage` props, so the graph stays data and a
|
|
164
|
+
* registered component can render the failure without the registry holding a
|
|
165
|
+
* function. Props the fallback declares itself win.
|
|
166
|
+
*/
|
|
167
|
+
function applyFallback(fallback, failure) {
|
|
168
|
+
if (typeof fallback === 'function')
|
|
169
|
+
return fallback(failure);
|
|
170
|
+
return { error: failure.error, errorMessage: failure.message, ...fallback };
|
|
171
|
+
}
|
|
172
|
+
function fallbackFor(instance, err) {
|
|
173
|
+
const fallback = instance.node.$meta?.fallback ?? instance.definition?.fallback;
|
|
174
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
175
|
+
if (fallback) {
|
|
176
|
+
return applyFallback(fallback, { error: err, message, component: instance.component });
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
component: 'text',
|
|
180
|
+
content: `${instance.component}: ${message}`,
|
|
181
|
+
fg: 'danger',
|
|
182
|
+
wrap: 'word',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Subscribe to exactly the store paths this instance's props read. Nothing
|
|
187
|
+
* else re-renders it, which is what keeps a hundred-row list from re-rendering
|
|
188
|
+
* because an unrelated counter moved.
|
|
189
|
+
*/
|
|
190
|
+
function subscribeToReads(instance) {
|
|
191
|
+
for (const sub of instance.subscriptions)
|
|
192
|
+
sub.dispose();
|
|
193
|
+
instance.subscriptions = [];
|
|
194
|
+
if (instance.reads.size === 0)
|
|
195
|
+
return;
|
|
196
|
+
for (const path of instance.reads) {
|
|
197
|
+
instance.subscriptions.push(instance.runtime.store.subscribe(path, () => {
|
|
198
|
+
markDirty(instance, `store ${path}`);
|
|
199
|
+
instance.runtime.requestRender();
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function reconcileChildren(runtime, instance, childNodes, options, depth) {
|
|
204
|
+
const previous = instance.children;
|
|
205
|
+
const byKey = new Map();
|
|
206
|
+
for (let i = 0; i < previous.length; i++) {
|
|
207
|
+
const child = previous[i];
|
|
208
|
+
byKey.set(child.key ?? `__index_${i}`, child);
|
|
209
|
+
}
|
|
210
|
+
const next = [];
|
|
211
|
+
const used = new Set();
|
|
212
|
+
for (let i = 0; i < childNodes.length; i++) {
|
|
213
|
+
const entry = childNodes[i];
|
|
214
|
+
const key = entry.node.key ?? `__index_${i}`;
|
|
215
|
+
const candidate = byKey.get(key);
|
|
216
|
+
const reusable = candidate && !used.has(candidate) ? candidate : undefined;
|
|
217
|
+
const child = reconcile(runtime, instance, reusable, entry.node, options, depth + 1, entry.dataContext ?? instance.dataContext);
|
|
218
|
+
if (reusable)
|
|
219
|
+
used.add(reusable);
|
|
220
|
+
next.push(child);
|
|
221
|
+
}
|
|
222
|
+
/*
|
|
223
|
+
* `next` as a set, because `includes` inside a loop over `previous` is
|
|
224
|
+
* quadratic - fine for a row of buttons, not for a list with a thousand
|
|
225
|
+
* children, which is exactly where a reconciler has to stay cheap.
|
|
226
|
+
*
|
|
227
|
+
* Built only here, where the answer is needed, rather than alongside `used`.
|
|
228
|
+
*/
|
|
229
|
+
const kept = new Set(next);
|
|
230
|
+
for (const child of previous) {
|
|
231
|
+
if (!used.has(child) && !kept.has(child))
|
|
232
|
+
unmountInstance(child);
|
|
233
|
+
}
|
|
234
|
+
instance.children = next;
|
|
235
|
+
}
|
|
236
|
+
function shallowEqual(a, b) {
|
|
237
|
+
const aKeys = Object.keys(a);
|
|
238
|
+
const bKeys = Object.keys(b);
|
|
239
|
+
if (aKeys.length !== bKeys.length)
|
|
240
|
+
return false;
|
|
241
|
+
for (const key of aKeys) {
|
|
242
|
+
if (!Object.is(a[key], b[key]))
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
/** Every effect queued during the last render, parents last. */
|
|
248
|
+
export function collectEffects(root) {
|
|
249
|
+
const effects = [];
|
|
250
|
+
walkInstances(root, (instance) => {
|
|
251
|
+
if (instance.pendingEffects.length === 0)
|
|
252
|
+
return;
|
|
253
|
+
effects.push(...instance.pendingEffects);
|
|
254
|
+
instance.pendingEffects = [];
|
|
255
|
+
});
|
|
256
|
+
return effects;
|
|
257
|
+
}
|
|
258
|
+
export function disposeTree(root) {
|
|
259
|
+
unmountInstance(root);
|
|
260
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ReactiveStore, EventBus } from '../types/store.js';
|
|
2
|
+
import type { ComponentRegistry } from '../types/component-registry.js';
|
|
3
|
+
import type { ResolvedTheme } from '../types/theme.js';
|
|
4
|
+
import type { TerminalCapabilities } from '../types/capabilities.js';
|
|
5
|
+
import type { Size } from '../types/geometry.js';
|
|
6
|
+
import type { ServiceContainer } from '../types/services.js';
|
|
7
|
+
import type { FocusManager } from '../types/focus.js';
|
|
8
|
+
import type { LayerManager } from '../types/layer.js';
|
|
9
|
+
import type { AnimationDriver } from '../types/animation.js';
|
|
10
|
+
import type { I18n } from '../types/i18n.js';
|
|
11
|
+
import type { WhenEngine } from '../types/when.js';
|
|
12
|
+
import type { TextUIApp } from '../types/app.js';
|
|
13
|
+
/**
|
|
14
|
+
* What the reconciler and the hooks are allowed to reach.
|
|
15
|
+
*
|
|
16
|
+
* Narrower than the app on purpose: the render tree should depend on the few
|
|
17
|
+
* things it actually uses, so a component can be rendered by the test harness
|
|
18
|
+
* or the static renderer without an application, a terminal or a frame loop.
|
|
19
|
+
*/
|
|
20
|
+
export interface Runtime {
|
|
21
|
+
store: ReactiveStore;
|
|
22
|
+
events: EventBus;
|
|
23
|
+
when: WhenEngine;
|
|
24
|
+
components: ComponentRegistry;
|
|
25
|
+
services: ServiceContainer;
|
|
26
|
+
focus: FocusManager;
|
|
27
|
+
layers: LayerManager;
|
|
28
|
+
animation: AnimationDriver;
|
|
29
|
+
i18n: I18n;
|
|
30
|
+
theme(): ResolvedTheme;
|
|
31
|
+
capabilities(): TerminalCapabilities;
|
|
32
|
+
size(): Size;
|
|
33
|
+
execute(id: string, args?: Record<string, unknown>): unknown;
|
|
34
|
+
emit(path: string, payload?: unknown): void;
|
|
35
|
+
/** Ask for another frame. Coalesced by the scheduler. */
|
|
36
|
+
requestRender(): void;
|
|
37
|
+
/** The application, when there is one. Null under the static renderer. */
|
|
38
|
+
app(): TextUIApp | null;
|
|
39
|
+
/** Where render-time errors go. */
|
|
40
|
+
onError(err: unknown, context: string): void;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,QAAQ,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,iBAAiB,CAAC;IAC9B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,YAAY,CAAC;IACrB,SAAS,EAAE,eAAe,CAAC;IAC3B,IAAI,EAAE,IAAI,CAAC;IAEX,KAAK,IAAI,aAAa,CAAC;IACvB,YAAY,IAAI,oBAAoB,CAAC;IACrC,IAAI,IAAI,IAAI,CAAC;IAEb,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;IAC7D,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAE5C,yDAAyD;IACzD,aAAa,IAAI,IAAI,CAAC;IAEtB,0EAA0E;IAC1E,GAAG,IAAI,SAAS,GAAG,IAAI,CAAC;IAExB,mCAAmC;IACnC,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Style, StyleInput, BorderSpec, BorderStyle, StyleColor } from '../types/style.js';
|
|
2
|
+
import type { ResolvedTheme } from '../types/theme.js';
|
|
3
|
+
import type { Edges } from '../types/geometry.js';
|
|
4
|
+
import type { Color } from '../types/cells.js';
|
|
5
|
+
import type { BorderChars } from '../types/style.js';
|
|
6
|
+
import { type PackedColor } from '../render/color.js';
|
|
7
|
+
/**
|
|
8
|
+
* Style resolution.
|
|
9
|
+
*
|
|
10
|
+
* Five sources, merged in one fixed order so the answer to "why is this blue"
|
|
11
|
+
* is always the same walk: the theme's entry for this component, the
|
|
12
|
+
* component's own default, convenience props written inline, the `style` prop,
|
|
13
|
+
* and finally the state overlay for focus, hover, active, selected, disabled.
|
|
14
|
+
*/
|
|
15
|
+
export declare const STYLE_KEYS: Set<string>;
|
|
16
|
+
export interface InteractionState {
|
|
17
|
+
focused: boolean;
|
|
18
|
+
hovered: boolean;
|
|
19
|
+
active: boolean;
|
|
20
|
+
selected: boolean;
|
|
21
|
+
disabled: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare const NO_INTERACTION: InteractionState;
|
|
24
|
+
export declare function mergeStyles(...styles: (Style | undefined)[]): Style;
|
|
25
|
+
/** Flatten a `style` prop, applying state overlays in a fixed order. */
|
|
26
|
+
export declare function flattenStyleInput(input: StyleInput | undefined, state: InteractionState): Style;
|
|
27
|
+
/** Pick the style keys written inline as convenience props. */
|
|
28
|
+
export declare function styleFromProps(props: Record<string, unknown>): Style;
|
|
29
|
+
export declare function resolveStyle(props: Record<string, unknown>, theme: ResolvedTheme, component: string, defaultStyle: Style | undefined, state: InteractionState): Style;
|
|
30
|
+
/** A token name, a literal colour, or nothing. */
|
|
31
|
+
export declare function resolveColor(value: StyleColor | undefined, theme: ResolvedTheme, fallback?: Color): Color;
|
|
32
|
+
export declare function packStyleColor(value: StyleColor | undefined, theme: ResolvedTheme, fallback?: Color): PackedColor;
|
|
33
|
+
export declare function attrsFromStyle(style: Style): number;
|
|
34
|
+
export interface ResolvedBorder {
|
|
35
|
+
style: BorderStyle;
|
|
36
|
+
chars: BorderChars;
|
|
37
|
+
color: StyleColor | undefined;
|
|
38
|
+
/** Per-edge overrides. Undefined here means "use `color`". */
|
|
39
|
+
colors: {
|
|
40
|
+
top?: StyleColor;
|
|
41
|
+
right?: StyleColor;
|
|
42
|
+
bottom?: StyleColor;
|
|
43
|
+
left?: StyleColor;
|
|
44
|
+
};
|
|
45
|
+
dim: boolean;
|
|
46
|
+
sides: {
|
|
47
|
+
top: boolean;
|
|
48
|
+
right: boolean;
|
|
49
|
+
bottom: boolean;
|
|
50
|
+
left: boolean;
|
|
51
|
+
};
|
|
52
|
+
edges: Edges;
|
|
53
|
+
}
|
|
54
|
+
export declare function resolveBorder(spec: BorderSpec | undefined, theme: ResolvedTheme): ResolvedBorder;
|
|
55
|
+
//# sourceMappingURL=style.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"style.d.ts","sourceRoot":"","sources":["../../src/runtime/style.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAiB,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/G,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKrD,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjE;;;;;;;GAOG;AAEH,eAAO,MAAM,UAAU,aAQrB,CAAC;AAEH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,eAAO,MAAM,cAAc,EAAE,gBAE5B,CAAC;AASF,wBAAgB,WAAW,CAAC,GAAG,MAAM,EAAE,CAAC,KAAK,GAAG,SAAS,CAAC,EAAE,GAAG,KAAK,CAOnE;AAED,wEAAwE;AACxE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,EAAE,KAAK,EAAE,gBAAgB,GAAG,KAAK,CAmB/F;AAED,+DAA+D;AAC/D,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,CAMpE;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,KAAK,EAAE,aAAa,EACpB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,KAAK,GAAG,SAAS,EAC/B,KAAK,EAAE,gBAAgB,GACtB,KAAK,CAYP;AAID,kDAAkD;AAClD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,UAAU,GAAG,SAAS,EAC7B,KAAK,EAAE,aAAa,EACpB,QAAQ,GAAE,KAAiB,GAC1B,KAAK,CAGP;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,UAAU,GAAG,SAAS,EAC7B,KAAK,EAAE,aAAa,EACpB,QAAQ,GAAE,KAAiB,GAC1B,WAAW,CAEb;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAUnD;AAID,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,UAAU,GAAG,SAAS,CAAC;IAC9B,8DAA8D;IAC9D,MAAM,EAAE;QAAE,GAAG,CAAC,EAAE,UAAU,CAAC;QAAC,KAAK,CAAC,EAAE,UAAU,CAAC;QAAC,MAAM,CAAC,EAAE,UAAU,CAAC;QAAC,IAAI,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;IACzF,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE;QAAE,GAAG,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC;IACxE,KAAK,EAAE,KAAK,CAAC;CACd;AAgBD,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,EAAE,KAAK,EAAE,aAAa,GAAG,cAAc,CAoChG"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { ATTR_BLINK, ATTR_BOLD, ATTR_DIM, ATTR_INVERSE, ATTR_ITALIC, ATTR_STRIKE, ATTR_UNDERLINE, } from '../types/cells.js';
|
|
2
|
+
import { packColor } from '../render/color.js';
|
|
3
|
+
/**
|
|
4
|
+
* Style resolution.
|
|
5
|
+
*
|
|
6
|
+
* Five sources, merged in one fixed order so the answer to "why is this blue"
|
|
7
|
+
* is always the same walk: the theme's entry for this component, the
|
|
8
|
+
* component's own default, convenience props written inline, the `style` prop,
|
|
9
|
+
* and finally the state overlay for focus, hover, active, selected, disabled.
|
|
10
|
+
*/
|
|
11
|
+
export const STYLE_KEYS = new Set([
|
|
12
|
+
'display', 'direction', 'gap', 'columnGap', 'rowGap', 'flexWrap', 'padding', 'margin',
|
|
13
|
+
'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight',
|
|
14
|
+
'flex', 'shrink', 'basis', 'align', 'alignSelf', 'justify',
|
|
15
|
+
'position', 'top', 'right', 'bottom', 'left', 'zIndex',
|
|
16
|
+
'overflow', 'overflowX', 'overflowY',
|
|
17
|
+
'fg', 'bg', 'bold', 'dim', 'italic', 'underline', 'inverse', 'strike', 'blink',
|
|
18
|
+
'border', 'wrap', 'textAlign', 'fill', 'scrim', 'scrimStrength',
|
|
19
|
+
]);
|
|
20
|
+
export const NO_INTERACTION = {
|
|
21
|
+
focused: false, hovered: false, active: false, selected: false, disabled: false,
|
|
22
|
+
};
|
|
23
|
+
function isStateful(value) {
|
|
24
|
+
return ('base' in value || 'focus' in value || 'hover' in value ||
|
|
25
|
+
'active' in value || 'selected' in value || 'disabled' in value);
|
|
26
|
+
}
|
|
27
|
+
export function mergeStyles(...styles) {
|
|
28
|
+
const out = {};
|
|
29
|
+
for (const style of styles) {
|
|
30
|
+
if (!style)
|
|
31
|
+
continue;
|
|
32
|
+
Object.assign(out, style);
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
/** Flatten a `style` prop, applying state overlays in a fixed order. */
|
|
37
|
+
export function flattenStyleInput(input, state) {
|
|
38
|
+
if (!input)
|
|
39
|
+
return {};
|
|
40
|
+
if (Array.isArray(input)) {
|
|
41
|
+
return mergeStyles(...input.map((item) => (item ? flattenStyleInput(item, state) : undefined)));
|
|
42
|
+
}
|
|
43
|
+
if (!isStateful(input))
|
|
44
|
+
return input;
|
|
45
|
+
// Order matters: selected loses to active, active loses to focus, and
|
|
46
|
+
// disabled wins over everything - a disabled control is not focusable.
|
|
47
|
+
return mergeStyles(input.base, state.selected ? input.selected : undefined, state.hovered ? input.hover : undefined, state.active ? input.active : undefined, state.focused ? input.focus : undefined, state.disabled ? input.disabled : undefined);
|
|
48
|
+
}
|
|
49
|
+
/** Pick the style keys written inline as convenience props. */
|
|
50
|
+
export function styleFromProps(props) {
|
|
51
|
+
const out = {};
|
|
52
|
+
for (const key of Object.keys(props)) {
|
|
53
|
+
if (STYLE_KEYS.has(key) && props[key] !== undefined)
|
|
54
|
+
out[key] = props[key];
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
export function resolveStyle(props, theme, component, defaultStyle, state) {
|
|
59
|
+
const variants = [];
|
|
60
|
+
if (typeof props.variant === 'string')
|
|
61
|
+
variants.push(props.variant);
|
|
62
|
+
if (typeof props.tone === 'string')
|
|
63
|
+
variants.push(props.tone);
|
|
64
|
+
if (typeof props.size === 'string')
|
|
65
|
+
variants.push(props.size);
|
|
66
|
+
return mergeStyles(theme.styleFor(component, variants), defaultStyle, styleFromProps(props), flattenStyleInput(props.style, state));
|
|
67
|
+
}
|
|
68
|
+
// ------------------------------------------------------------------ colour
|
|
69
|
+
/** A token name, a literal colour, or nothing. */
|
|
70
|
+
export function resolveColor(value, theme, fallback = 'default') {
|
|
71
|
+
if (value === undefined)
|
|
72
|
+
return fallback;
|
|
73
|
+
return theme.color(value);
|
|
74
|
+
}
|
|
75
|
+
export function packStyleColor(value, theme, fallback = 'default') {
|
|
76
|
+
return packColor(resolveColor(value, theme, fallback));
|
|
77
|
+
}
|
|
78
|
+
export function attrsFromStyle(style) {
|
|
79
|
+
let attrs = 0;
|
|
80
|
+
if (style.bold)
|
|
81
|
+
attrs |= ATTR_BOLD;
|
|
82
|
+
if (style.dim)
|
|
83
|
+
attrs |= ATTR_DIM;
|
|
84
|
+
if (style.italic)
|
|
85
|
+
attrs |= ATTR_ITALIC;
|
|
86
|
+
if (style.underline)
|
|
87
|
+
attrs |= ATTR_UNDERLINE;
|
|
88
|
+
if (style.inverse)
|
|
89
|
+
attrs |= ATTR_INVERSE;
|
|
90
|
+
if (style.strike)
|
|
91
|
+
attrs |= ATTR_STRIKE;
|
|
92
|
+
if (style.blink)
|
|
93
|
+
attrs |= ATTR_BLINK;
|
|
94
|
+
return attrs;
|
|
95
|
+
}
|
|
96
|
+
const NO_BORDER = {
|
|
97
|
+
style: 'none',
|
|
98
|
+
chars: {
|
|
99
|
+
topLeft: ' ', top: ' ', topRight: ' ', right: ' ', bottomRight: ' ',
|
|
100
|
+
bottom: ' ', bottomLeft: ' ', left: ' ', cross: ' ',
|
|
101
|
+
teeTop: ' ', teeBottom: ' ', teeLeft: ' ', teeRight: ' ',
|
|
102
|
+
},
|
|
103
|
+
color: undefined,
|
|
104
|
+
colors: {},
|
|
105
|
+
dim: false,
|
|
106
|
+
sides: { top: false, right: false, bottom: false, left: false },
|
|
107
|
+
edges: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
108
|
+
};
|
|
109
|
+
export function resolveBorder(spec, theme) {
|
|
110
|
+
if (spec === undefined)
|
|
111
|
+
return NO_BORDER;
|
|
112
|
+
const style = typeof spec === 'string' ? spec : spec.style ?? theme.border;
|
|
113
|
+
if (style === 'none')
|
|
114
|
+
return NO_BORDER;
|
|
115
|
+
// Naming any side means naming all of them. `sides: { left: true }` has to
|
|
116
|
+
// mean a left rule and nothing else - if unspecified sides defaulted to
|
|
117
|
+
// true, every panel divider in every shell would draw a full box instead.
|
|
118
|
+
const sidesSpec = typeof spec === 'string' ? undefined : spec.sides;
|
|
119
|
+
const sides = sidesSpec
|
|
120
|
+
? {
|
|
121
|
+
top: sidesSpec.top === true,
|
|
122
|
+
right: sidesSpec.right === true,
|
|
123
|
+
bottom: sidesSpec.bottom === true,
|
|
124
|
+
left: sidesSpec.left === true,
|
|
125
|
+
}
|
|
126
|
+
: { top: true, right: true, bottom: true, left: true };
|
|
127
|
+
const base = theme.borderChars(style);
|
|
128
|
+
const chars = typeof spec === 'string' || !spec.chars ? base : { ...base, ...spec.chars };
|
|
129
|
+
return {
|
|
130
|
+
style,
|
|
131
|
+
chars,
|
|
132
|
+
color: typeof spec === 'string' ? undefined : spec.color,
|
|
133
|
+
colors: typeof spec === 'string' ? {} : spec.colors ?? {},
|
|
134
|
+
dim: typeof spec !== 'string' && spec.dim === true,
|
|
135
|
+
sides,
|
|
136
|
+
edges: {
|
|
137
|
+
top: sides.top ? 1 : 0,
|
|
138
|
+
right: sides.right ? 1 : 0,
|
|
139
|
+
bottom: sides.bottom ? 1 : 0,
|
|
140
|
+
left: sides.left ? 1 : 0,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BorderChars, BorderStyle } from '../types/style.js';
|
|
2
|
+
/**
|
|
3
|
+
* Border glyph sets.
|
|
4
|
+
*
|
|
5
|
+
* `ascii` is not a fallback nobody sees - it is what an `unicode: 'ascii'`
|
|
6
|
+
* terminal actually gets, so it has to look deliberate rather than broken.
|
|
7
|
+
*/
|
|
8
|
+
export declare const BORDER_SETS: Record<BorderStyle, BorderChars>;
|
|
9
|
+
export declare function borderCharsFor(style: BorderStyle, unicode: 'ascii' | 'bmp' | 'full'): BorderChars;
|
|
10
|
+
//# sourceMappingURL=borders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"borders.d.ts","sourceRoot":"","sources":["../../src/themes/borders.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAElE;;;;;GAKG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,WAAW,CAuDxD,CAAC;AAqBF,wBAAgB,cAAc,CAC5B,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,OAAO,GAAG,KAAK,GAAG,MAAM,GAChC,WAAW,CAOb"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Border glyph sets.
|
|
3
|
+
*
|
|
4
|
+
* `ascii` is not a fallback nobody sees - it is what an `unicode: 'ascii'`
|
|
5
|
+
* terminal actually gets, so it has to look deliberate rather than broken.
|
|
6
|
+
*/
|
|
7
|
+
export const BORDER_SETS = {
|
|
8
|
+
none: {
|
|
9
|
+
topLeft: ' ', top: ' ', topRight: ' ',
|
|
10
|
+
right: ' ', bottomRight: ' ', bottom: ' ',
|
|
11
|
+
bottomLeft: ' ', left: ' ',
|
|
12
|
+
cross: ' ', teeTop: ' ', teeBottom: ' ', teeLeft: ' ', teeRight: ' ',
|
|
13
|
+
},
|
|
14
|
+
single: {
|
|
15
|
+
topLeft: '┌', top: '─', topRight: '┐',
|
|
16
|
+
right: '│', bottomRight: '┘', bottom: '─',
|
|
17
|
+
bottomLeft: '└', left: '│',
|
|
18
|
+
cross: '┼', teeTop: '┬', teeBottom: '┴', teeLeft: '┤', teeRight: '├',
|
|
19
|
+
},
|
|
20
|
+
round: {
|
|
21
|
+
topLeft: '╭', top: '─', topRight: '╮',
|
|
22
|
+
right: '│', bottomRight: '╯', bottom: '─',
|
|
23
|
+
bottomLeft: '╰', left: '│',
|
|
24
|
+
cross: '┼', teeTop: '┬', teeBottom: '┴', teeLeft: '┤', teeRight: '├',
|
|
25
|
+
},
|
|
26
|
+
double: {
|
|
27
|
+
topLeft: '╔', top: '═', topRight: '╗',
|
|
28
|
+
right: '║', bottomRight: '╝', bottom: '═',
|
|
29
|
+
bottomLeft: '╚', left: '║',
|
|
30
|
+
cross: '╬', teeTop: '╦', teeBottom: '╩', teeLeft: '╣', teeRight: '╠',
|
|
31
|
+
},
|
|
32
|
+
bold: {
|
|
33
|
+
topLeft: '┏', top: '━', topRight: '┓',
|
|
34
|
+
right: '┃', bottomRight: '┛', bottom: '━',
|
|
35
|
+
bottomLeft: '┗', left: '┃',
|
|
36
|
+
cross: '╋', teeTop: '┳', teeBottom: '┻', teeLeft: '┫', teeRight: '┣',
|
|
37
|
+
},
|
|
38
|
+
thick: {
|
|
39
|
+
topLeft: '▛', top: '▀', topRight: '▜',
|
|
40
|
+
right: '▐', bottomRight: '▟', bottom: '▄',
|
|
41
|
+
bottomLeft: '▙', left: '▌',
|
|
42
|
+
cross: '┼', teeTop: '┬', teeBottom: '┴', teeLeft: '┤', teeRight: '├',
|
|
43
|
+
},
|
|
44
|
+
half: {
|
|
45
|
+
topLeft: '▗', top: '▄', topRight: '▖',
|
|
46
|
+
right: '▌', bottomRight: '▘', bottom: '▀',
|
|
47
|
+
bottomLeft: '▝', left: '▐',
|
|
48
|
+
cross: '┼', teeTop: '┬', teeBottom: '┴', teeLeft: '┤', teeRight: '├',
|
|
49
|
+
},
|
|
50
|
+
dashed: {
|
|
51
|
+
topLeft: '┌', top: '╌', topRight: '┐',
|
|
52
|
+
right: '╎', bottomRight: '┘', bottom: '╌',
|
|
53
|
+
bottomLeft: '└', left: '╎',
|
|
54
|
+
cross: '┼', teeTop: '┬', teeBottom: '┴', teeLeft: '┤', teeRight: '├',
|
|
55
|
+
},
|
|
56
|
+
ascii: {
|
|
57
|
+
topLeft: '+', top: '-', topRight: '+',
|
|
58
|
+
right: '|', bottomRight: '+', bottom: '-',
|
|
59
|
+
bottomLeft: '+', left: '|',
|
|
60
|
+
cross: '+', teeTop: '+', teeBottom: '+', teeLeft: '+', teeRight: '+',
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
/** What each style degrades to when the terminal cannot draw it. */
|
|
64
|
+
const ASCII_FALLBACK = {
|
|
65
|
+
none: 'none',
|
|
66
|
+
single: 'ascii',
|
|
67
|
+
round: 'ascii',
|
|
68
|
+
double: 'ascii',
|
|
69
|
+
bold: 'ascii',
|
|
70
|
+
thick: 'ascii',
|
|
71
|
+
half: 'ascii',
|
|
72
|
+
dashed: 'ascii',
|
|
73
|
+
ascii: 'ascii',
|
|
74
|
+
};
|
|
75
|
+
/** Block-drawing styles need more than the BMP box-drawing range. */
|
|
76
|
+
const BMP_FALLBACK = {
|
|
77
|
+
thick: 'single',
|
|
78
|
+
half: 'single',
|
|
79
|
+
};
|
|
80
|
+
export function borderCharsFor(style, unicode) {
|
|
81
|
+
if (unicode === 'ascii')
|
|
82
|
+
return BORDER_SETS[ASCII_FALLBACK[style]];
|
|
83
|
+
if (unicode === 'bmp') {
|
|
84
|
+
const fallback = BMP_FALLBACK[style];
|
|
85
|
+
return BORDER_SETS[fallback ?? style];
|
|
86
|
+
}
|
|
87
|
+
return BORDER_SETS[style];
|
|
88
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ThemeDefinition } from '../types/theme.js';
|
|
2
|
+
/**
|
|
3
|
+
* The built-in themes.
|
|
4
|
+
*
|
|
5
|
+
* `dark` and `light` are the two bases; everything else extends one of them
|
|
6
|
+
* and restates only what it changes. The three that follow exist because the
|
|
7
|
+
* same application should be able to look like a dense operator console, an
|
|
8
|
+
* airy report or a workbench without a component knowing which - so the
|
|
9
|
+
* difference lives here, in border style and density, not in the catalog.
|
|
10
|
+
*/
|
|
11
|
+
export declare const DARK: ThemeDefinition;
|
|
12
|
+
export declare const LIGHT: ThemeDefinition;
|
|
13
|
+
/** Dense, bordered, high contrast. Every region is a labelled box. */
|
|
14
|
+
export declare const CONSOLE: ThemeDefinition;
|
|
15
|
+
/** Borderless. Whitespace and alignment do the separating. */
|
|
16
|
+
export declare const PAPER: ThemeDefinition;
|
|
17
|
+
/** Rounded chrome around a persistent frame. */
|
|
18
|
+
export declare const WORKBENCH: ThemeDefinition;
|
|
19
|
+
/** No colour at all. What a `colorDepth: 0` terminal or a pipe gets. */
|
|
20
|
+
export declare const MONO: ThemeDefinition;
|
|
21
|
+
/**
|
|
22
|
+
* Paper, after dark.
|
|
23
|
+
*
|
|
24
|
+
* The same airy, borderless, warm-accented character as `paper` - the point of
|
|
25
|
+
* it is the restraint, not the brightness - with the ink and the page swapped.
|
|
26
|
+
* A warm dark rather than a blue one, so the two read as one family and a
|
|
27
|
+
* reader moving between them is not moving between two different products.
|
|
28
|
+
*/
|
|
29
|
+
export declare const PAPER_DARK: ThemeDefinition;
|
|
30
|
+
export declare const BUILTIN_THEMES: ThemeDefinition[];
|
|
31
|
+
//# sourceMappingURL=builtin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builtin.d.ts","sourceRoot":"","sources":["../../src/themes/builtin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD;;;;;;;;GAQG;AAEH,eAAO,MAAM,IAAI,EAAE,eA4ClB,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,eA4CnB,CAAC;AAEF,sEAAsE;AACtE,eAAO,MAAM,OAAO,EAAE,eAoCrB,CAAC;AAEF,8DAA8D;AAC9D,eAAO,MAAM,KAAK,EAAE,eAiCnB,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,SAAS,EAAE,eAiCvB,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,IAAI,EAAE,eAwClB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,EAAE,eA8BxB,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,eAAe,EAE3C,CAAC"}
|