@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,567 @@
|
|
|
1
|
+
import { ATTR_DIM } from '../types/cells.js';
|
|
2
|
+
import { overflowOn } from '../render/layout.js';
|
|
3
|
+
import { COLOR_DEFAULT, mix, packColor } from '../render/color.js';
|
|
4
|
+
import { rectIntersect } from '../types/geometry.js';
|
|
5
|
+
import { graphemes, graphemeWidth, sanitize, stringWidth, truncate, truncateSideOf, wrapModeOf, wrapText, } from '../util/text.js';
|
|
6
|
+
import { attrsFromStyle, NO_INTERACTION, packStyleColor, resolveBorder, resolveStyle, } from './style.js';
|
|
7
|
+
/**
|
|
8
|
+
* Painting.
|
|
9
|
+
*
|
|
10
|
+
* The tree is walked twice per frame: once to build layout boxes, once to
|
|
11
|
+
* paint them. Both walks skip function components entirely - they contribute
|
|
12
|
+
* their children and nothing else - so the layout engine and the painter only
|
|
13
|
+
* ever see `box`, `text` and `canvas`.
|
|
14
|
+
*/
|
|
15
|
+
// ------------------------------------------------------------ paint surface
|
|
16
|
+
class Surface {
|
|
17
|
+
buffer;
|
|
18
|
+
rect;
|
|
19
|
+
clipRect;
|
|
20
|
+
defaultBg;
|
|
21
|
+
/**
|
|
22
|
+
* What a cell gets when the caller does not name a background.
|
|
23
|
+
*
|
|
24
|
+
* `COLOR_DEFAULT` for the painters that own the whole box, and the box's own
|
|
25
|
+
* resolved background for a `canvas`, which does not. A canvas is drawn
|
|
26
|
+
* *onto* something - a panel, a selected row, a themed surface - and a glyph
|
|
27
|
+
* written with only an `fg` was resetting the cell behind it to the
|
|
28
|
+
* terminal's default, punching a hole through whatever it was sitting on.
|
|
29
|
+
*
|
|
30
|
+
* A painter that really wants the terminal's own background can still say
|
|
31
|
+
* so, because `'default'` is a colour it can pass.
|
|
32
|
+
*/
|
|
33
|
+
constructor(buffer, rect, clipRect, defaultBg = COLOR_DEFAULT) {
|
|
34
|
+
this.buffer = buffer;
|
|
35
|
+
this.rect = rect;
|
|
36
|
+
this.clipRect = clipRect;
|
|
37
|
+
this.defaultBg = defaultBg;
|
|
38
|
+
}
|
|
39
|
+
visible(ax, ay) {
|
|
40
|
+
return (ax >= this.clipRect.x && ax < this.clipRect.x + this.clipRect.width &&
|
|
41
|
+
ay >= this.clipRect.y && ay < this.clipRect.y + this.clipRect.height);
|
|
42
|
+
}
|
|
43
|
+
put(x, y, char, style) {
|
|
44
|
+
const ax = this.rect.x + x;
|
|
45
|
+
const ay = this.rect.y + y;
|
|
46
|
+
if (!this.visible(ax, ay))
|
|
47
|
+
return;
|
|
48
|
+
this.buffer.put(ax, ay, char, style?.fg === undefined ? COLOR_DEFAULT : packColor(style.fg), style?.bg === undefined ? this.defaultBg : packColor(style.bg), style?.attrs ?? 0, style?.link);
|
|
49
|
+
}
|
|
50
|
+
/*
|
|
51
|
+
* One style, packed once.
|
|
52
|
+
*
|
|
53
|
+
* `put` packs both colours out of the style it is handed, which is right for
|
|
54
|
+
* one cell and wrong for the two callers that write a *run* of them: a
|
|
55
|
+
* twenty-character label was twenty pairs of `packColor` calls on the same
|
|
56
|
+
* two values, and a filled box was one pair per cell of the box. Packing is
|
|
57
|
+
* what a colour becomes, and it becomes the same thing every time.
|
|
58
|
+
*/
|
|
59
|
+
text(x, y, text, style) {
|
|
60
|
+
const fg = style?.fg === undefined ? COLOR_DEFAULT : packColor(style.fg);
|
|
61
|
+
const bg = style?.bg === undefined ? this.defaultBg : packColor(style.bg);
|
|
62
|
+
const attrs = style?.attrs ?? 0;
|
|
63
|
+
const link = style?.link;
|
|
64
|
+
const oy = this.rect.y + y;
|
|
65
|
+
let cx = x;
|
|
66
|
+
for (const g of graphemes(sanitize(text))) {
|
|
67
|
+
const w = graphemeWidth(g);
|
|
68
|
+
if (w === 0)
|
|
69
|
+
continue;
|
|
70
|
+
const ax = this.rect.x + cx;
|
|
71
|
+
if (this.visible(ax, oy))
|
|
72
|
+
this.buffer.put(ax, oy, g, fg, bg, attrs, link);
|
|
73
|
+
cx += w;
|
|
74
|
+
}
|
|
75
|
+
return cx - x;
|
|
76
|
+
}
|
|
77
|
+
fill(rect, char, style) {
|
|
78
|
+
const r = rect ?? { x: 0, y: 0, width: this.rect.width, height: this.rect.height };
|
|
79
|
+
const fg = style?.fg === undefined ? COLOR_DEFAULT : packColor(style.fg);
|
|
80
|
+
const bg = style?.bg === undefined ? this.defaultBg : packColor(style.bg);
|
|
81
|
+
const attrs = style?.attrs ?? 0;
|
|
82
|
+
const link = style?.link;
|
|
83
|
+
for (let y = r.y; y < r.y + r.height; y++) {
|
|
84
|
+
const ay = this.rect.y + y;
|
|
85
|
+
for (let x = r.x; x < r.x + r.width; x++) {
|
|
86
|
+
const ax = this.rect.x + x;
|
|
87
|
+
if (this.visible(ax, ay))
|
|
88
|
+
this.buffer.put(ax, ay, char, fg, bg, attrs, link);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
cell(x, y, cell) {
|
|
93
|
+
this.put(x, y, cell.char, {
|
|
94
|
+
fg: cell.fg, bg: cell.bg, attrs: cell.attrs, link: cell.link,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
clip(rect) {
|
|
98
|
+
const absolute = {
|
|
99
|
+
x: this.rect.x + rect.x,
|
|
100
|
+
y: this.rect.y + rect.y,
|
|
101
|
+
width: rect.width,
|
|
102
|
+
height: rect.height,
|
|
103
|
+
};
|
|
104
|
+
return new Surface(this.buffer, absolute, rectIntersect(this.clipRect, absolute), this.defaultBg);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Direct writer for the runtime itself, in absolute buffer coordinates. */
|
|
108
|
+
function rawSurface(buffer, clip) {
|
|
109
|
+
return new Surface(buffer, { x: 0, y: 0, width: buffer.width, height: buffer.height }, clip);
|
|
110
|
+
}
|
|
111
|
+
// ------------------------------------------------------------ render context
|
|
112
|
+
export function createRenderContext(theme, capabilities, state = NO_INTERACTION) {
|
|
113
|
+
return {
|
|
114
|
+
theme,
|
|
115
|
+
capabilities,
|
|
116
|
+
focused: state.focused,
|
|
117
|
+
hovered: state.hovered,
|
|
118
|
+
active: state.active,
|
|
119
|
+
selected: state.selected,
|
|
120
|
+
disabled: state.disabled,
|
|
121
|
+
color: (c, fallback = 'default') => c === undefined ? fallback : theme.color(String(c)),
|
|
122
|
+
glyph: (name) => {
|
|
123
|
+
const g = theme.glyphs[name];
|
|
124
|
+
return typeof g === 'string' ? g : '';
|
|
125
|
+
},
|
|
126
|
+
measureText: stringWidth,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const NO_INHERITANCE = { fg: COLOR_DEFAULT, bg: COLOR_DEFAULT, attrs: 0 };
|
|
130
|
+
const VISUAL = Symbol('textui.visual');
|
|
131
|
+
/**
|
|
132
|
+
* Colour is inherited; a box's own always wins.
|
|
133
|
+
*
|
|
134
|
+
* Without this a `text` inside a coloured box is drawn in the terminal's
|
|
135
|
+
* default colours - and because a cell holds one background, the glyphs punch
|
|
136
|
+
* a hole through the box behind them. That is a label in the wrong colour on a
|
|
137
|
+
* button, a selected row whose text stays the unselected colour, and a filled
|
|
138
|
+
* block with a ragged bar of default background across the middle of it.
|
|
139
|
+
*/
|
|
140
|
+
function visualFor(instance, env, inherited = NO_INHERITANCE) {
|
|
141
|
+
const state = env.stateOf(instance);
|
|
142
|
+
const style = resolveStyle(instance.props, env.theme, instance.component, instance.definition?.defaultStyle, state);
|
|
143
|
+
const border = resolveBorder(style.border, env.theme);
|
|
144
|
+
const ownFg = packStyleColor(style.fg, env.theme);
|
|
145
|
+
const ownBg = packStyleColor(style.bg, env.theme);
|
|
146
|
+
return {
|
|
147
|
+
style,
|
|
148
|
+
border,
|
|
149
|
+
fg: ownFg === COLOR_DEFAULT ? inherited.fg : ownFg,
|
|
150
|
+
bg: ownBg === COLOR_DEFAULT ? inherited.bg : ownBg,
|
|
151
|
+
// Attributes accumulate: `bold` on a row is bold for what is in the row.
|
|
152
|
+
attrs: attrsFromStyle(style) | inherited.attrs,
|
|
153
|
+
// Stating a background makes a box opaque, even when the background it
|
|
154
|
+
// states is the terminal's own. The two are not the same thing: a box
|
|
155
|
+
// that says nothing inherits what is behind it, and a box that says
|
|
156
|
+
// `bg: 'default'` fills itself with spaces at the terminal's colour.
|
|
157
|
+
//
|
|
158
|
+
// Every colour in the `mono` theme is `default`, so the old test - is the
|
|
159
|
+
// packed colour something other than the default? - made every surface in
|
|
160
|
+
// that theme transparent. A palette opened over a document drew its border
|
|
161
|
+
// and its rows and left the document showing through the gaps between
|
|
162
|
+
// them, which is unreadable and looked like a compositing bug.
|
|
163
|
+
ownBg: style.bg !== undefined,
|
|
164
|
+
ownFg: style.fg !== undefined,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/** Boxes contributed by an instance. Function components contribute none. */
|
|
168
|
+
export function buildBoxes(instance, env, inherited = NO_INHERITANCE) {
|
|
169
|
+
if (instance.kind !== 'host') {
|
|
170
|
+
// A function component contributes no box, and no colour of its own.
|
|
171
|
+
const out = [];
|
|
172
|
+
for (const child of instance.children)
|
|
173
|
+
out.push(...buildBoxes(child, env, inherited));
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
const visual = visualFor(instance, env, inherited);
|
|
177
|
+
const box = {
|
|
178
|
+
style: visual.style,
|
|
179
|
+
borderEdges: visual.border.edges,
|
|
180
|
+
children: [],
|
|
181
|
+
rect: { x: 0, y: 0, width: 0, height: 0 },
|
|
182
|
+
content: { x: 0, y: 0, width: 0, height: 0 },
|
|
183
|
+
scrollTop: typeof instance.props.scrollTop === 'number' ? instance.props.scrollTop : 0,
|
|
184
|
+
scrollLeft: typeof instance.props.scrollLeft === 'number' ? instance.props.scrollLeft : 0,
|
|
185
|
+
[VISUAL]: visual,
|
|
186
|
+
instance,
|
|
187
|
+
};
|
|
188
|
+
switch (instance.component) {
|
|
189
|
+
case 'text':
|
|
190
|
+
box.measure = textMeasure(instance, visual.style);
|
|
191
|
+
break;
|
|
192
|
+
case 'canvas': {
|
|
193
|
+
const intrinsic = instance.props.intrinsic;
|
|
194
|
+
box.measure = () => ({ width: intrinsic?.width ?? 0, height: intrinsic?.height ?? 0 });
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case 'spacer': {
|
|
198
|
+
const size = typeof instance.props.size === 'number' ? instance.props.size : undefined;
|
|
199
|
+
// No size and no flex means take what is left. A spacer that measures
|
|
200
|
+
// zero and does not grow is a node that does nothing, which is never
|
|
201
|
+
// what anyone wrote one for - and it is what `size` has always been
|
|
202
|
+
// documented to mean when it is unset.
|
|
203
|
+
if (size === undefined && box.style.flex === undefined) {
|
|
204
|
+
box.style = { ...box.style, flex: 1 };
|
|
205
|
+
}
|
|
206
|
+
box.measure = () => ({ width: size ?? 0, height: size ?? 0 });
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
default:
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
const handDown = { fg: visual.fg, bg: visual.bg, attrs: visual.attrs };
|
|
213
|
+
for (const child of instance.children) {
|
|
214
|
+
box.children.push(...buildBoxes(child, env, handDown));
|
|
215
|
+
}
|
|
216
|
+
// Painting and hit-testing order within one parent.
|
|
217
|
+
box.children.sort((a, b) => (a.style.zIndex ?? 0) - (b.style.zIndex ?? 0));
|
|
218
|
+
instance.box = box;
|
|
219
|
+
return [box];
|
|
220
|
+
}
|
|
221
|
+
function textContent(instance) {
|
|
222
|
+
const { content, children } = instance.props;
|
|
223
|
+
if (typeof content === 'string')
|
|
224
|
+
return content;
|
|
225
|
+
if (typeof content === 'number')
|
|
226
|
+
return String(content);
|
|
227
|
+
if (typeof children === 'string')
|
|
228
|
+
return children;
|
|
229
|
+
if (typeof children === 'number')
|
|
230
|
+
return String(children);
|
|
231
|
+
if (Array.isArray(children)) {
|
|
232
|
+
return children
|
|
233
|
+
.filter((c) => typeof c === 'string' || typeof c === 'number')
|
|
234
|
+
.join('');
|
|
235
|
+
}
|
|
236
|
+
return '';
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Flatten to one row, for the truncating wrap modes.
|
|
240
|
+
*
|
|
241
|
+
* A newline in a text that has one row to live in has nowhere to go. Joining
|
|
242
|
+
* with a space keeps the sentence readable and keeps the cut where the reader
|
|
243
|
+
* can see it; dropping everything after the first newline would silently lose
|
|
244
|
+
* text that the ellipsis then claims was merely too long.
|
|
245
|
+
*/
|
|
246
|
+
function oneLine(text) {
|
|
247
|
+
return text.includes('\n') ? text.split('\n').join(' ') : text;
|
|
248
|
+
}
|
|
249
|
+
function textMeasure(instance, style) {
|
|
250
|
+
return (maxWidth) => {
|
|
251
|
+
const text = sanitize(textContent(instance));
|
|
252
|
+
if (text === '')
|
|
253
|
+
return { width: 0, height: text.includes('\n') ? 1 : 1 };
|
|
254
|
+
const wrap = style.wrap ?? 'none';
|
|
255
|
+
// A truncating text is one row, always. It still asks for the width it
|
|
256
|
+
// would like - a box sizing itself around it gets the whole string when
|
|
257
|
+
// there is room, and only cuts when there is not.
|
|
258
|
+
if (truncateSideOf(wrap) !== undefined) {
|
|
259
|
+
return { width: stringWidth(oneLine(text)), height: 1 };
|
|
260
|
+
}
|
|
261
|
+
if (wrap === 'none') {
|
|
262
|
+
const lines = text.split('\n');
|
|
263
|
+
return {
|
|
264
|
+
width: Math.max(...lines.map(stringWidth)),
|
|
265
|
+
height: lines.length,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
const limit = Number.isFinite(maxWidth) && maxWidth > 0 ? maxWidth : stringWidth(text);
|
|
269
|
+
const lines = wrapText(text, limit, wrapModeOf(wrap));
|
|
270
|
+
return {
|
|
271
|
+
width: Math.min(limit, Math.max(0, ...lines.map(stringWidth))),
|
|
272
|
+
height: Math.max(1, lines.length),
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
// ------------------------------------------------------------------- paint
|
|
277
|
+
export function paintTree(buffer, instance, env, clip) {
|
|
278
|
+
if (instance.kind !== 'host') {
|
|
279
|
+
for (const child of instance.children)
|
|
280
|
+
paintTree(buffer, child, env, clip);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const box = instance.box;
|
|
284
|
+
if (!box)
|
|
285
|
+
return;
|
|
286
|
+
const visual = box[VISUAL] ?? visualFor(instance, env);
|
|
287
|
+
const rect = box.rect;
|
|
288
|
+
if (rect.width <= 0 || rect.height <= 0)
|
|
289
|
+
return;
|
|
290
|
+
const own = rectIntersect(clip, rect);
|
|
291
|
+
if (own.width <= 0 || own.height <= 0) {
|
|
292
|
+
// Fully clipped, but absolutely-positioned descendants may still be visible
|
|
293
|
+
// if they escaped - they do not, so nothing to do.
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const surface = rawSurface(buffer, own);
|
|
297
|
+
// 1. background
|
|
298
|
+
//
|
|
299
|
+
// Only when this box states one: an inherited background has already been
|
|
300
|
+
// painted by whoever owns it, and filling it again per nested box would be
|
|
301
|
+
// the same cells written several times a frame.
|
|
302
|
+
if (visual.ownBg || visual.style.fill) {
|
|
303
|
+
/*
|
|
304
|
+
* Straight to the buffer, with the colours it already has.
|
|
305
|
+
*
|
|
306
|
+
* This loop used to build a style object per cell and call `colorOf` twice
|
|
307
|
+
* to fill it - unpacking two packed colours into `Color` values so that
|
|
308
|
+
* `put` could pack them straight back. On a full-screen background that is
|
|
309
|
+
* five allocations a cell and sixty thousand a frame, all of it to arrive
|
|
310
|
+
* at the two integers `visual.fg` and `visual.bg` already held.
|
|
311
|
+
*
|
|
312
|
+
* Iterating the clipped rect rather than the box's own also drops the
|
|
313
|
+
* per-cell visibility test: `own` is the intersection, so every cell in it
|
|
314
|
+
* is visible by construction.
|
|
315
|
+
*/
|
|
316
|
+
const fill = visual.style.fill ?? ' ';
|
|
317
|
+
for (let y = own.y; y < own.y + own.height; y++) {
|
|
318
|
+
for (let x = own.x; x < own.x + own.width; x++) {
|
|
319
|
+
buffer.put(x, y, fill, visual.fg, visual.bg);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// 2. scrim: recede what is already here, rather than cover it
|
|
324
|
+
if (visual.style.scrim !== undefined && visual.style.scrim !== false) {
|
|
325
|
+
washRegion(buffer, own, packStyleColor(visual.style.scrim === true ? 'scrim' : visual.style.scrim, env.theme), visual.style.scrimStrength ?? 0.55);
|
|
326
|
+
}
|
|
327
|
+
// 3. border, with its title and footer
|
|
328
|
+
if (visual.border.style !== 'none') {
|
|
329
|
+
paintBorder(surface, rect, visual, instance, env);
|
|
330
|
+
}
|
|
331
|
+
// 4. content
|
|
332
|
+
switch (instance.component) {
|
|
333
|
+
case 'text':
|
|
334
|
+
paintText(surface, box, visual, instance, env);
|
|
335
|
+
break;
|
|
336
|
+
case 'canvas':
|
|
337
|
+
paintCanvas(buffer, box, instance, env, own, visual.bg);
|
|
338
|
+
break;
|
|
339
|
+
default:
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
// 5. children, clipped to this box's content when overflow is contained -
|
|
343
|
+
// per axis, so a row that scrolls sideways can still spill downwards.
|
|
344
|
+
const clipX = overflowOn(visual.style, 'x') !== 'visible';
|
|
345
|
+
const clipY = overflowOn(visual.style, 'y') !== 'visible';
|
|
346
|
+
const childClip = clipX || clipY
|
|
347
|
+
? rectIntersect(clip, {
|
|
348
|
+
x: clipX ? box.content.x : clip.x,
|
|
349
|
+
y: clipY ? box.content.y : clip.y,
|
|
350
|
+
width: clipX ? box.content.width : clip.width,
|
|
351
|
+
height: clipY ? box.content.height : clip.height,
|
|
352
|
+
})
|
|
353
|
+
: clip;
|
|
354
|
+
for (const child of instance.children) {
|
|
355
|
+
paintTree(buffer, child, env, childClip);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Blend a region toward one colour.
|
|
360
|
+
*
|
|
361
|
+
* A terminal has no alpha, so the choice is between covering the content
|
|
362
|
+
* behind a modal and moving it toward the scrim colour. Moving it keeps the
|
|
363
|
+
* screen recognisable - you can still see what the dialog is about - and it is
|
|
364
|
+
* what dimming means on a display that cannot dim.
|
|
365
|
+
*
|
|
366
|
+
* A cell left at the terminal's default colour cannot be blended, because we
|
|
367
|
+
* do not know what colour it is; it gets the dim attribute instead.
|
|
368
|
+
*/
|
|
369
|
+
function washRegion(buffer, rect, color, strength) {
|
|
370
|
+
const amount = Math.max(0, Math.min(1, strength));
|
|
371
|
+
/*
|
|
372
|
+
* Straight at the cells, not through `get()`.
|
|
373
|
+
*
|
|
374
|
+
* `Buffer.get` builds a friendly object - unpacking both colours into `Color`
|
|
375
|
+
* values - and this immediately packs them back to compare and mix. That is
|
|
376
|
+
* an allocation and a round trip per cell, and a scrim covers a whole dialog
|
|
377
|
+
* area: an 80x30 modal is 2,400 objects per frame to answer a question the
|
|
378
|
+
* packed integers already answer. `get()` stays the public shape; the
|
|
379
|
+
* renderer's inner loop is not the place for it.
|
|
380
|
+
*/
|
|
381
|
+
for (let y = rect.y; y < rect.y + rect.height; y++) {
|
|
382
|
+
for (let x = rect.x; x < rect.x + rect.width; x++) {
|
|
383
|
+
const at = buffer.packedAt(x, y);
|
|
384
|
+
if (!at)
|
|
385
|
+
continue;
|
|
386
|
+
buffer.put(x, y, at.char, at.fg === COLOR_DEFAULT ? at.fg : mix(at.fg, color, amount), at.bg === COLOR_DEFAULT ? at.bg : mix(at.bg, color, amount), at.fg === COLOR_DEFAULT || at.bg === COLOR_DEFAULT ? at.attrs | ATTR_DIM : at.attrs, at.link);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function colorOf(packed) {
|
|
391
|
+
if (packed === COLOR_DEFAULT)
|
|
392
|
+
return 'default';
|
|
393
|
+
if (packed >= 0x1000000) {
|
|
394
|
+
return { rgb: [(packed >> 16) & 0xff, (packed >> 8) & 0xff, packed & 0xff] };
|
|
395
|
+
}
|
|
396
|
+
return { palette: packed };
|
|
397
|
+
}
|
|
398
|
+
function paintBorder(surface, rect, visual, instance, env) {
|
|
399
|
+
const { chars, sides, colors } = visual.border;
|
|
400
|
+
/*
|
|
401
|
+
* A frame is the theme's `border` unless this box asked for something else.
|
|
402
|
+
*
|
|
403
|
+
* "Something else" is a colour on the border spec, or a colour on the box -
|
|
404
|
+
* `<box fg="danger" border="single">` means a danger frame, and that is
|
|
405
|
+
* worth keeping. What it must *not* mean is a colour the box merely
|
|
406
|
+
* inherited: the test used to be `visual.fg === COLOR_DEFAULT`, and
|
|
407
|
+
* `visual.fg` is the effective colour, so any box under an ancestor that set
|
|
408
|
+
* one took that instead of the border token. An application whose shell
|
|
409
|
+
* states its text colour - which is most of them - drew every frame in the
|
|
410
|
+
* text colour, as bright as the words inside it, and the `border` token in
|
|
411
|
+
* the theme went unused.
|
|
412
|
+
*
|
|
413
|
+
* `ownFg` is the same question `ownBg` already asks for the background.
|
|
414
|
+
*/
|
|
415
|
+
const fg = visual.border.color !== undefined
|
|
416
|
+
? colorOf(packStyleColor(visual.border.color, env.theme))
|
|
417
|
+
: visual.ownFg
|
|
418
|
+
? colorOf(visual.fg)
|
|
419
|
+
: colorOf(packStyleColor('border', env.theme));
|
|
420
|
+
const bg = colorOf(visual.bg);
|
|
421
|
+
// `dim` on the border spec dims the frame and nothing else. Setting it on
|
|
422
|
+
// the box would take the title, the content and every child with it.
|
|
423
|
+
const attrs = visual.attrs | (visual.border.dim ? ATTR_DIM : 0);
|
|
424
|
+
const style = { fg, bg, attrs };
|
|
425
|
+
/** One edge's style: its own colour if it named one, the frame's otherwise. */
|
|
426
|
+
const edge = (side) => {
|
|
427
|
+
const own = colors[side];
|
|
428
|
+
return own === undefined ? style : { fg: colorOf(packStyleColor(own, env.theme)), bg, attrs };
|
|
429
|
+
};
|
|
430
|
+
const top = edge('top');
|
|
431
|
+
const bottomStyle = edge('bottom');
|
|
432
|
+
const { x, y, width: w, height: h } = rect;
|
|
433
|
+
const right = x + w - 1;
|
|
434
|
+
const bottom = y + h - 1;
|
|
435
|
+
if (sides.top) {
|
|
436
|
+
for (let i = x; i <= right; i++)
|
|
437
|
+
surface.put(i, y, chars.top, top);
|
|
438
|
+
}
|
|
439
|
+
if (sides.bottom && h > 1) {
|
|
440
|
+
for (let i = x; i <= right; i++)
|
|
441
|
+
surface.put(i, bottom, chars.bottom, bottomStyle);
|
|
442
|
+
}
|
|
443
|
+
if (sides.left) {
|
|
444
|
+
const left = edge('left');
|
|
445
|
+
for (let i = y; i <= bottom; i++)
|
|
446
|
+
surface.put(x, i, chars.left, left);
|
|
447
|
+
}
|
|
448
|
+
if (sides.right && w > 1) {
|
|
449
|
+
const rightStyle = edge('right');
|
|
450
|
+
for (let i = y; i <= bottom; i++)
|
|
451
|
+
surface.put(right, i, chars.right, rightStyle);
|
|
452
|
+
}
|
|
453
|
+
// A corner is one cell and two edges meet in it, so one of them has to win.
|
|
454
|
+
// The horizontal rule takes it: a box reads as a top and a bottom line with
|
|
455
|
+
// sides between them, and breaking a long rule's colour at its own ends is
|
|
456
|
+
// the more visible mistake.
|
|
457
|
+
if (sides.top && sides.left)
|
|
458
|
+
surface.put(x, y, chars.topLeft, top);
|
|
459
|
+
if (sides.top && sides.right && w > 1)
|
|
460
|
+
surface.put(right, y, chars.topRight, top);
|
|
461
|
+
if (sides.bottom && sides.left && h > 1)
|
|
462
|
+
surface.put(x, bottom, chars.bottomLeft, bottomStyle);
|
|
463
|
+
if (sides.bottom && sides.right && w > 1 && h > 1) {
|
|
464
|
+
surface.put(right, bottom, chars.bottomRight, bottomStyle);
|
|
465
|
+
}
|
|
466
|
+
const inner = w - 2;
|
|
467
|
+
if (inner <= 0)
|
|
468
|
+
return;
|
|
469
|
+
/*
|
|
470
|
+
* A second label on the top rule, hard against the right.
|
|
471
|
+
*
|
|
472
|
+
* Painted *before* the title, and the title is then given the width it
|
|
473
|
+
* leaves, so the two cannot land on the same cells whatever `titleAlign`
|
|
474
|
+
* says. That order also decides which one loses when there is not enough
|
|
475
|
+
* room: the right label is the short one - a count, a shortcut, a state -
|
|
476
|
+
* and the title is the one that can be truncated and still be read.
|
|
477
|
+
*/
|
|
478
|
+
const rightTitle = instance.props.rightTitle;
|
|
479
|
+
let reserved = 0;
|
|
480
|
+
if (sides.top && typeof rightTitle === 'string' && rightTitle !== '') {
|
|
481
|
+
const label = ` ${rightTitle} `;
|
|
482
|
+
reserved = Math.min(inner, stringWidth(sanitize(label)));
|
|
483
|
+
paintBorderLabel(surface, x, y, inner, label, 'right', { ...style, fg: colorOf(packStyleColor('muted', env.theme)), attrs: visual.attrs }, env.theme.glyphs.ellipsis);
|
|
484
|
+
}
|
|
485
|
+
const title = instance.props.title;
|
|
486
|
+
if (sides.top && typeof title === 'string' && title !== '' && inner - reserved > 0) {
|
|
487
|
+
paintBorderLabel(surface, x, y, inner - reserved, ` ${title} `, instance.props.titleAlign ?? 'left', { ...style, fg: colorOf(visual.fg === COLOR_DEFAULT ? packStyleColor('text', env.theme) : visual.fg), attrs: visual.attrs }, env.theme.glyphs.ellipsis);
|
|
488
|
+
}
|
|
489
|
+
const footer = instance.props.footer;
|
|
490
|
+
if (sides.bottom && h > 1 && typeof footer === 'string' && footer !== '') {
|
|
491
|
+
paintBorderLabel(surface, x, bottom, inner, ` ${footer} `, instance.props.footerAlign ?? 'left', { ...style, fg: colorOf(packStyleColor('muted', env.theme)), attrs: visual.attrs }, env.theme.glyphs.ellipsis);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function paintBorderLabel(surface, x, y, inner, label, align, style, ellipsis) {
|
|
495
|
+
const text = truncate(sanitize(label), inner, ellipsis);
|
|
496
|
+
const w = stringWidth(text);
|
|
497
|
+
const offset = align === 'center' ? Math.max(0, Math.floor((inner - w) / 2))
|
|
498
|
+
: align === 'right' ? Math.max(0, inner - w)
|
|
499
|
+
: 0;
|
|
500
|
+
surface.text(x + 1 + offset, y, text, style);
|
|
501
|
+
}
|
|
502
|
+
function paintText(surface, box, visual, instance, env) {
|
|
503
|
+
const content = sanitize(textContent(instance));
|
|
504
|
+
if (content === '')
|
|
505
|
+
return;
|
|
506
|
+
const { content: area } = box;
|
|
507
|
+
if (area.width <= 0 || area.height <= 0)
|
|
508
|
+
return;
|
|
509
|
+
const style = {
|
|
510
|
+
fg: colorOf(visual.fg),
|
|
511
|
+
bg: colorOf(visual.bg),
|
|
512
|
+
attrs: visual.attrs,
|
|
513
|
+
link: typeof instance.props.link === 'string' ? instance.props.link : undefined,
|
|
514
|
+
};
|
|
515
|
+
const wrap = visual.style.wrap ?? 'none';
|
|
516
|
+
const align = visual.style.textAlign ?? 'left';
|
|
517
|
+
// The ellipsis is a glyph like any other: on an ascii terminal it is '...'.
|
|
518
|
+
const ellipsis = typeof instance.props.ellipsis === 'string'
|
|
519
|
+
? instance.props.ellipsis
|
|
520
|
+
: env.theme.glyphs.ellipsis;
|
|
521
|
+
// Where the cut goes, if there is one. `wrap` names it for the whole text;
|
|
522
|
+
// the older `truncate` prop still wins where both are set, so a caller that
|
|
523
|
+
// wants word wrap *and* an ellipsis on the last row can still ask for it -
|
|
524
|
+
// that is a combination `wrap` alone cannot express.
|
|
525
|
+
const wrapSide = truncateSideOf(wrap);
|
|
526
|
+
const explicit = instance.props.truncate;
|
|
527
|
+
const truncateSide = explicit ?? wrapSide;
|
|
528
|
+
const raw = wrapSide !== undefined
|
|
529
|
+
? [oneLine(content)]
|
|
530
|
+
: wrap === 'none' ? content.split('\n') : wrapText(content, area.width, wrapModeOf(wrap));
|
|
531
|
+
for (let i = 0; i < raw.length && i < area.height; i++) {
|
|
532
|
+
let line = raw[i];
|
|
533
|
+
if (stringWidth(line) > area.width) {
|
|
534
|
+
line = truncateSide === false
|
|
535
|
+
? line
|
|
536
|
+
: truncate(line, area.width, ellipsis, truncateSide ?? 'end');
|
|
537
|
+
}
|
|
538
|
+
const w = stringWidth(line);
|
|
539
|
+
const offset = align === 'center' ? Math.max(0, Math.floor((area.width - w) / 2))
|
|
540
|
+
: align === 'right' ? Math.max(0, area.width - w)
|
|
541
|
+
: 0;
|
|
542
|
+
surface.text(area.x + offset, area.y + i, line, style);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* The escape hatch. A canvas draws in its own coordinate space starting at
|
|
547
|
+
* 0,0, clipped to its content box - so a chart can be written without knowing
|
|
548
|
+
* where on screen it landed.
|
|
549
|
+
*/
|
|
550
|
+
function paintCanvas(buffer, box, instance, env, clip,
|
|
551
|
+
/** The box's own resolved background, which is what it is drawn onto. */
|
|
552
|
+
background) {
|
|
553
|
+
const draw = instance.props.draw;
|
|
554
|
+
if (typeof draw !== 'function')
|
|
555
|
+
return;
|
|
556
|
+
const area = box.content;
|
|
557
|
+
if (area.width <= 0 || area.height <= 0)
|
|
558
|
+
return;
|
|
559
|
+
const surface = new Surface(buffer, area, rectIntersect(clip, area), background);
|
|
560
|
+
const ctx = createRenderContext(env.theme, env.capabilities, env.stateOf(instance));
|
|
561
|
+
try {
|
|
562
|
+
draw(surface, ctx);
|
|
563
|
+
}
|
|
564
|
+
catch (err) {
|
|
565
|
+
instance.runtime.onError(err, `canvas draw in <${instance.component}>`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { BindingPath, ComponentNode } from '../types/graph.js';
|
|
2
|
+
import type { Runtime } from './runtime.js';
|
|
3
|
+
import type { Instance } from './instance.js';
|
|
4
|
+
/**
|
|
5
|
+
* The render pass: a node graph in, a retained instance tree out.
|
|
6
|
+
*
|
|
7
|
+
* Two things make this cheap enough to run per frame. Instances are matched by
|
|
8
|
+
* key and component name, so an unchanged subtree keeps its hooks and its
|
|
9
|
+
* layout. And an instance is only re-rendered when something marked it dirty -
|
|
10
|
+
* its own state, a store path it read, or its parent handing it new props.
|
|
11
|
+
*/
|
|
12
|
+
export interface RenderOptions {
|
|
13
|
+
/** Re-render everything, ignoring dirty flags. After a theme change. */
|
|
14
|
+
force?: boolean;
|
|
15
|
+
/** Record why each instance rendered. */
|
|
16
|
+
diagnostics?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function renderTree(runtime: Runtime, root: Instance | null, node: ComponentNode, options?: RenderOptions): Instance;
|
|
19
|
+
export declare function reconcile(runtime: Runtime, parent: Instance | null, existing: Instance | undefined, node: ComponentNode, options: RenderOptions, depth: number, dataContext: BindingPath | undefined): Instance;
|
|
20
|
+
/** Every effect queued during the last render, parents last. */
|
|
21
|
+
export declare function collectEffects(root: Instance): (() => void)[];
|
|
22
|
+
export declare function disposeTree(root: Instance): void;
|
|
23
|
+
//# sourceMappingURL=reconcile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/runtime/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAA8B,MAAM,mBAAmB,CAAC;AAChG,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAQ9C;;;;;;;GAOG;AAEH,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,yCAAyC;IACzC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAID,wBAAgB,UAAU,CACxB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,QAAQ,GAAG,IAAI,EACrB,IAAI,EAAE,aAAa,EACnB,OAAO,GAAE,aAAkB,GAC1B,QAAQ,CAGV;AASD,wBAAgB,SAAS,CACvB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,QAAQ,GAAG,IAAI,EACvB,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAC9B,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,WAAW,GAAG,SAAS,GACnC,QAAQ,CA4DV;AAgND,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,CAAC,MAAM,IAAI,CAAC,EAAE,CAQ7D;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAEhD"}
|