@textui/core 0.1.0 → 0.2.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 (60) hide show
  1. package/README.md +17 -20
  2. package/dist/app/app.d.ts +36 -0
  3. package/dist/app/app.d.ts.map +1 -1
  4. package/dist/app/app.js +150 -1
  5. package/dist/core/focus.d.ts.map +1 -1
  6. package/dist/core/focus.js +12 -1
  7. package/dist/core/store.d.ts.map +1 -1
  8. package/dist/core/store.js +2 -10
  9. package/dist/jsx/intrinsics.d.ts +18 -0
  10. package/dist/jsx/intrinsics.d.ts.map +1 -1
  11. package/dist/render/layout.js +10 -2
  12. package/dist/themes/builtin.d.ts +2 -0
  13. package/dist/themes/builtin.d.ts.map +1 -1
  14. package/dist/themes/builtin.js +64 -1
  15. package/dist/themes/dividers.d.ts +14 -0
  16. package/dist/themes/dividers.d.ts.map +1 -0
  17. package/dist/themes/dividers.js +42 -0
  18. package/dist/themes/glyphs.d.ts.map +1 -1
  19. package/dist/themes/glyphs.js +4 -0
  20. package/dist/themes/index.d.ts +1 -0
  21. package/dist/themes/index.d.ts.map +1 -1
  22. package/dist/themes/index.js +1 -0
  23. package/dist/themes/registry.d.ts.map +1 -1
  24. package/dist/themes/registry.js +31 -1
  25. package/dist/types/command.d.ts +18 -1
  26. package/dist/types/command.d.ts.map +1 -1
  27. package/dist/types/input.d.ts +9 -0
  28. package/dist/types/input.d.ts.map +1 -1
  29. package/dist/types/markdown.d.ts +36 -1
  30. package/dist/types/markdown.d.ts.map +1 -1
  31. package/dist/types/style.d.ts +28 -1
  32. package/dist/types/style.d.ts.map +1 -1
  33. package/dist/types/terminal.d.ts +9 -0
  34. package/dist/types/terminal.d.ts.map +1 -1
  35. package/dist/types/theme.d.ts +26 -1
  36. package/dist/types/theme.d.ts.map +1 -1
  37. package/dist/util/markdown.d.ts.map +1 -1
  38. package/dist/util/markdown.js +172 -2
  39. package/dist/util/paths.d.ts +9 -3
  40. package/dist/util/paths.d.ts.map +1 -1
  41. package/dist/util/paths.js +11 -16
  42. package/package.json +5 -5
  43. package/src/app/app.ts +144 -1
  44. package/src/core/focus.ts +12 -1
  45. package/src/core/store.ts +2 -7
  46. package/src/jsx/intrinsics.ts +18 -0
  47. package/src/render/layout.ts +7 -2
  48. package/src/themes/builtin.ts +65 -1
  49. package/src/themes/dividers.ts +48 -0
  50. package/src/themes/glyphs.ts +4 -0
  51. package/src/themes/index.ts +1 -0
  52. package/src/themes/registry.ts +30 -2
  53. package/src/types/command.ts +18 -1
  54. package/src/types/input.ts +9 -0
  55. package/src/types/markdown.ts +38 -2
  56. package/src/types/style.ts +33 -1
  57. package/src/types/terminal.ts +9 -0
  58. package/src/types/theme.ts +29 -1
  59. package/src/util/markdown.ts +202 -3
  60. package/src/util/paths.ts +12 -15
@@ -24,6 +24,7 @@ export const DARK: ThemeDefinition = {
24
24
  border: '#30363d',
25
25
  borderStrong: '#484f58',
26
26
  borderSubtle: '#21262d',
27
+ divider: '#484f58',
27
28
  text: '#e6edf3',
28
29
  muted: '#8b949e',
29
30
  subtle: '#6e7681',
@@ -70,6 +71,7 @@ export const LIGHT: ThemeDefinition = {
70
71
  border: '#d0d7de',
71
72
  borderStrong: '#8c959f',
72
73
  borderSubtle: '#eaeef2',
74
+ divider: '#8c959f',
73
75
  text: '#1f2328',
74
76
  muted: '#656d76',
75
77
  subtle: '#8c959f',
@@ -115,6 +117,7 @@ export const CONSOLE: ThemeDefinition = {
115
117
  surface: '#0a0e14',
116
118
  border: '#3b4252',
117
119
  borderStrong: '#5e81ac',
120
+ divider: '#5e81ac',
118
121
  accent: '#88c0d0',
119
122
  text: '#d8dee9',
120
123
  muted: '#616e88',
@@ -145,6 +148,59 @@ export const CONSOLE: ThemeDefinition = {
145
148
  export const PAPER: ThemeDefinition = {
146
149
  id: 'paper',
147
150
  name: 'Paper',
151
+ appearance: 'dark',
152
+ extends: 'dark',
153
+ border: 'none',
154
+ density: 'airy',
155
+ colors: {
156
+ canvas: 'default',
157
+ surface: 'default',
158
+ surfaceAlt: 'default',
159
+ overlay: 'default',
160
+ border: 'default',
161
+ borderStrong: 'default',
162
+ borderSubtle: 'default',
163
+ divider: 'default',
164
+ text: 'default',
165
+ muted: '#9b949e',
166
+ subtle: '#a7a7a7',
167
+ inverted: '#0d1117',
168
+ accent: '#58a6ff',
169
+ primary: '#388bfd',
170
+ secondary: '#a371f7',
171
+ success: '#3fb950',
172
+ warning: '#d29922',
173
+ danger: '#f85149',
174
+ info: '#58a6ff',
175
+ onAccent: '#0d1117',
176
+ onDefault: '#0d1117',
177
+ onPrimary: '#0d1117',
178
+ onSecondary: '#0d1117',
179
+ onMuted: '#0d1117',
180
+ onSuccess: '#0d1117',
181
+ onInfo: '#0d1117',
182
+ onWarning: '#0d1117',
183
+ onDanger: '#0d1117',
184
+ hover: '#1f2937',
185
+ active: '#264466',
186
+ selected: '#1f6feb',
187
+ focus: '#58a6ff',
188
+ disabled: '#484f58',
189
+ scrim: '#010409',
190
+ cursor: 'default',
191
+ shadow: '#010409',
192
+ },
193
+ spacing: { none: 0, xs: 1, sm: 1, md: 2, lg: 3, xl: 4 },
194
+ components: {
195
+ Panel: { base: { border: 'none', padding: [1, 2] } },
196
+ Button: { base: { padding: [0, 2] } },
197
+ },
198
+ };
199
+
200
+ /** Borderless. Whitespace and alignment do the separating. */
201
+ export const PAPER_LIGHT: ThemeDefinition = {
202
+ id: 'paper-light',
203
+ name: 'Paper Light',
148
204
  appearance: 'light',
149
205
  extends: 'light',
150
206
  border: 'none',
@@ -155,6 +211,7 @@ export const PAPER: ThemeDefinition = {
155
211
  surfaceAlt: '#f5f2ec',
156
212
  border: '#e5e0d8',
157
213
  borderSubtle: '#f0ece5',
214
+ divider: 'default',
158
215
  subtle: '#c56532',
159
216
  text: '#2b2a27',
160
217
  muted: '#7a756c',
@@ -192,6 +249,7 @@ export const WORKBENCH: ThemeDefinition = {
192
249
  overlay: '#181825',
193
250
  border: '#45475a',
194
251
  borderStrong: '#585b70',
252
+ divider: '#585b70',
195
253
  text: '#cdd6f4',
196
254
  muted: '#a6adc8',
197
255
  subtle: '#6c7086',
@@ -219,6 +277,10 @@ export const MONO: ThemeDefinition = {
219
277
  name: 'Monochrome',
220
278
  appearance: 'dark',
221
279
  border: 'ascii',
280
+ cursor: 'underline',
281
+ // Chosen, not downgraded to: this theme is ascii on a terminal that could
282
+ // draw anything, so the rule has to say so too.
283
+ divider: 'ascii',
222
284
  density: 'normal',
223
285
  colors: {
224
286
  canvas: 'default',
@@ -228,6 +290,7 @@ export const MONO: ThemeDefinition = {
228
290
  border: 'default',
229
291
  borderStrong: 'default',
230
292
  borderSubtle: 'default',
293
+ divider: 'default',
231
294
  text: 'default',
232
295
  muted: 'default',
233
296
  subtle: 'default',
@@ -277,6 +340,7 @@ export const PAPER_DARK: ThemeDefinition = {
277
340
  surfaceAlt: '#26231f',
278
341
  border: '#3a352e',
279
342
  borderSubtle: '#2a2621',
343
+ divider: 'default',
280
344
  subtle: '#db8c4c',
281
345
  text: '#e8e3d9',
282
346
  muted: '#9a9287',
@@ -297,5 +361,5 @@ export const PAPER_DARK: ThemeDefinition = {
297
361
  };
298
362
 
299
363
  export const BUILTIN_THEMES: ThemeDefinition[] = [
300
- DARK, LIGHT, CONSOLE, PAPER, PAPER_DARK, WORKBENCH, MONO,
364
+ DARK, LIGHT, CONSOLE, PAPER, PAPER_LIGHT, PAPER_DARK, WORKBENCH, MONO,
301
365
  ];
@@ -0,0 +1,48 @@
1
+ import type { DividerChars, DividerStyle } from '../types/style.js';
2
+
3
+ /**
4
+ * The divider sets.
5
+ *
6
+ * A parallel to `BORDER_SETS`, and deliberately not part of it. A border is
7
+ * thirteen characters that only mean anything together - they enclose a box.
8
+ * A divider encloses nothing: it is one rule, in one direction, whose whole
9
+ * job is to separate. Folding it into `BorderChars` made a theme choose a
10
+ * frame style in order to choose a rule, so a borderless theme could not have
11
+ * one without every bordered component reserving a ring it never draws.
12
+ */
13
+ export const DIVIDER_SETS: Record<DividerStyle, DividerChars> = {
14
+ none: { horizontal: ' ', vertical: ' ' },
15
+ single: { horizontal: '┈', vertical: '│' },
16
+ double: { horizontal: '═', vertical: '║' },
17
+ dashed: { horizontal: '┄', vertical: '┆' },
18
+ thick: { horizontal: '━', vertical: '┃' },
19
+ ascii: { horizontal: '-', vertical: '|' },
20
+ };
21
+
22
+ /** What a terminal with no box drawing gets instead. */
23
+ const ASCII_FALLBACK: Record<DividerStyle, DividerStyle> = {
24
+ none: 'none',
25
+ single: 'ascii',
26
+ double: 'ascii',
27
+ dashed: 'ascii',
28
+ thick: 'ascii',
29
+ ascii: 'ascii',
30
+ };
31
+
32
+ /**
33
+ * The BMP tier has box drawing but not every weight of it: `┄` and `━` are
34
+ * outside it, and a missing glyph is a question mark on somebody's terminal.
35
+ */
36
+ const BMP_FALLBACK: Partial<Record<DividerStyle, DividerStyle>> = {
37
+ dashed: 'single',
38
+ thick: 'single',
39
+ };
40
+
41
+ export function dividerCharsFor(
42
+ style: DividerStyle,
43
+ unicode: 'ascii' | 'bmp' | 'full',
44
+ ): DividerChars {
45
+ if (unicode === 'ascii') return DIVIDER_SETS[ASCII_FALLBACK[style]];
46
+ if (unicode === 'bmp') return DIVIDER_SETS[BMP_FALLBACK[style] ?? style];
47
+ return DIVIDER_SETS[style];
48
+ }
@@ -22,6 +22,8 @@ export const FULL_GLYPHS: ThemeGlyphs = {
22
22
  chevronUp: '▴',
23
23
  arrowUp: '↑',
24
24
  arrowDown: '↓',
25
+ arrowLeft: '←',
26
+ arrowRight: '→',
25
27
  ellipsis: '…',
26
28
  search: '⌕',
27
29
  radioOn: '●',
@@ -71,6 +73,8 @@ export const ASCII_GLYPHS: ThemeGlyphs = {
71
73
  chevronUp: '^',
72
74
  arrowUp: '^',
73
75
  arrowDown: 'v',
76
+ arrowLeft: '<',
77
+ arrowRight: '>',
74
78
  ellipsis: '...',
75
79
  search: '/',
76
80
  radioOn: '(*)',
@@ -1,4 +1,5 @@
1
1
  export * from './borders.js';
2
+ export * from './dividers.js';
2
3
  export * from './glyphs.js';
3
4
  export * from './builtin.js';
4
5
  export * from './registry.js';
@@ -2,13 +2,14 @@ import type {
2
2
  ResolvedTheme, ThemeDefinition, ThemeGlyphs, ThemeRegistry, ThemeSpacing,
3
3
  } from '../types/theme.js';
4
4
  import type { Color } from '../types/cells.js';
5
- import type { BorderChars, BorderStyle, ColorToken, Density, StyleColor } from '../types/style.js';
5
+ import type { BorderChars, BorderStyle, ColorToken, CursorStyle, Density, DividerChars, DividerStyle, StyleColor, TableRules } from '../types/style.js';
6
6
  import type { Style } from '../types/style.js';
7
7
  import type { SyntaxScope } from '../types/syntax.js';
8
8
  import type { TerminalCapabilities } from '../types/capabilities.js';
9
9
  import type { Disposable } from '../types/disposable.js';
10
10
  import { toDisposable } from '../util/disposable.js';
11
11
  import { borderCharsFor } from './borders.js';
12
+ import { dividerCharsFor } from './dividers.js';
12
13
  import { glyphsFor } from './glyphs.js';
13
14
  import { BUILTIN_THEMES } from './builtin.js';
14
15
 
@@ -16,7 +17,7 @@ const DEFAULT_SPACING: ThemeSpacing = { none: 0, xs: 0, sm: 1, md: 1, lg: 2, xl:
16
17
 
17
18
  const FALLBACK_COLORS: Record<ColorToken, Color> = {
18
19
  canvas: 'default', surface: 'default', surfaceAlt: 'default', overlay: 'default',
19
- border: 'default', borderStrong: 'default', borderSubtle: 'default',
20
+ border: 'default', borderStrong: 'default', borderSubtle: 'default', divider: 'default',
20
21
  text: 'default', muted: 'default', subtle: 'default', inverted: 'default',
21
22
  accent: 'default', primary: 'default', secondary: 'default',
22
23
  success: 'default', warning: 'default', danger: 'default', info: 'default',
@@ -115,6 +116,14 @@ export class Themes implements ThemeRegistry {
115
116
  let spacing = { ...DEFAULT_SPACING };
116
117
  let glyphOverrides: Partial<ThemeGlyphs> = {};
117
118
  let border: BorderStyle = 'single';
119
+ let divider: DividerStyle = 'single';
120
+ // Undefined means "leave the terminal's own setting alone", which is the
121
+ // right default: a theme that says nothing should not restyle the caret.
122
+ let cursorStyle: CursorStyle | undefined;
123
+ // The quiet one. A rule between every pair of rows is right for a table of
124
+ // few, long rows and noise on a table of twenty short ones, so a theme
125
+ // opts in rather than out.
126
+ let tableRules: TableRules = 'header';
118
127
  let density: Density = 'normal';
119
128
  const components: Record<string, Record<string, Style>> = {};
120
129
  let syntaxOverrides: Partial<Record<SyntaxScope, StyleColor>> = {};
@@ -142,6 +151,9 @@ export class Themes implements ThemeRegistry {
142
151
  if (def.spacing) spacing = { ...spacing, ...def.spacing };
143
152
  if (def.glyphs) glyphOverrides = { ...glyphOverrides, ...def.glyphs };
144
153
  if (def.border) border = def.border;
154
+ if (def.divider) divider = def.divider;
155
+ if (def.cursor) cursorStyle = def.cursor;
156
+ if (def.tableRules) tableRules = def.tableRules;
145
157
  if (def.density) density = def.density;
146
158
  for (const [name, variants] of Object.entries(def.components ?? {})) {
147
159
  components[name] = { ...components[name], ...variants };
@@ -172,6 +184,7 @@ export class Themes implements ThemeRegistry {
172
184
 
173
185
  const glyphs: ThemeGlyphs = { ...glyphsFor(caps.unicode), ...glyphOverrides };
174
186
  const charCache = new Map<BorderStyle, BorderChars>();
187
+ const ruleCache = new Map<DividerStyle, DividerChars>();
175
188
 
176
189
  const resolved: ResolvedTheme = {
177
190
  id: leaf.id,
@@ -181,6 +194,9 @@ export class Themes implements ThemeRegistry {
181
194
  spacing,
182
195
  glyphs,
183
196
  border,
197
+ divider,
198
+ cursor: cursorStyle,
199
+ tableRules,
184
200
  density,
185
201
  components,
186
202
  syntax,
@@ -203,6 +219,18 @@ export class Themes implements ThemeRegistry {
203
219
  return chars;
204
220
  },
205
221
 
222
+ dividerChars(style?: DividerStyle): DividerChars {
223
+ const s = style ?? divider;
224
+ let chars = ruleCache.get(s);
225
+ if (!chars) {
226
+ const base = dividerCharsFor(s, caps.unicode);
227
+ const custom = leaf.dividerChars?.[s];
228
+ chars = custom ? { ...base, ...custom } : base;
229
+ ruleCache.set(s, chars);
230
+ }
231
+ return chars;
232
+ },
233
+
206
234
  styleFor(component: string, variants: string[] = []): Style {
207
235
  const entry = components[component];
208
236
  if (!entry) return {};
@@ -43,6 +43,16 @@ export interface ArgSpec {
43
43
  /** Fixed choices, or a resolver for a picker. */
44
44
  choices?: ArgChoices | (() => Promise<ArgChoices> | ArgChoices);
45
45
  default?: unknown;
46
+ /**
47
+ * How the picker should lay out each choice's `description`.
48
+ *
49
+ * The argument is what knows: a list of branch names has nothing to say
50
+ * under each one, and a list of approval modes is *only* told apart by what
51
+ * is under each one. `below` gives every choice a second line, which is the
52
+ * only place a sentence fits - inline it shares the width with the label and
53
+ * every answer shows the same truncated half.
54
+ */
55
+ descriptions?: 'inline' | 'below';
46
56
 
47
57
  /**
48
58
  * Show what a choice would do, before it is chosen.
@@ -75,6 +85,13 @@ export interface CommandDefinition {
75
85
  id: string;
76
86
  title: string;
77
87
  description?: string;
88
+ /**
89
+ * The group this belongs to. The palette names it once, above the group.
90
+ *
91
+ * It is not a per-row label: repeating it beside every row spends the width
92
+ * the rows need for saying what they do, and still does not say where one
93
+ * group ends and the next begins.
94
+ */
78
95
  category?: string;
79
96
  icon?: string;
80
97
  /**
@@ -86,7 +103,7 @@ export interface CommandDefinition {
86
103
  */
87
104
  keepOpen?: boolean;
88
105
  /**
89
- * A short state word shown beside the row, in place of the category.
106
+ * A short state word shown beside the row, in place of its description.
90
107
  *
91
108
  * The icon is the row's identity and should not move under the reader as
92
109
  * state changes; this is where the state goes instead.
@@ -52,6 +52,15 @@ export interface MouseEvent {
52
52
  ctrl: boolean;
53
53
  alt: boolean;
54
54
  shift: boolean;
55
+ /**
56
+ * When it happened, in milliseconds.
57
+ *
58
+ * A terminal reports presses and releases and never says "double click" -
59
+ * there is no such thing on the wire - so telling one gesture from two is
60
+ * arithmetic on when they arrived and where. Stamped by whatever produced
61
+ * the event, which is the only thing that knows.
62
+ */
63
+ at?: number;
55
64
  handled: boolean;
56
65
  }
57
66
 
@@ -1,4 +1,4 @@
1
- import type { StyleColor } from './style.js';
1
+ import type { StyleColor, TableRules } from './style.js';
2
2
 
3
3
  /**
4
4
  * Markdown, as rows a terminal can scroll.
@@ -14,6 +14,9 @@ import type { StyleColor } from './style.js';
14
14
  * put the visible ones back into one box.
15
15
  */
16
16
 
17
+ /** Which edge a table column's text is pushed against. */
18
+ export type MarkdownAlign = 'left' | 'center' | 'right';
19
+
17
20
  /** A run of text with one style. Inline emphasis is why rows are not strings. */
18
21
  export interface MarkdownRun {
19
22
  text: string;
@@ -28,7 +31,30 @@ export type MarkdownRow =
28
31
  | { kind: 'rule' }
29
32
  | { kind: 'heading'; runs: MarkdownRun[]; level: number }
30
33
  | { kind: 'text'; runs: MarkdownRun[]; prefix?: string; prefixFg?: StyleColor; fg?: StyleColor }
31
- | { kind: 'fence'; fence: number; part: 'open' | 'code' | 'close'; text?: string; language?: string };
34
+ | { kind: 'fence'; fence: number; part: 'open' | 'code' | 'close'; text?: string; language?: string }
35
+ /**
36
+ * One line of a table, with its cells already fitted to their columns.
37
+ *
38
+ * The widths are decided once for the whole table and repeated on every row
39
+ * of it, because a column that is measured per row is not a column. Cells
40
+ * arrive padded and truncated to exactly `widths[i]`, so a painter only has
41
+ * to put the separators in - which is also what keeps the one-row-per-row
42
+ * invariant: a cell too long for its column is cut, never wrapped, or the
43
+ * table would be a different height than the document said it was.
44
+ *
45
+ * `top`, `rule` and `bottom` are edges and carry no cells; the painter draws
46
+ * them from the widths, in whatever the theme's border characters are. They
47
+ * are rows here for the same reason a fence's rules are: they take a line on
48
+ * the screen, so anything counting rows has to be able to count them.
49
+ */
50
+ | {
51
+ kind: 'table';
52
+ table: number;
53
+ part: 'top' | 'head' | 'rule' | 'body' | 'bottom';
54
+ cells: MarkdownRun[][];
55
+ widths: number[];
56
+ align: MarkdownAlign[];
57
+ };
32
58
 
33
59
  export interface MarkdownLayoutOptions {
34
60
  /** Cells to wrap to. Zero means "not measured yet" - nothing is wrapped. */
@@ -44,4 +70,14 @@ export interface MarkdownLayoutOptions {
44
70
  * rules nobody draws stops two rows short of the end of the document.
45
71
  */
46
72
  ruled?: boolean;
73
+ /**
74
+ * How much of a table gets ruled. The theme's `tableRules`, passed through.
75
+ *
76
+ * It has to be decided here rather than by whatever paints the rows,
77
+ * because a rule between two rows *is* a row - it takes a line on the
78
+ * screen. A painter that added them would be drawing more lines than the
79
+ * layout counted, and every viewer that scrolls by row index would land in
80
+ * the wrong place by one per table row.
81
+ */
82
+ tableRules?: TableRules;
47
83
  }
@@ -16,7 +16,7 @@ export type ColorToken =
16
16
  | 'onAccent' | 'onPrimary' | 'onSecondary'
17
17
  | 'onSuccess' | 'onWarning' | 'onDanger' | 'onInfo'
18
18
  | 'hover' | 'active' | 'selected' | 'focus' | 'disabled'
19
- | 'scrim' | 'cursor' | 'shadow';
19
+ | 'scrim' | 'cursor' | 'shadow' | 'divider';
20
20
 
21
21
  /** Anywhere a colour is accepted, a semantic token is accepted too. */
22
22
  export type StyleColor = ColorToken | Color;
@@ -45,6 +45,38 @@ export interface BorderChars {
45
45
  teeRight: string;
46
46
  }
47
47
 
48
+ /**
49
+ * A rule that separates, rather than a frame that encloses.
50
+ *
51
+ * Kept apart from `BorderStyle` on purpose: a theme that draws no frames may
52
+ * still want a rule, and tying the two means choosing a divider glyph decides
53
+ * whether every bordered component reserves a ring.
54
+ */
55
+ export type DividerStyle =
56
+ | 'none' | 'single' | 'double' | 'dashed' | 'thick' | 'ascii';
57
+
58
+ /** A divider runs either way, so it names both. */
59
+ export interface DividerChars {
60
+ horizontal: string;
61
+ vertical: string;
62
+ }
63
+
64
+ /**
65
+ * The shape of the caret.
66
+ *
67
+ * Named for DECSCUSR, which is what a terminal understands, so a theme value
68
+ * maps straight onto the escape sequence with nothing to translate.
69
+ */
70
+ export type CursorStyle = 'block' | 'underline' | 'bar';
71
+
72
+ /**
73
+ * How much of a table gets ruled: the header only, or between every row.
74
+ *
75
+ * Not a border style - it is a question about how many lines, not which
76
+ * glyphs. The glyphs are the theme's border set either way.
77
+ */
78
+ export type TableRules = 'header' | 'all';
79
+
48
80
  export type BorderSides = {
49
81
  top?: boolean;
50
82
  right?: boolean;
@@ -1,3 +1,4 @@
1
+ import type { CursorStyle } from './style.js';
1
2
  import type { Disposable } from './disposable.js';
2
3
  import type { Size } from './geometry.js';
3
4
  import type { TerminalCapabilities, CapabilityOverrides } from './capabilities.js';
@@ -30,6 +31,8 @@ export interface AcquiredState {
30
31
  focusEvents: boolean;
31
32
  paste: boolean;
32
33
  cursorHidden: boolean;
34
+ /** Whether this session changed the caret shape, and so owes a reset. */
35
+ cursorShaped?: boolean;
33
36
  enhancedKeys: boolean;
34
37
  rawMode: boolean;
35
38
  titleSet: boolean;
@@ -55,4 +58,10 @@ export interface TerminalAdapter extends Disposable {
55
58
  /** OSC 52, when the terminal allows it. */
56
59
  writeClipboard?(text: string): void;
57
60
  setTitle?(title: string): void;
61
+ /**
62
+ * DECSCUSR. Session state rather than frame state - it survives until
63
+ * something changes it, so it is set when it changes and put back on
64
+ * teardown, the way the alt screen and raw mode are.
65
+ */
66
+ setCursorShape?(shape: CursorStyle): void;
58
67
  }
@@ -1,5 +1,8 @@
1
1
  import type { Color } from './cells.js';
2
- import type { BorderChars, BorderStyle, ColorToken, Density, Style, StyleColor } from './style.js';
2
+ import type {
3
+ BorderChars, BorderStyle, ColorToken, CursorStyle, Density, DividerChars, DividerStyle,
4
+ Style, StyleColor, TableRules,
5
+ } from './style.js';
3
6
  import type { SyntaxScope } from './syntax.js';
4
7
  import type { Disposable } from './disposable.js';
5
8
  import type { TerminalCapabilities } from './capabilities.js';
@@ -33,6 +36,8 @@ export interface ThemeGlyphs {
33
36
  chevronUp: string;
34
37
  arrowUp: string;
35
38
  arrowDown: string;
39
+ arrowLeft: string;
40
+ arrowRight: string;
36
41
  ellipsis: string;
37
42
  search: string;
38
43
  radioOn: string;
@@ -80,6 +85,25 @@ export interface ThemeDefinition {
80
85
  /** Default border style for chrome. `'none'` gives the borderless look. */
81
86
  border?: BorderStyle;
82
87
  borderChars?: Partial<Record<BorderStyle, BorderChars>>;
88
+ /**
89
+ * Default rule style. Independent of `border`, so a borderless theme can
90
+ * still separate with a line.
91
+ */
92
+ divider?: DividerStyle;
93
+ dividerChars?: Partial<Record<DividerStyle, DividerChars>>;
94
+ /** The caret's shape. The terminal's own setting is the default. */
95
+ cursor?: CursorStyle;
96
+ /**
97
+ * How much of a table gets ruled.
98
+ *
99
+ * `header` is the default and the quiet one: a box, and a rule under the
100
+ * header. `all` puts a rule between every pair of rows as well, which is
101
+ * what a table of few, long rows wants - a wrapped-looking cell beside a
102
+ * short one is ambiguous about which row it belongs to until something
103
+ * separates them. On a table of twenty short rows the same lines are noise,
104
+ * which is why it is the theme's call rather than the default.
105
+ */
106
+ tableRules?: TableRules;
83
107
  density?: Density;
84
108
  /** Per-component style overrides, keyed by component name then variant. */
85
109
  components?: Record<string, Record<string, Style>>;
@@ -100,6 +124,9 @@ export interface ResolvedTheme {
100
124
  spacing: ThemeSpacing;
101
125
  glyphs: ThemeGlyphs;
102
126
  border: BorderStyle;
127
+ divider: DividerStyle;
128
+ cursor: CursorStyle | undefined;
129
+ tableRules: TableRules;
103
130
  density: Density;
104
131
  components: Record<string, Record<string, Style>>;
105
132
  /** Every syntax scope, resolved to a colour. */
@@ -107,6 +134,7 @@ export interface ResolvedTheme {
107
134
  /** Resolve a token (or pass a literal colour through). */
108
135
  color(token: string): Color;
109
136
  borderChars(style?: BorderStyle): BorderChars;
137
+ dividerChars(style?: DividerStyle): DividerChars;
110
138
  /** Component style for a name + variant list, merged in order. */
111
139
  styleFor(component: string, variants?: string[]): Style;
112
140
  }