@svelterm/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.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -0
  3. package/dist/src/components/spinner.d.ts +11 -0
  4. package/dist/src/components/spinner.js +19 -0
  5. package/dist/src/components/text-buffer.d.ts +21 -0
  6. package/dist/src/components/text-buffer.js +87 -0
  7. package/dist/src/css/animation-runner.d.ts +17 -0
  8. package/dist/src/css/animation-runner.js +72 -0
  9. package/dist/src/css/animation.d.ts +5 -0
  10. package/dist/src/css/animation.js +6 -0
  11. package/dist/src/css/calc.d.ts +5 -0
  12. package/dist/src/css/calc.js +130 -0
  13. package/dist/src/css/color.d.ts +1 -0
  14. package/dist/src/css/color.js +157 -0
  15. package/dist/src/css/compute.d.ts +63 -0
  16. package/dist/src/css/compute.js +606 -0
  17. package/dist/src/css/defaults.d.ts +8 -0
  18. package/dist/src/css/defaults.js +44 -0
  19. package/dist/src/css/incremental.d.ts +9 -0
  20. package/dist/src/css/incremental.js +46 -0
  21. package/dist/src/css/index.d.ts +5 -0
  22. package/dist/src/css/index.js +3 -0
  23. package/dist/src/css/media.d.ts +11 -0
  24. package/dist/src/css/media.js +59 -0
  25. package/dist/src/css/parser.d.ts +20 -0
  26. package/dist/src/css/parser.js +241 -0
  27. package/dist/src/css/selector.d.ts +17 -0
  28. package/dist/src/css/selector.js +272 -0
  29. package/dist/src/css/specificity.d.ts +7 -0
  30. package/dist/src/css/specificity.js +89 -0
  31. package/dist/src/css/values.d.ts +17 -0
  32. package/dist/src/css/values.js +58 -0
  33. package/dist/src/css/variables.d.ts +6 -0
  34. package/dist/src/css/variables.js +42 -0
  35. package/dist/src/debug/console.d.ts +16 -0
  36. package/dist/src/debug/console.js +65 -0
  37. package/dist/src/debug/server.d.ts +22 -0
  38. package/dist/src/debug/server.js +90 -0
  39. package/dist/src/headless.d.ts +21 -0
  40. package/dist/src/headless.js +26 -0
  41. package/dist/src/index.d.ts +18 -0
  42. package/dist/src/index.js +485 -0
  43. package/dist/src/input/dispatch.d.ts +18 -0
  44. package/dist/src/input/dispatch.js +70 -0
  45. package/dist/src/input/focus.d.ts +18 -0
  46. package/dist/src/input/focus.js +81 -0
  47. package/dist/src/input/hit.d.ts +3 -0
  48. package/dist/src/input/hit.js +29 -0
  49. package/dist/src/input/keyboard.d.ts +9 -0
  50. package/dist/src/input/keyboard.js +100 -0
  51. package/dist/src/input/mouse.d.ts +7 -0
  52. package/dist/src/input/mouse.js +35 -0
  53. package/dist/src/input/scroll.d.ts +2 -0
  54. package/dist/src/input/scroll.js +24 -0
  55. package/dist/src/layout/cache.d.ts +4 -0
  56. package/dist/src/layout/cache.js +8 -0
  57. package/dist/src/layout/engine.d.ts +9 -0
  58. package/dist/src/layout/engine.js +455 -0
  59. package/dist/src/layout/flex.d.ts +4 -0
  60. package/dist/src/layout/flex.js +30 -0
  61. package/dist/src/layout/incremental.d.ts +8 -0
  62. package/dist/src/layout/incremental.js +58 -0
  63. package/dist/src/layout/size.d.ts +2 -0
  64. package/dist/src/layout/size.js +25 -0
  65. package/dist/src/layout/text.d.ts +7 -0
  66. package/dist/src/layout/text.js +52 -0
  67. package/dist/src/render/ansi.d.ts +23 -0
  68. package/dist/src/render/ansi.js +108 -0
  69. package/dist/src/render/border.d.ts +4 -0
  70. package/dist/src/render/border.js +60 -0
  71. package/dist/src/render/buffer.d.ts +23 -0
  72. package/dist/src/render/buffer.js +70 -0
  73. package/dist/src/render/context.d.ts +19 -0
  74. package/dist/src/render/context.js +98 -0
  75. package/dist/src/render/diff.d.ts +2 -0
  76. package/dist/src/render/diff.js +53 -0
  77. package/dist/src/render/incremental-paint.d.ts +10 -0
  78. package/dist/src/render/incremental-paint.js +94 -0
  79. package/dist/src/render/paint-text.d.ts +29 -0
  80. package/dist/src/render/paint-text.js +120 -0
  81. package/dist/src/render/paint.d.ts +5 -0
  82. package/dist/src/render/paint.js +220 -0
  83. package/dist/src/render/queue.d.ts +24 -0
  84. package/dist/src/render/queue.js +54 -0
  85. package/dist/src/render/scrollbar.d.ts +3 -0
  86. package/dist/src/render/scrollbar.js +19 -0
  87. package/dist/src/render/snapshot.d.ts +18 -0
  88. package/dist/src/render/snapshot.js +126 -0
  89. package/dist/src/renderer/default.d.ts +3 -0
  90. package/dist/src/renderer/default.js +3 -0
  91. package/dist/src/renderer/index.d.ts +11 -0
  92. package/dist/src/renderer/index.js +116 -0
  93. package/dist/src/renderer/node.d.ts +44 -0
  94. package/dist/src/renderer/node.js +153 -0
  95. package/dist/src/terminal/screen.d.ts +10 -0
  96. package/dist/src/terminal/screen.js +31 -0
  97. package/dist/src/terminal/stdin-router.d.ts +31 -0
  98. package/dist/src/terminal/stdin-router.js +133 -0
  99. package/package.json +64 -0
@@ -0,0 +1,606 @@
1
+ import { matchesSelector } from './selector.js';
2
+ import { resolveColor } from './color.js';
3
+ import { parseCellValue, parseSizeValue, parseJustify, parseAlign } from './values.js';
4
+ import { collectVariables, resolveVar } from './variables.js';
5
+ import { computeSpecificity, compareSpecificity } from './specificity.js';
6
+ import { evaluateMediaQuery } from './media.js';
7
+ import { computeLayout } from '../layout/engine.js';
8
+ const DEFAULT_MEDIA = {
9
+ colorScheme: 'dark',
10
+ displayMode: 'terminal',
11
+ width: 80,
12
+ height: 24,
13
+ };
14
+ const INLINE_ELEMENTS = new Set(['span', 'a', 'strong', 'em', 'b', 'i', 'u', 'code', 'small', 'sub', 'sup']);
15
+ const TABLE_ELEMENTS = {
16
+ table: 'table', tr: 'table-row', td: 'table-cell', th: 'table-cell',
17
+ };
18
+ function defaultDisplay(tag) {
19
+ if (!tag)
20
+ return 'block';
21
+ if (INLINE_ELEMENTS.has(tag))
22
+ return 'inline';
23
+ if (tag in TABLE_ELEMENTS)
24
+ return TABLE_ELEMENTS[tag];
25
+ return 'block';
26
+ }
27
+ export function defaultStyle(tag) {
28
+ return {
29
+ fg: 'default', bg: 'default',
30
+ bold: false, italic: false, underline: false, strikethrough: false, dim: false,
31
+ display: defaultDisplay(tag),
32
+ flexDirection: 'row',
33
+ justifyContent: 'start', alignItems: 'start', alignSelf: 'auto',
34
+ gap: 0,
35
+ paddingTop: 0, paddingRight: 0, paddingBottom: 0, paddingLeft: 0,
36
+ width: null, height: null,
37
+ minWidth: null, minHeight: null, maxWidth: null, maxHeight: null,
38
+ marginTop: 0, marginRight: 0, marginBottom: 0, marginLeft: 0,
39
+ flexGrow: 0, flexShrink: 1, flexBasis: 'auto', flexWrap: 'nowrap', order: 0,
40
+ gridTemplateColumns: null, gridTemplateRows: null,
41
+ animationName: null, animationDuration: 0, animationIterationCount: 1,
42
+ borderStyle: 'none', borderColor: 'default',
43
+ borderTop: true, borderRight: true, borderBottom: true, borderLeft: true,
44
+ overflow: 'visible',
45
+ textOverflow: 'clip',
46
+ whiteSpace: 'normal',
47
+ textAlign: 'left',
48
+ position: 'static',
49
+ top: null, right: null, bottom: null, left: null,
50
+ zIndex: 0,
51
+ visibility: 'visible',
52
+ };
53
+ }
54
+ export function resolveStyles(root, stylesheet, media, availWidth, availHeight) {
55
+ const hasContainerRules = stylesheet.rules.some(r => r.container);
56
+ // Filter by media if context provided; always filter @supports
57
+ const filtered = media ? filterByMedia(stylesheet, media) : filterSupports(stylesheet);
58
+ if (!hasContainerRules) {
59
+ // Simple path: no container queries
60
+ const variables = collectVariables(root, filtered);
61
+ const styles = new Map();
62
+ resolveNode(root, filtered, styles, variables);
63
+ return styles;
64
+ }
65
+ // Two-pass for container queries:
66
+ // Pass 1: resolve without @container rules to get initial layout
67
+ const withoutContainer = filterContainerRules(filtered, false);
68
+ const variables1 = collectVariables(root, withoutContainer);
69
+ const styles1 = new Map();
70
+ resolveNode(root, withoutContainer, styles1, variables1);
71
+ // Compute layout to get container dimensions
72
+ const layout = computeLayout(root, styles1, availWidth ?? media?.width ?? 80, availHeight ?? media?.height ?? 24);
73
+ // Pass 2: evaluate container rules against computed layout
74
+ const containerRules = filtered.rules.filter(r => r.container);
75
+ const matchingContainerRules = containerRules.filter(r => evaluateContainerQuery(r, root, layout));
76
+ if (matchingContainerRules.length === 0)
77
+ return styles1;
78
+ // Re-resolve with matching container rules included
79
+ const withMatchingContainers = {
80
+ rules: [...withoutContainer.rules, ...matchingContainerRules],
81
+ keyframes: filtered.keyframes,
82
+ };
83
+ const variables2 = collectVariables(root, withMatchingContainers);
84
+ const styles2 = new Map();
85
+ resolveNode(root, withMatchingContainers, styles2, variables2);
86
+ return styles2;
87
+ }
88
+ function filterSupports(stylesheet) {
89
+ const rules = stylesheet.rules.filter(rule => {
90
+ if (rule.supports && !evaluateSupports(rule.supports))
91
+ return false;
92
+ return true;
93
+ });
94
+ return { rules, keyframes: stylesheet.keyframes };
95
+ }
96
+ export function filterByMedia(stylesheet, context) {
97
+ const rules = stylesheet.rules.filter(rule => {
98
+ if (rule.media && !evaluateMediaQuery(rule.media, context))
99
+ return false;
100
+ if (rule.supports && !evaluateSupports(rule.supports))
101
+ return false;
102
+ return true;
103
+ });
104
+ return { rules, keyframes: stylesheet.keyframes };
105
+ }
106
+ const SUPPORTED_PROPERTIES = new Set([
107
+ 'display', 'flex-direction', 'justify-content', 'align-items', 'align-self',
108
+ 'gap', 'flex-grow', 'flex-shrink', 'flex-wrap', 'flex', 'order',
109
+ 'grid-template-columns',
110
+ 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
111
+ 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
112
+ 'width', 'height', 'min-width', 'min-height', 'max-width', 'max-height',
113
+ 'color', 'background-color', 'background',
114
+ 'font-weight', 'font-style', 'text-decoration', 'text-align', 'text-overflow',
115
+ 'white-space', 'overflow', 'visibility', 'opacity',
116
+ 'border', 'border-style', 'border-color',
117
+ 'position', 'top', 'right', 'bottom', 'left', 'z-index',
118
+ ]);
119
+ function filterContainerRules(stylesheet, include) {
120
+ const rules = stylesheet.rules.filter(r => include ? !!r.container : !r.container);
121
+ return { rules, keyframes: stylesheet.keyframes };
122
+ }
123
+ function evaluateContainerQuery(rule, root, layout) {
124
+ if (!rule.container)
125
+ return false;
126
+ const condition = rule.container;
127
+ const colonIdx = condition.indexOf(':');
128
+ if (colonIdx === -1)
129
+ return false;
130
+ const feature = condition.substring(0, colonIdx).trim();
131
+ const value = parseInt(condition.substring(colonIdx + 1).trim());
132
+ // Find nodes that match the rule's selectors, then check their container ancestors
133
+ const matchingNodes = findMatchingNodes(root, rule.selectors);
134
+ for (const node of matchingNodes) {
135
+ if (hasMatchingContainerAncestor(node, feature, value, layout))
136
+ return true;
137
+ }
138
+ return false;
139
+ }
140
+ function findMatchingNodes(root, selectors) {
141
+ const result = [];
142
+ walkNodes(root, (node) => {
143
+ if (node.nodeType === 'element' && selectors.some(sel => matchesSelector(node, sel))) {
144
+ result.push(node);
145
+ }
146
+ });
147
+ return result;
148
+ }
149
+ function walkNodes(node, fn) {
150
+ fn(node);
151
+ for (const child of node.children)
152
+ walkNodes(child, fn);
153
+ }
154
+ function hasMatchingContainerAncestor(node, feature, value, layout) {
155
+ // Check nearest ancestor with a layout box (closest container)
156
+ const ancestor = node.parent;
157
+ if (!ancestor)
158
+ return false;
159
+ const box = layout.get(ancestor.id);
160
+ if (!box)
161
+ return false;
162
+ switch (feature) {
163
+ case 'min-width': return box.width >= value;
164
+ case 'max-width': return box.width <= value;
165
+ case 'min-height': return box.height >= value;
166
+ case 'max-height': return box.height <= value;
167
+ }
168
+ return false;
169
+ }
170
+ function evaluateSupports(condition) {
171
+ const colonIdx = condition.indexOf(':');
172
+ if (colonIdx === -1)
173
+ return false;
174
+ const property = condition.substring(0, colonIdx).trim();
175
+ return SUPPORTED_PROPERTIES.has(property);
176
+ }
177
+ export function resolveNode(node, stylesheet, styles, variables) {
178
+ if (node.nodeType === 'element') {
179
+ const vars = variables.get(node.id) ?? new Map();
180
+ const parentStyle = node.parent ? styles.get(node.parent.id) : undefined;
181
+ const resolved = computeStyle(node, stylesheet, vars, parentStyle);
182
+ styles.set(node.id, resolved);
183
+ node.cache.resolvedStyle = resolved;
184
+ node.cache.classAttr = node.attributes.get('class') ?? '';
185
+ }
186
+ for (const child of node.children) {
187
+ resolveNode(child, stylesheet, styles, variables);
188
+ }
189
+ }
190
+ function computeStyle(node, stylesheet, vars, parentStyle) {
191
+ const style = defaultStyle(node.tag);
192
+ // Collect all matching declarations with specificity
193
+ const scored = [];
194
+ let order = 0;
195
+ for (const rule of stylesheet.rules) {
196
+ for (const selector of rule.selectors) {
197
+ if (!matchesSelector(node, selector))
198
+ continue;
199
+ const specificity = computeSpecificity(selector);
200
+ for (const decl of rule.declarations) {
201
+ if (decl.property.startsWith('--'))
202
+ continue;
203
+ scored.push({
204
+ property: decl.property,
205
+ value: resolveVar(decl.value, vars),
206
+ specificity,
207
+ order: order++,
208
+ });
209
+ }
210
+ }
211
+ }
212
+ // Sort by specificity (ascending), then by source order (ascending)
213
+ // Later application = higher priority, so lower specificity first
214
+ scored.sort((a, b) => {
215
+ const specCmp = compareSpecificity(a.specificity, b.specificity);
216
+ return specCmp !== 0 ? specCmp : a.order - b.order;
217
+ });
218
+ for (const decl of scored) {
219
+ if (decl.value === 'inherit' && parentStyle) {
220
+ applyInherit(style, decl.property, parentStyle);
221
+ }
222
+ else if (decl.value === 'initial') {
223
+ applyInitial(style, decl.property, node.tag);
224
+ }
225
+ else if (decl.value === 'unset') {
226
+ if (INHERITABLE_PROPERTIES.has(decl.property) && parentStyle) {
227
+ applyInherit(style, decl.property, parentStyle);
228
+ }
229
+ else {
230
+ applyInitial(style, decl.property, node.tag);
231
+ }
232
+ }
233
+ else {
234
+ applyDeclaration(style, decl.property, decl.value);
235
+ }
236
+ }
237
+ return style;
238
+ }
239
+ const INHERITABLE_PROPERTIES = new Set([
240
+ 'color', 'font-weight', 'font-style', 'text-decoration',
241
+ 'white-space', 'text-align', 'visibility', 'opacity',
242
+ ]);
243
+ function applyInherit(style, property, parentStyle) {
244
+ switch (property) {
245
+ case 'color':
246
+ style.fg = parentStyle.fg;
247
+ break;
248
+ case 'background-color':
249
+ case 'background':
250
+ style.bg = parentStyle.bg;
251
+ break;
252
+ case 'font-weight':
253
+ style.bold = parentStyle.bold;
254
+ break;
255
+ case 'font-style':
256
+ style.italic = parentStyle.italic;
257
+ break;
258
+ case 'text-decoration':
259
+ style.underline = parentStyle.underline;
260
+ style.strikethrough = parentStyle.strikethrough;
261
+ break;
262
+ case 'white-space':
263
+ style.whiteSpace = parentStyle.whiteSpace;
264
+ break;
265
+ case 'text-align':
266
+ style.textAlign = parentStyle.textAlign;
267
+ break;
268
+ case 'visibility':
269
+ style.visibility = parentStyle.visibility;
270
+ break;
271
+ case 'opacity':
272
+ style.dim = parentStyle.dim;
273
+ break;
274
+ }
275
+ }
276
+ function applyInitial(style, property, tag) {
277
+ const initial = defaultStyle(tag);
278
+ switch (property) {
279
+ case 'color':
280
+ style.fg = initial.fg;
281
+ break;
282
+ case 'background-color':
283
+ case 'background':
284
+ style.bg = initial.bg;
285
+ break;
286
+ case 'font-weight':
287
+ style.bold = initial.bold;
288
+ break;
289
+ case 'font-style':
290
+ style.italic = initial.italic;
291
+ break;
292
+ case 'text-decoration':
293
+ style.underline = initial.underline;
294
+ style.strikethrough = initial.strikethrough;
295
+ break;
296
+ }
297
+ }
298
+ function applyDeclaration(style, property, value) {
299
+ switch (property) {
300
+ case 'color':
301
+ style.fg = resolveColor(value);
302
+ break;
303
+ case 'background-color':
304
+ case 'background':
305
+ style.bg = resolveColor(value);
306
+ break;
307
+ case 'font-weight':
308
+ style.bold = value === 'bold' || parseInt(value) >= 700;
309
+ break;
310
+ case 'font-style':
311
+ style.italic = value === 'italic';
312
+ break;
313
+ case 'text-decoration':
314
+ if (value.includes('underline'))
315
+ style.underline = true;
316
+ if (value.includes('line-through'))
317
+ style.strikethrough = true;
318
+ break;
319
+ case 'display':
320
+ if (['block', 'inline', 'inline-block', 'flex', 'grid', 'table', 'table-row', 'table-cell', 'none'].includes(value)) {
321
+ style.display = value;
322
+ }
323
+ break;
324
+ case 'flex-direction':
325
+ if (['row', 'column', 'row-reverse', 'column-reverse'].includes(value)) {
326
+ style.flexDirection = value;
327
+ }
328
+ break;
329
+ case 'justify-content':
330
+ style.justifyContent = parseJustify(value);
331
+ break;
332
+ case 'align-items':
333
+ style.alignItems = parseAlign(value);
334
+ break;
335
+ case 'align-self':
336
+ if (['auto', 'start', 'end', 'center', 'stretch', 'flex-start', 'flex-end'].includes(value)) {
337
+ style.alignSelf = value === 'flex-start' ? 'start' : value === 'flex-end' ? 'end' : value;
338
+ }
339
+ break;
340
+ case 'gap':
341
+ style.gap = parseCellValue(value);
342
+ break;
343
+ case 'padding': {
344
+ const parts = value.split(/\s+/);
345
+ const parsed = parts.map(parseSizeOrCell);
346
+ if (parsed.length === 1) {
347
+ style.paddingTop = style.paddingRight = style.paddingBottom = style.paddingLeft = parsed[0];
348
+ }
349
+ else if (parsed.length === 2) {
350
+ style.paddingTop = style.paddingBottom = parsed[0];
351
+ style.paddingRight = style.paddingLeft = parsed[1];
352
+ }
353
+ else if (parsed.length === 3) {
354
+ style.paddingTop = parsed[0];
355
+ style.paddingRight = style.paddingLeft = parsed[1];
356
+ style.paddingBottom = parsed[2];
357
+ }
358
+ else if (parsed.length >= 4) {
359
+ style.paddingTop = parsed[0];
360
+ style.paddingRight = parsed[1];
361
+ style.paddingBottom = parsed[2];
362
+ style.paddingLeft = parsed[3];
363
+ }
364
+ break;
365
+ }
366
+ case 'padding-top':
367
+ style.paddingTop = parseSizeOrCell(value);
368
+ break;
369
+ case 'padding-right':
370
+ style.paddingRight = parseSizeOrCell(value);
371
+ break;
372
+ case 'padding-bottom':
373
+ style.paddingBottom = parseSizeOrCell(value);
374
+ break;
375
+ case 'padding-left':
376
+ style.paddingLeft = parseSizeOrCell(value);
377
+ break;
378
+ case 'width':
379
+ style.width = parseSizeValue(value);
380
+ break;
381
+ case 'height':
382
+ style.height = parseSizeValue(value);
383
+ break;
384
+ case 'min-width':
385
+ style.minWidth = parseCellValue(value);
386
+ break;
387
+ case 'min-height':
388
+ style.minHeight = parseCellValue(value);
389
+ break;
390
+ case 'max-width':
391
+ style.maxWidth = parseCellValue(value);
392
+ break;
393
+ case 'max-height':
394
+ style.maxHeight = parseCellValue(value);
395
+ break;
396
+ case 'margin': {
397
+ const parts = value.split(/\s+/);
398
+ const parsed = parts.map(p => p === 'auto' ? -1 : parseSizeOrCell(p));
399
+ if (parsed.length === 1) {
400
+ style.marginTop = style.marginRight = style.marginBottom = style.marginLeft = parsed[0];
401
+ }
402
+ else if (parsed.length === 2) {
403
+ style.marginTop = style.marginBottom = parsed[0];
404
+ style.marginRight = style.marginLeft = parsed[1];
405
+ }
406
+ else if (parsed.length === 3) {
407
+ style.marginTop = parsed[0];
408
+ style.marginRight = style.marginLeft = parsed[1];
409
+ style.marginBottom = parsed[2];
410
+ }
411
+ else if (parsed.length >= 4) {
412
+ style.marginTop = parsed[0];
413
+ style.marginRight = parsed[1];
414
+ style.marginBottom = parsed[2];
415
+ style.marginLeft = parsed[3];
416
+ }
417
+ break;
418
+ }
419
+ case 'margin-top':
420
+ style.marginTop = value === 'auto' ? -1 : parseSizeOrCell(value);
421
+ break;
422
+ case 'margin-right':
423
+ style.marginRight = value === 'auto' ? -1 : parseSizeOrCell(value);
424
+ break;
425
+ case 'margin-bottom':
426
+ style.marginBottom = value === 'auto' ? -1 : parseSizeOrCell(value);
427
+ break;
428
+ case 'margin-left':
429
+ style.marginLeft = value === 'auto' ? -1 : parseSizeOrCell(value);
430
+ break;
431
+ case 'flex': {
432
+ // flex shorthand: flex: <grow> [<shrink> [<basis>]]
433
+ const flexParts = value.split(/\s+/);
434
+ style.flexGrow = parseFloat(flexParts[0]) || 0;
435
+ if (flexParts.length > 1)
436
+ style.flexShrink = parseFloat(flexParts[1]) || 1;
437
+ if (flexParts.length > 2)
438
+ style.flexBasis = parseFlexBasis(flexParts[2]);
439
+ break;
440
+ }
441
+ case 'flex-basis':
442
+ style.flexBasis = parseFlexBasis(value);
443
+ break;
444
+ case 'flex-grow':
445
+ style.flexGrow = parseFloat(value) || 0;
446
+ break;
447
+ case 'flex-shrink':
448
+ style.flexShrink = parseFloat(value) || 1;
449
+ break;
450
+ case 'flex-wrap':
451
+ style.flexWrap = value === 'wrap' ? 'wrap' : 'nowrap';
452
+ break;
453
+ case 'order':
454
+ style.order = parseInt(value) || 0;
455
+ break;
456
+ case 'grid-template-columns':
457
+ style.gridTemplateColumns = value;
458
+ break;
459
+ case 'grid-template-rows':
460
+ style.gridTemplateRows = value;
461
+ break;
462
+ case 'animation':
463
+ parseAnimationShorthand(style, value);
464
+ break;
465
+ case 'animation-name':
466
+ style.animationName = value === 'none' ? null : value;
467
+ break;
468
+ case 'animation-duration':
469
+ style.animationDuration = parseDuration(value);
470
+ break;
471
+ case 'animation-iteration-count':
472
+ style.animationIterationCount = value === 'infinite' ? Infinity : (parseInt(value) || 1);
473
+ break;
474
+ case 'border':
475
+ if (BORDER_STYLES.has(value))
476
+ style.borderStyle = value;
477
+ break;
478
+ case 'border-style':
479
+ if (BORDER_STYLES.has(value))
480
+ style.borderStyle = value;
481
+ break;
482
+ case 'border-color':
483
+ style.borderColor = value === 'currentColor' ? style.fg : resolveColor(value);
484
+ break;
485
+ case 'border-top':
486
+ setIndividualBorderSide(style, 'borderTop', value);
487
+ break;
488
+ case 'border-right':
489
+ setIndividualBorderSide(style, 'borderRight', value);
490
+ break;
491
+ case 'border-bottom':
492
+ setIndividualBorderSide(style, 'borderBottom', value);
493
+ break;
494
+ case 'border-left':
495
+ setIndividualBorderSide(style, 'borderLeft', value);
496
+ break;
497
+ case 'overflow':
498
+ if (value === 'hidden' || value === 'scroll' || value === 'auto')
499
+ style.overflow = value;
500
+ else
501
+ style.overflow = 'visible';
502
+ break;
503
+ case 'text-overflow':
504
+ style.textOverflow = value === 'ellipsis' ? 'ellipsis'
505
+ : value === 'ellipsis-middle' ? 'ellipsis-middle' : 'clip';
506
+ break;
507
+ case 'white-space':
508
+ if (value === 'nowrap' || value === 'pre')
509
+ style.whiteSpace = value;
510
+ else
511
+ style.whiteSpace = 'normal';
512
+ break;
513
+ case 'text-align':
514
+ if (value === 'center' || value === 'right')
515
+ style.textAlign = value;
516
+ else
517
+ style.textAlign = 'left';
518
+ break;
519
+ case 'position':
520
+ if (value === 'relative' || value === 'absolute' || value === 'fixed')
521
+ style.position = value;
522
+ else
523
+ style.position = 'static';
524
+ break;
525
+ case 'top':
526
+ style.top = parseCellValue(value);
527
+ break;
528
+ case 'right':
529
+ style.right = parseCellValue(value);
530
+ break;
531
+ case 'bottom':
532
+ style.bottom = parseCellValue(value);
533
+ break;
534
+ case 'left':
535
+ style.left = parseCellValue(value);
536
+ break;
537
+ case 'z-index':
538
+ style.zIndex = parseInt(value) || 0;
539
+ break;
540
+ case 'visibility':
541
+ style.visibility = value === 'hidden' ? 'hidden' : 'visible';
542
+ break;
543
+ case 'opacity':
544
+ if (value === 'dim') {
545
+ style.dim = true;
546
+ }
547
+ else {
548
+ const num = parseFloat(value);
549
+ style.dim = !isNaN(num) && num < 1;
550
+ }
551
+ break;
552
+ }
553
+ }
554
+ const BORDER_STYLES = new Set(['none', 'single', 'double', 'rounded', 'heavy']);
555
+ function setIndividualBorderSide(style, side, value) {
556
+ const enabled = value === 'true' || value === '1';
557
+ // When setting individual sides, disable all others first (if this is the first individual side set)
558
+ if (enabled && style.borderTop && style.borderRight && style.borderBottom && style.borderLeft) {
559
+ style.borderTop = false;
560
+ style.borderRight = false;
561
+ style.borderBottom = false;
562
+ style.borderLeft = false;
563
+ }
564
+ style[side] = enabled;
565
+ }
566
+ function parseFlexBasis(value) {
567
+ if (value === 'auto')
568
+ return 'auto';
569
+ const num = parseCellValue(value);
570
+ return num;
571
+ }
572
+ function parseSizeOrCell(value) {
573
+ if (value.endsWith('%'))
574
+ return value;
575
+ if (value.startsWith('calc(') || value.startsWith('min(') || value.startsWith('max(') || value.startsWith('clamp('))
576
+ return value;
577
+ return parseCellValue(value);
578
+ }
579
+ function parseAnimationShorthand(style, value) {
580
+ if (value === 'none') {
581
+ style.animationName = null;
582
+ return;
583
+ }
584
+ const parts = value.split(/\s+/);
585
+ for (const part of parts) {
586
+ if (part.endsWith('s') && !part.endsWith('ss')) {
587
+ style.animationDuration = parseDuration(part);
588
+ }
589
+ else if (part === 'infinite') {
590
+ style.animationIterationCount = Infinity;
591
+ }
592
+ else if (/^\d+$/.test(part)) {
593
+ style.animationIterationCount = parseInt(part);
594
+ }
595
+ else if (!['ease', 'linear', 'ease-in', 'ease-out', 'ease-in-out', 'normal', 'reverse', 'alternate', 'forwards', 'backwards', 'both', 'running', 'paused'].includes(part)) {
596
+ style.animationName = part;
597
+ }
598
+ }
599
+ }
600
+ function parseDuration(value) {
601
+ if (value.endsWith('ms'))
602
+ return parseFloat(value);
603
+ if (value.endsWith('s'))
604
+ return parseFloat(value) * 1000;
605
+ return 0;
606
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Default terminal stylesheet — equivalent to a browser's user-agent stylesheet.
3
+ * Uses standard CSS properties where possible. Terminal-specific values
4
+ * (like `border: single` and `cell` units) are values that browsers naturally ignore.
5
+ *
6
+ * Adapts to dark/light mode via @media (prefers-color-scheme).
7
+ */
8
+ export declare const DEFAULT_STYLESHEET = "\nh1, h2, h3, h4, h5, h6 { font-weight: bold; }\nstrong, b { font-weight: bold; }\nem, i { font-style: italic; }\nu { text-decoration: underline; }\ns, del, strike { text-decoration: line-through; }\na { text-decoration: underline; }\npre { display: flex; }\np { margin-top: 1cell; margin-bottom: 1cell; }\nh1 { margin-top: 1cell; margin-bottom: 1cell; }\nh2 { margin-top: 1cell; margin-bottom: 1cell; }\nh3, h4, h5, h6 { margin-top: 1cell; margin-bottom: 1cell; }\nhr { height: 1cell; width: 100%; margin-top: 1cell; margin-bottom: 1cell; }\nblockquote { margin-left: 2cell; border-left: true; border-style: single; border-color: gray; padding-left: 1cell; }\nli { padding-left: 3cell; }\nul, ol { margin-top: 1cell; margin-bottom: 1cell; }\nmark { background-color: yellow; color: black; }\nkbd { border: single; border-color: gray; padding: 0 1cell; }\nabbr { text-decoration: underline; }\nsamp { color: cyan; }\nvar { font-style: italic; }\ndt { font-weight: bold; }\ndd { margin-left: 2cell; }\nfigure { margin-left: 2cell; margin-right: 2cell; margin-top: 1cell; margin-bottom: 1cell; }\nfigcaption { font-style: italic; }\n\n@media (prefers-color-scheme: dark) {\n code { color: cyan; }\n a { color: #5599ff; }\n mark { background-color: #666600; color: white; }\n}\n\n@media (prefers-color-scheme: light) {\n code { color: #8800cc; }\n a { color: #0044cc; }\n}\n";
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Default terminal stylesheet — equivalent to a browser's user-agent stylesheet.
3
+ * Uses standard CSS properties where possible. Terminal-specific values
4
+ * (like `border: single` and `cell` units) are values that browsers naturally ignore.
5
+ *
6
+ * Adapts to dark/light mode via @media (prefers-color-scheme).
7
+ */
8
+ export const DEFAULT_STYLESHEET = `
9
+ h1, h2, h3, h4, h5, h6 { font-weight: bold; }
10
+ strong, b { font-weight: bold; }
11
+ em, i { font-style: italic; }
12
+ u { text-decoration: underline; }
13
+ s, del, strike { text-decoration: line-through; }
14
+ a { text-decoration: underline; }
15
+ pre { display: flex; }
16
+ p { margin-top: 1cell; margin-bottom: 1cell; }
17
+ h1 { margin-top: 1cell; margin-bottom: 1cell; }
18
+ h2 { margin-top: 1cell; margin-bottom: 1cell; }
19
+ h3, h4, h5, h6 { margin-top: 1cell; margin-bottom: 1cell; }
20
+ hr { height: 1cell; width: 100%; margin-top: 1cell; margin-bottom: 1cell; }
21
+ blockquote { margin-left: 2cell; border-left: true; border-style: single; border-color: gray; padding-left: 1cell; }
22
+ li { padding-left: 3cell; }
23
+ ul, ol { margin-top: 1cell; margin-bottom: 1cell; }
24
+ mark { background-color: yellow; color: black; }
25
+ kbd { border: single; border-color: gray; padding: 0 1cell; }
26
+ abbr { text-decoration: underline; }
27
+ samp { color: cyan; }
28
+ var { font-style: italic; }
29
+ dt { font-weight: bold; }
30
+ dd { margin-left: 2cell; }
31
+ figure { margin-left: 2cell; margin-right: 2cell; margin-top: 1cell; margin-bottom: 1cell; }
32
+ figcaption { font-style: italic; }
33
+
34
+ @media (prefers-color-scheme: dark) {
35
+ code { color: cyan; }
36
+ a { color: #5599ff; }
37
+ mark { background-color: #666600; color: white; }
38
+ }
39
+
40
+ @media (prefers-color-scheme: light) {
41
+ code { color: #8800cc; }
42
+ a { color: #0044cc; }
43
+ }
44
+ `;
@@ -0,0 +1,9 @@
1
+ import { TermNode } from '../renderer/node.js';
2
+ import { CSSStyleSheet } from './parser.js';
3
+ import { type ResolvedStyle } from './compute.js';
4
+ /**
5
+ * Incremental style resolution — re-resolves dirty nodes and their
6
+ * descendants using the same resolveNode function as full resolution.
7
+ * Variables are collected once from the full tree for consistency.
8
+ */
9
+ export declare function resolveStylesIncremental(root: TermNode, stylesheet: CSSStyleSheet, existingStyles: Map<number, ResolvedStyle>, dirtyNodes: Set<TermNode>, onResolve?: (nodeId: number) => void, onLayoutAffected?: (node: TermNode) => void): Map<number, ResolvedStyle>;