@svelterm/core 0.1.0 → 0.23.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.
Files changed (166) hide show
  1. package/CHANGELOG.md +465 -0
  2. package/README.md +42 -29
  3. package/dist/src/cli/build.d.ts +13 -0
  4. package/dist/src/cli/build.js +119 -0
  5. package/dist/src/cli/bundle.d.ts +25 -0
  6. package/dist/src/cli/bundle.js +61 -0
  7. package/dist/src/cli/dev.d.ts +10 -0
  8. package/dist/src/cli/dev.js +152 -0
  9. package/dist/src/cli/devtools.d.ts +9 -0
  10. package/dist/src/cli/devtools.js +47 -0
  11. package/dist/src/cli/init.d.ts +8 -0
  12. package/dist/src/cli/init.js +153 -0
  13. package/dist/src/cli/main.d.ts +9 -0
  14. package/dist/src/cli/main.js +52 -0
  15. package/dist/src/cli/svt-bin.d.ts +2 -0
  16. package/dist/src/cli/svt-bin.js +6 -0
  17. package/dist/src/cli/svt.d.ts +14 -0
  18. package/dist/src/cli/svt.js +76 -0
  19. package/dist/src/components/text-buffer.js +8 -5
  20. package/dist/src/css/animation-runner.d.ts +15 -6
  21. package/dist/src/css/animation-runner.js +80 -29
  22. package/dist/src/css/animation.d.ts +12 -0
  23. package/dist/src/css/animation.js +21 -0
  24. package/dist/src/css/calc.js +4 -3
  25. package/dist/src/css/color.d.ts +19 -0
  26. package/dist/src/css/color.js +371 -62
  27. package/dist/src/css/compute.d.ts +31 -4
  28. package/dist/src/css/compute.js +273 -34
  29. package/dist/src/css/defaults.d.ts +1 -1
  30. package/dist/src/css/defaults.js +9 -0
  31. package/dist/src/css/easing.d.ts +9 -0
  32. package/dist/src/css/easing.js +95 -0
  33. package/dist/src/css/incremental.d.ts +1 -1
  34. package/dist/src/css/incremental.js +2 -2
  35. package/dist/src/css/interpolate.d.ts +13 -0
  36. package/dist/src/css/interpolate.js +41 -0
  37. package/dist/src/css/parser.js +59 -3
  38. package/dist/src/css/pseudo-elements.d.ts +9 -0
  39. package/dist/src/css/pseudo-elements.js +97 -0
  40. package/dist/src/css/selector.d.ts +17 -2
  41. package/dist/src/css/selector.js +128 -13
  42. package/dist/src/css/specificity.js +17 -6
  43. package/dist/src/css/values.d.ts +6 -1
  44. package/dist/src/css/values.js +13 -6
  45. package/dist/src/debug/context.d.ts +13 -0
  46. package/dist/src/debug/context.js +11 -0
  47. package/dist/src/debug/css.d.ts +12 -0
  48. package/dist/src/debug/css.js +28 -0
  49. package/dist/src/debug/dom.d.ts +17 -0
  50. package/dist/src/debug/dom.js +92 -0
  51. package/dist/src/devtools/DevTools.compiled.js +327 -0
  52. package/dist/src/devtools/DevTools.css.js +1 -0
  53. package/dist/src/devtools/client.d.ts +36 -0
  54. package/dist/src/devtools/client.js +76 -0
  55. package/dist/src/framelog.d.ts +54 -0
  56. package/dist/src/framelog.js +99 -0
  57. package/dist/src/headless.js +12 -4
  58. package/dist/src/index.d.ts +66 -3
  59. package/dist/src/index.js +610 -81
  60. package/dist/src/input/checkable.d.ts +8 -0
  61. package/dist/src/input/checkable.js +66 -0
  62. package/dist/src/input/details.d.ts +6 -0
  63. package/dist/src/input/details.js +34 -0
  64. package/dist/src/input/focus.d.ts +6 -0
  65. package/dist/src/input/focus.js +27 -9
  66. package/dist/src/input/keyboard.d.ts +2 -2
  67. package/dist/src/input/keyboard.js +32 -5
  68. package/dist/src/input/label.d.ts +8 -0
  69. package/dist/src/input/label.js +53 -0
  70. package/dist/src/input/modal.d.ts +9 -0
  71. package/dist/src/input/modal.js +28 -0
  72. package/dist/src/input/mouse.d.ts +2 -2
  73. package/dist/src/input/mouse.js +15 -2
  74. package/dist/src/input/select.d.ts +12 -0
  75. package/dist/src/input/select.js +63 -0
  76. package/dist/src/input/selection.d.ts +48 -0
  77. package/dist/src/input/selection.js +150 -0
  78. package/dist/src/layout/engine.d.ts +2 -0
  79. package/dist/src/layout/engine.js +1092 -142
  80. package/dist/src/layout/flex.js +4 -4
  81. package/dist/src/layout/size.js +3 -2
  82. package/dist/src/layout/text.d.ts +3 -2
  83. package/dist/src/layout/text.js +96 -17
  84. package/dist/src/layout/unicode.d.ts +20 -0
  85. package/dist/src/layout/unicode.js +121 -0
  86. package/dist/src/render/animation-clock.d.ts +57 -0
  87. package/dist/src/render/animation-clock.js +221 -0
  88. package/dist/src/render/ansi-text.d.ts +26 -0
  89. package/dist/src/render/ansi-text.js +131 -0
  90. package/dist/src/render/ansi.d.ts +18 -0
  91. package/dist/src/render/ansi.js +64 -19
  92. package/dist/src/render/border.js +166 -17
  93. package/dist/src/render/buffer.d.ts +1 -0
  94. package/dist/src/render/buffer.js +5 -2
  95. package/dist/src/render/clock.d.ts +35 -0
  96. package/dist/src/render/clock.js +67 -0
  97. package/dist/src/render/color-depth.d.ts +8 -0
  98. package/dist/src/render/color-depth.js +59 -0
  99. package/dist/src/render/context.d.ts +1 -0
  100. package/dist/src/render/context.js +17 -21
  101. package/dist/src/render/cursor-emit.d.ts +18 -0
  102. package/dist/src/render/cursor-emit.js +50 -0
  103. package/dist/src/render/diff.d.ts +12 -0
  104. package/dist/src/render/diff.js +120 -0
  105. package/dist/src/render/generation.d.ts +9 -0
  106. package/dist/src/render/generation.js +14 -0
  107. package/dist/src/render/graphics-layer.d.ts +27 -0
  108. package/dist/src/render/graphics-layer.js +86 -0
  109. package/dist/src/render/image.d.ts +27 -0
  110. package/dist/src/render/image.js +113 -0
  111. package/dist/src/render/incremental-paint.d.ts +7 -3
  112. package/dist/src/render/incremental-paint.js +52 -79
  113. package/dist/src/render/inline.d.ts +59 -0
  114. package/dist/src/render/inline.js +219 -0
  115. package/dist/src/render/kitty-graphics.d.ts +24 -0
  116. package/dist/src/render/kitty-graphics.js +58 -0
  117. package/dist/src/render/paint-text.js +68 -22
  118. package/dist/src/render/paint.d.ts +8 -1
  119. package/dist/src/render/paint.js +358 -31
  120. package/dist/src/render/png.d.ts +13 -0
  121. package/dist/src/render/png.js +145 -0
  122. package/dist/src/render/scrollbar.d.ts +8 -2
  123. package/dist/src/render/scrollbar.js +71 -14
  124. package/dist/src/render/snapshot.js +3 -1
  125. package/dist/src/renderer/default.d.ts +7 -0
  126. package/dist/src/renderer/default.js +11 -0
  127. package/dist/src/renderer/index.d.ts +8 -2
  128. package/dist/src/renderer/index.js +4 -2
  129. package/dist/src/renderer/node.d.ts +109 -0
  130. package/dist/src/renderer/node.js +165 -1
  131. package/dist/src/terminal/capabilities.d.ts +33 -0
  132. package/dist/src/terminal/capabilities.js +66 -0
  133. package/dist/src/terminal/clipboard.d.ts +9 -0
  134. package/dist/src/terminal/clipboard.js +39 -0
  135. package/dist/src/terminal/io.d.ts +82 -0
  136. package/dist/src/terminal/io.js +155 -0
  137. package/dist/src/terminal/screen.d.ts +3 -10
  138. package/dist/src/terminal/screen.js +5 -28
  139. package/dist/src/terminal/stdin-router.d.ts +8 -5
  140. package/dist/src/terminal/stdin-router.js +22 -11
  141. package/dist/src/utils/node-map.d.ts +24 -0
  142. package/dist/src/utils/node-map.js +75 -0
  143. package/dist/src/vite/config.d.ts +62 -0
  144. package/dist/src/vite/config.js +191 -0
  145. package/docs/compatibility.md +67 -0
  146. package/docs/debug/devtools.md +40 -0
  147. package/docs/debug/svt.md +50 -0
  148. package/docs/distribution.md +106 -0
  149. package/docs/elements.md +120 -0
  150. package/docs/getting-started.md +177 -0
  151. package/docs/guide/css.md +187 -0
  152. package/docs/guide/input.md +143 -0
  153. package/docs/guide/layout.md +171 -0
  154. package/docs/guide/theming.md +94 -0
  155. package/docs/how-it-works.md +115 -0
  156. package/docs/inline-mode.md +77 -0
  157. package/docs/layout.md +112 -0
  158. package/docs/motion.md +91 -0
  159. package/docs/reference/README.md +65 -0
  160. package/docs/reference/css/properties/border-corner.md +82 -0
  161. package/docs/reference/css/properties/border-style.md +168 -0
  162. package/docs/reference.md +227 -0
  163. package/docs/selectors.md +80 -0
  164. package/docs/terminal-css.md +149 -0
  165. package/docs/terminals.md +83 -0
  166. package/package.json +28 -7
@@ -1,79 +1,301 @@
1
- const ANSI_COLORS = {
2
- black: 'black', red: 'red', green: 'green', yellow: 'yellow',
3
- blue: 'blue', magenta: 'magenta', cyan: 'cyan', white: 'white',
4
- };
5
- const ANSI_RGB = {
6
- black: [0, 0, 0], red: [255, 0, 0], green: [0, 255, 0], yellow: [255, 255, 0],
7
- blue: [0, 0, 255], magenta: [255, 0, 255], cyan: [0, 255, 255], white: [255, 255, 255],
8
- };
1
+ /**
2
+ * CSS Color Level 4 parser and resolver.
3
+ *
4
+ * Supports: hex (#rgb, #rrggbb, #rrggbbaa), rgb(), hsl(), hwb(),
5
+ * lab(), lch(), oklab(), oklch(), named colours, transparent,
6
+ * light-dark(a, b) (resolved against the active colorScheme).
7
+ * Both legacy comma syntax and modern space + / alpha syntax.
8
+ */
9
+ // --- Public API ---
10
+ /**
11
+ * Expand `light-dark(a, b)` to whichever side matches the active scheme.
12
+ * Recursive on the chosen side so `light-dark(light-dark(...), x)` works.
13
+ * Returns the input unchanged when no light-dark() call is present.
14
+ */
15
+ export function expandLightDark(value, scheme = 'dark') {
16
+ if (!value.includes('light-dark('))
17
+ return value;
18
+ const start = value.indexOf('light-dark(');
19
+ const argsStart = start + 'light-dark('.length;
20
+ // Find the matching closing paren (track nesting for nested colour funcs).
21
+ let depth = 1;
22
+ let i = argsStart;
23
+ while (i < value.length && depth > 0) {
24
+ const ch = value[i];
25
+ if (ch === '(')
26
+ depth++;
27
+ else if (ch === ')')
28
+ depth--;
29
+ if (depth === 0)
30
+ break;
31
+ i++;
32
+ }
33
+ if (depth !== 0)
34
+ return value; // unbalanced — give up, let resolveColor fail loudly
35
+ const args = value.slice(argsStart, i);
36
+ // Split on the comma at depth 0 (commas inside nested parens aren't separators).
37
+ let split = -1;
38
+ let d = 0;
39
+ for (let j = 0; j < args.length; j++) {
40
+ const ch = args[j];
41
+ if (ch === '(')
42
+ d++;
43
+ else if (ch === ')')
44
+ d--;
45
+ else if (ch === ',' && d === 0) {
46
+ split = j;
47
+ break;
48
+ }
49
+ }
50
+ if (split < 0)
51
+ return value;
52
+ const lightArg = args.slice(0, split).trim();
53
+ const darkArg = args.slice(split + 1).trim();
54
+ const picked = scheme === 'light' ? lightArg : darkArg;
55
+ const before = value.slice(0, start);
56
+ const after = value.slice(i + 1);
57
+ // Recurse so additional light-dark()s elsewhere in the string are also expanded.
58
+ return expandLightDark(before + picked + after, scheme);
59
+ }
9
60
  export function resolveColor(value) {
10
61
  const lower = value.toLowerCase().trim();
11
- // ANSI named colors (exact match, highest priority)
12
- const ansiName = ANSI_COLORS[lower];
13
- if (ansiName)
14
- return ansiName;
15
- // Hex colors
62
+ // ANSI named colors (exact match, highest priority for terminal rendering)
63
+ if (lower in ANSI_COLORS)
64
+ return ANSI_COLORS[lower];
65
+ // transparent — terminal output has no alpha layer, so the closest
66
+ // semantic match is "no colour set": let the parent's bg show through.
67
+ if (lower === 'transparent')
68
+ return 'default';
69
+ // Hex colors stay truecolor, even when they equal a basic ANSI colour:
70
+ // keywords are themeable (mapped to SGR names the terminal palette can
71
+ // restyle), an explicit hex is exact.
16
72
  if (lower.startsWith('#')) {
17
- const expanded = expandHex(lower);
18
- return hexToNearestAnsi(expanded) ?? expanded;
19
- }
20
- // rgb() / rgba()
21
- if (lower.startsWith('rgb')) {
22
- return parseRgbFunc(lower);
73
+ return expandHex(lower);
23
74
  }
24
- // hsl() / hsla()
25
- if (lower.startsWith('hsl')) {
26
- return parseHslFunc(lower);
75
+ // Color functions
76
+ const funcMatch = lower.match(/^(\w+)\((.+)\)$/);
77
+ if (funcMatch) {
78
+ const rgb = parseColorFunction(funcMatch[1], funcMatch[2]);
79
+ if (rgb) {
80
+ const alpha = parseFunctionAlpha(funcMatch[1], funcMatch[2]);
81
+ if (alpha <= 0)
82
+ return 'default';
83
+ const base = rgbToColor(rgb[0], rgb[1], rgb[2]);
84
+ return alpha < 1 ? base + toHex(Math.round(alpha * 255)) : base;
85
+ }
27
86
  }
28
87
  // CSS named colors
29
- const named = CSS_NAMED_COLORS[lower];
30
- if (named)
31
- return named;
88
+ if (lower in CSS_NAMED_COLORS)
89
+ return CSS_NAMED_COLORS[lower];
32
90
  return 'default';
33
91
  }
34
- function expandHex(hex) {
35
- const h = hex.slice(1);
36
- if (h.length === 3)
37
- return '#' + h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
38
- return hex;
92
+ /** Nominal xterm values for blending over SGR colour names. */
93
+ const NOMINAL_RGB = {
94
+ black: [0, 0, 0], red: [205, 0, 0], green: [0, 205, 0], yellow: [205, 205, 0],
95
+ blue: [0, 0, 238], magenta: [205, 0, 205], cyan: [0, 205, 205], white: [229, 229, 229],
96
+ // The terminal's actual default bg is unknowable — assume black.
97
+ default: [0, 0, 0],
98
+ };
99
+ /**
100
+ * Composite an alpha colour (#rrggbbaa) over a base colour, returning
101
+ * opaque #rrggbb. Opaque over-colours return unchanged.
102
+ */
103
+ export function blendColor(under, over) {
104
+ if (!over.startsWith('#') || over.length !== 9)
105
+ return over;
106
+ const alpha = parseInt(over.slice(7, 9), 16) / 255;
107
+ const overRgb = [
108
+ parseInt(over.slice(1, 3), 16),
109
+ parseInt(over.slice(3, 5), 16),
110
+ parseInt(over.slice(5, 7), 16),
111
+ ];
112
+ const underRgb = under.startsWith('#')
113
+ ? [parseInt(under.slice(1, 3), 16), parseInt(under.slice(3, 5), 16), parseInt(under.slice(5, 7), 16)]
114
+ : NOMINAL_RGB[under] ?? NOMINAL_RGB.default;
115
+ const mixed = overRgb.map((channel, i) => Math.round(channel * alpha + underRgb[i] * (1 - alpha)));
116
+ return '#' + mixed.map(c => toHex(c)).join('');
39
117
  }
40
- function hexToNearestAnsi(hex) {
41
- const r = parseInt(hex.slice(1, 3), 16);
42
- const g = parseInt(hex.slice(3, 5), 16);
43
- const b = parseInt(hex.slice(5, 7), 16);
44
- for (const [name, [cr, cg, cb]] of Object.entries(ANSI_RGB)) {
45
- if (r === cr && g === cg && b === cb)
46
- return name;
118
+ /** The alpha component of a colour-function value (legacy or modern). */
119
+ function parseFunctionAlpha(name, args) {
120
+ let alphaStr = null;
121
+ if (args.includes(',')) {
122
+ const parts = args.split(',').map(s => s.trim());
123
+ if ((name === 'rgb' || name === 'rgba' || name === 'hsl' || name === 'hsla') && parts.length >= 4) {
124
+ alphaStr = parts[3];
125
+ }
126
+ }
127
+ else {
128
+ const slashIdx = args.indexOf('/');
129
+ if (slashIdx >= 0)
130
+ alphaStr = args.substring(slashIdx + 1).trim();
47
131
  }
48
- return null;
132
+ if (alphaStr === null)
133
+ return 1;
134
+ const value = alphaStr.endsWith('%') ? parseFloat(alphaStr) / 100 : parseFloat(alphaStr);
135
+ if (isNaN(value))
136
+ return 1;
137
+ return Math.max(0, Math.min(1, value));
49
138
  }
50
- function parseRgbFunc(value) {
51
- const match = value.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
52
- if (!match)
53
- return 'default';
54
- const r = parseInt(match[1]);
55
- const g = parseInt(match[2]);
56
- const b = parseInt(match[3]);
57
- return rgbToColor(r, g, b);
58
- }
59
- function parseHslFunc(value) {
60
- const match = value.match(/hsla?\(\s*(\d+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%/);
61
- if (!match)
62
- return 'default';
63
- const h = parseInt(match[1]);
64
- const s = parseFloat(match[2]) / 100;
65
- const l = parseFloat(match[3]) / 100;
66
- const [r, g, b] = hslToRgb(h, s, l);
67
- return rgbToColor(r, g, b);
139
+ // --- Color function parsing ---
140
+ function parseColorFunction(name, args) {
141
+ // Detect legacy comma syntax (rgb/hsl only)
142
+ const isLegacy = args.includes(',');
143
+ if (isLegacy) {
144
+ const parts = args.split(',').map(s => s.trim());
145
+ switch (name) {
146
+ case 'rgb':
147
+ case 'rgba': return parseRgbChannels(parts);
148
+ case 'hsl':
149
+ case 'hsla': return parseHslChannels(parts);
150
+ default: return null;
151
+ }
152
+ }
153
+ // Modern space-separated syntax with optional / alpha
154
+ const slashIdx = args.indexOf('/');
155
+ const channelStr = slashIdx >= 0 ? args.substring(0, slashIdx) : args;
156
+ const parts = channelStr.trim().split(/\s+/);
157
+ switch (name) {
158
+ case 'rgb':
159
+ case 'rgba': return parseRgbChannels(parts);
160
+ case 'hsl':
161
+ case 'hsla': return parseHslChannels(parts);
162
+ case 'hwb': return parseHwbChannels(parts);
163
+ case 'lab': return parseLabChannels(parts);
164
+ case 'lch': return parseLchChannels(parts);
165
+ case 'oklab': return parseOklabChannels(parts);
166
+ case 'oklch': return parseOklchChannels(parts);
167
+ default: return null;
168
+ }
68
169
  }
69
- function rgbToColor(r, g, b) {
70
- const hex = '#' + toHex(r) + toHex(g) + toHex(b);
71
- return hexToNearestAnsi(hex) ?? hex;
170
+ // --- Channel parsers ---
171
+ function parseRgbChannels(parts) {
172
+ if (parts.length < 3)
173
+ return null;
174
+ const r = parseChannelValue(parts[0], 255);
175
+ const g = parseChannelValue(parts[1], 255);
176
+ const b = parseChannelValue(parts[2], 255);
177
+ if (r == null || g == null || b == null)
178
+ return null;
179
+ return [clamp(r, 0, 255), clamp(g, 0, 255), clamp(b, 0, 255)];
72
180
  }
73
- function toHex(n) {
74
- return Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, '0');
181
+ function parseHslChannels(parts) {
182
+ if (parts.length < 3)
183
+ return null;
184
+ const h = parseAngle(parts[0]);
185
+ const s = parsePercent(parts[1]);
186
+ const l = parsePercent(parts[2]);
187
+ if (h == null || s == null || l == null)
188
+ return null;
189
+ return hslToRgb(h, s, l);
190
+ }
191
+ function parseHwbChannels(parts) {
192
+ if (parts.length < 3)
193
+ return null;
194
+ const h = parseAngle(parts[0]);
195
+ let w = parsePercent(parts[1]);
196
+ let b = parsePercent(parts[2]);
197
+ if (h == null || w == null || b == null)
198
+ return null;
199
+ // Normalise when w + b > 1
200
+ if (w + b > 1) {
201
+ const t = w + b;
202
+ w /= t;
203
+ b /= t;
204
+ }
205
+ return hwbToRgb(h, w, b);
206
+ }
207
+ function parseLabChannels(parts) {
208
+ if (parts.length < 3)
209
+ return null;
210
+ const L = parseChannelValue(parts[0], 100);
211
+ const a = parseFloat(parts[1]);
212
+ const b = parseFloat(parts[2]);
213
+ if (L == null || isNaN(a) || isNaN(b))
214
+ return null;
215
+ return labToRgb(L, a, b);
216
+ }
217
+ function parseLchChannels(parts) {
218
+ if (parts.length < 3)
219
+ return null;
220
+ const L = parseChannelValue(parts[0], 100);
221
+ const C = parseFloat(parts[1]);
222
+ const H = parseAngle(parts[2]);
223
+ if (L == null || isNaN(C) || H == null)
224
+ return null;
225
+ // LCH → LAB
226
+ const a = C * Math.cos(H * Math.PI / 180);
227
+ const b = C * Math.sin(H * Math.PI / 180);
228
+ return labToRgb(L, a, b);
229
+ }
230
+ function parseOklabChannels(parts) {
231
+ if (parts.length < 3)
232
+ return null;
233
+ const L = parseChannelValue(parts[0], 1);
234
+ const a = parseFloat(parts[1]);
235
+ const b = parseFloat(parts[2]);
236
+ if (L == null || isNaN(a) || isNaN(b))
237
+ return null;
238
+ return oklabToRgb(L, a, b);
239
+ }
240
+ function parseOklchChannels(parts) {
241
+ if (parts.length < 3)
242
+ return null;
243
+ const L = parseChannelValue(parts[0], 1);
244
+ const C = parseFloat(parts[1]);
245
+ const H = parseAngle(parts[2]);
246
+ if (L == null || isNaN(C) || H == null)
247
+ return null;
248
+ // OKLCH → OKLAB
249
+ const a = C * Math.cos(H * Math.PI / 180);
250
+ const b = C * Math.sin(H * Math.PI / 180);
251
+ return oklabToRgb(L, a, b);
75
252
  }
253
+ // --- Value parsers ---
254
+ /** Parse a number or percentage. maxVal is what 100% maps to. */
255
+ function parseChannelValue(s, maxVal) {
256
+ s = s.trim();
257
+ if (s === 'none')
258
+ return 0;
259
+ if (s.endsWith('%')) {
260
+ const v = parseFloat(s);
261
+ return isNaN(v) ? null : (v / 100) * maxVal;
262
+ }
263
+ const v = parseFloat(s);
264
+ return isNaN(v) ? null : v;
265
+ }
266
+ /** Parse a percentage to 0-1. */
267
+ function parsePercent(s) {
268
+ s = s.trim();
269
+ if (s === 'none')
270
+ return 0;
271
+ if (s.endsWith('%')) {
272
+ const v = parseFloat(s);
273
+ return isNaN(v) ? null : v / 100;
274
+ }
275
+ // Allow raw 0-1 values for oklab/oklch
276
+ const v = parseFloat(s);
277
+ return isNaN(v) ? null : v;
278
+ }
279
+ /** Parse an angle value with unit support. Returns degrees. */
280
+ function parseAngle(s) {
281
+ s = s.trim();
282
+ if (s === 'none')
283
+ return 0;
284
+ if (s.endsWith('grad'))
285
+ return parseFloat(s) * 0.9;
286
+ if (s.endsWith('rad'))
287
+ return parseFloat(s) * 180 / Math.PI;
288
+ if (s.endsWith('turn'))
289
+ return parseFloat(s) * 360;
290
+ if (s.endsWith('deg'))
291
+ return parseFloat(s);
292
+ // Bare number = degrees
293
+ const v = parseFloat(s);
294
+ return isNaN(v) ? null : v;
295
+ }
296
+ // --- Colour space conversions ---
76
297
  function hslToRgb(h, s, l) {
298
+ h = ((h % 360) + 360) % 360; // normalise hue
77
299
  if (s === 0) {
78
300
  const v = Math.round(l * 255);
79
301
  return [v, v, v];
@@ -112,7 +334,94 @@ function hslToRgb(h, s, l) {
112
334
  Math.round((b + m) * 255),
113
335
  ];
114
336
  }
115
- // CSS Level 4 named colors (subset most commonly used)
337
+ function hwbToRgb(h, w, b) {
338
+ // Get pure hue colour
339
+ const [hr, hg, hb] = hslToRgb(h, 1, 0.5);
340
+ const factor = 1 - w - b;
341
+ return [
342
+ Math.round((hr / 255 * factor + w) * 255),
343
+ Math.round((hg / 255 * factor + w) * 255),
344
+ Math.round((hb / 255 * factor + w) * 255),
345
+ ];
346
+ }
347
+ /** OKLAB → sRGB */
348
+ function oklabToRgb(L, a, b) {
349
+ // Oklab → LMS
350
+ const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
351
+ const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
352
+ const s_ = L - 0.0894841775 * a - 1.2914855480 * b;
353
+ const l = l_ * l_ * l_;
354
+ const m = m_ * m_ * m_;
355
+ const s = s_ * s_ * s_;
356
+ // LMS → linear sRGB
357
+ const lr = 4.0767416621 * l - 3.3077363322 * m + 0.2309101289 * s;
358
+ const lg = -1.2684380046 * l + 2.6097574011 * m - 0.3413193761 * s;
359
+ const lb = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
360
+ // Linear sRGB → sRGB (gamma) and scale to 0-255
361
+ return [
362
+ Math.round(clamp(linearToSrgb(lr), 0, 1) * 255),
363
+ Math.round(clamp(linearToSrgb(lg), 0, 1) * 255),
364
+ Math.round(clamp(linearToSrgb(lb), 0, 1) * 255),
365
+ ];
366
+ }
367
+ /** LAB (CIELAB) → sRGB */
368
+ function labToRgb(L, a, b) {
369
+ // LAB → XYZ-D50
370
+ const fy = (L + 16) / 116;
371
+ const fx = a / 500 + fy;
372
+ const fz = fy - b / 200;
373
+ const D50_X = 0.3457 / 0.3585;
374
+ const D50_Y = 1.0;
375
+ const D50_Z = (1 - 0.3457 - 0.3585) / 0.3585;
376
+ const x = (fx > 6 / 29 ? fx * fx * fx : (116 * fx - 16) / 903.3) * D50_X;
377
+ const y = (L > 8 ? ((L + 16) / 116) ** 3 : L / 903.3) * D50_Y;
378
+ const z = (fz > 6 / 29 ? fz * fz * fz : (116 * fz - 16) / 903.3) * D50_Z;
379
+ // XYZ-D50 → XYZ-D65 (Bradford chromatic adaptation)
380
+ const xd = 0.9554734527 * x - 0.0230985368 * y + 0.0632593086 * z;
381
+ const yd = -0.0283697093 * x + 1.0099954580 * y + 0.0210415381 * z;
382
+ const zd = 0.0123140016 * x - 0.0205076964 * y + 1.3303899330 * z;
383
+ // XYZ-D65 → linear sRGB
384
+ const lr = 3.2404541621 * xd - 1.5371385940 * yd - 0.4985314096 * zd;
385
+ const lg = -0.9692660305 * xd + 1.8760108454 * yd + 0.0415560175 * zd;
386
+ const lb = 0.0556434309 * xd - 0.2040259135 * yd + 1.0572251882 * zd;
387
+ return [
388
+ Math.round(clamp(linearToSrgb(lr), 0, 1) * 255),
389
+ Math.round(clamp(linearToSrgb(lg), 0, 1) * 255),
390
+ Math.round(clamp(linearToSrgb(lb), 0, 1) * 255),
391
+ ];
392
+ }
393
+ /** Linear sRGB → sRGB gamma correction */
394
+ function linearToSrgb(v) {
395
+ if (v <= 0.0031308)
396
+ return 12.92 * v;
397
+ return 1.055 * Math.pow(v, 1 / 2.4) - 0.055;
398
+ }
399
+ // --- Helpers ---
400
+ function clamp(v, min, max) {
401
+ return Math.max(min, Math.min(max, v));
402
+ }
403
+ function expandHex(hex) {
404
+ const h = hex.slice(1);
405
+ if (h.length === 3)
406
+ return '#' + h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
407
+ if (h.length === 4)
408
+ return '#' + h[0] + h[0] + h[1] + h[1] + h[2] + h[2] + h[3] + h[3];
409
+ return hex;
410
+ }
411
+ function rgbToColor(r, g, b) {
412
+ // Computed colours (rgb(), hsl(), oklch(), …) stay truecolor like hex —
413
+ // only keywords map to themeable ANSI names.
414
+ return '#' + toHex(r) + toHex(g) + toHex(b);
415
+ }
416
+ function toHex(n) {
417
+ return Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, '0');
418
+ }
419
+ // --- ANSI colours ---
420
+ const ANSI_COLORS = {
421
+ black: 'black', red: 'red', green: 'green', yellow: 'yellow',
422
+ blue: 'blue', magenta: 'magenta', cyan: 'cyan', white: 'white',
423
+ };
424
+ // --- CSS named colours (all 148) ---
116
425
  const CSS_NAMED_COLORS = {
117
426
  aliceblue: '#f0f8ff', antiquewhite: '#faebd7', aqua: '#00ffff', aquamarine: '#7fffd4',
118
427
  azure: '#f0ffff', beige: '#f5f5dc', bisque: '#ffe4c4', blanchedalmond: '#ffebcd',
@@ -1,4 +1,6 @@
1
1
  import { TermNode } from '../renderer/node.js';
2
+ import { NodeMap } from '../utils/node-map.js';
3
+ export type StyleMap = NodeMap<ResolvedStyle>;
2
4
  import { CSSStyleSheet } from './parser.js';
3
5
  import { type MediaContext } from './media.js';
4
6
  export interface ResolvedStyle {
@@ -9,7 +11,7 @@ export interface ResolvedStyle {
9
11
  underline: boolean;
10
12
  strikethrough: boolean;
11
13
  dim: boolean;
12
- display: 'block' | 'inline' | 'inline-block' | 'flex' | 'grid' | 'table' | 'table-row' | 'table-cell' | 'none' | 'contents';
14
+ display: 'block' | 'inline' | 'inline-block' | 'flex' | 'grid' | 'table' | 'inline-table' | 'table-row' | 'table-cell' | 'table-row-group' | 'table-header-group' | 'table-footer-group' | 'table-caption' | 'table-column-group' | 'table-column' | 'none' | 'contents';
13
15
  flexDirection: 'row' | 'column' | 'row-reverse' | 'column-reverse';
14
16
  justifyContent: 'start' | 'end' | 'center' | 'space-between' | 'space-around' | 'space-evenly';
15
17
  alignItems: 'start' | 'end' | 'center' | 'stretch';
@@ -36,28 +38,53 @@ export interface ResolvedStyle {
36
38
  order: number;
37
39
  gridTemplateColumns: string | null;
38
40
  gridTemplateRows: string | null;
41
+ gridColumnStart: number | null;
42
+ gridColumnEnd: number | null;
43
+ gridColumnSpan: number | null;
44
+ gridRowStart: number | null;
45
+ gridRowEnd: number | null;
46
+ gridRowSpan: number | null;
47
+ gridTemplateAreas: string | null;
48
+ gridArea: string | null;
39
49
  animationName: string | null;
40
50
  animationDuration: number;
41
51
  animationIterationCount: number;
42
- borderStyle: 'none' | 'single' | 'double' | 'rounded' | 'heavy';
52
+ animationTimingFunction: string;
53
+ transitionProperty: string | null;
54
+ transitionDuration: number;
55
+ transitionTimingFunction: string;
56
+ borderStyle: 'none' | 'single' | 'double' | 'rounded' | 'heavy' | 'ascii' | 'eighth-cell-inner' | 'eighth-cell-outer' | 'half-cell-inner' | 'half-cell-outer' | 'full-cell';
57
+ borderCorner: 'none' | 'h' | 'v';
43
58
  borderColor: string;
44
59
  borderTop: boolean;
45
60
  borderRight: boolean;
46
61
  borderBottom: boolean;
47
62
  borderLeft: boolean;
63
+ boxSizing: 'border-box' | 'content-box';
48
64
  overflow: 'visible' | 'hidden' | 'scroll' | 'auto';
49
65
  textOverflow: 'clip' | 'ellipsis' | 'ellipsis-middle';
50
66
  whiteSpace: 'normal' | 'nowrap' | 'pre';
67
+ wordBreak: 'normal' | 'break-all';
68
+ opacity: number;
51
69
  textAlign: 'left' | 'center' | 'right';
52
- position: 'static' | 'relative' | 'absolute' | 'fixed';
70
+ textTransform: 'none' | 'uppercase' | 'lowercase' | 'capitalize';
71
+ position: 'static' | 'relative' | 'absolute' | 'fixed' | 'sticky';
53
72
  top: number | null;
54
73
  right: number | null;
55
74
  bottom: number | null;
56
75
  left: number | null;
57
76
  zIndex: number;
58
77
  visibility: 'visible' | 'hidden';
78
+ captionSide: 'top' | 'bottom';
79
+ tableLayout: 'auto' | 'fixed';
80
+ verticalAlign: 'top' | 'middle' | 'bottom' | 'baseline';
81
+ borderCollapse: 'separate' | 'collapse';
82
+ borderSpacingH: number;
83
+ borderSpacingV: number;
84
+ emptyCells: 'show' | 'hide';
59
85
  }
60
86
  export declare function defaultStyle(tag?: string): ResolvedStyle;
61
87
  export declare function resolveStyles(root: TermNode, stylesheet: CSSStyleSheet, media?: MediaContext, availWidth?: number, availHeight?: number): Map<number, ResolvedStyle>;
62
88
  export declare function filterByMedia(stylesheet: CSSStyleSheet, context: MediaContext): CSSStyleSheet;
63
- export declare function resolveNode(node: TermNode, stylesheet: CSSStyleSheet, styles: Map<number, ResolvedStyle>, variables: Map<number, Map<string, string>>): void;
89
+ export declare function resolveNode(node: TermNode, stylesheet: CSSStyleSheet, styles: Map<number, ResolvedStyle>, variables: Map<number, Map<string, string>>, scheme?: 'dark' | 'light'): void;
90
+ export declare function applyDeclaration(style: ResolvedStyle, property: string, value: string, scheme?: 'dark' | 'light'): void;