@effect-tui/react 0.2.4 → 0.4.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 (39) hide show
  1. package/dist/src/components/Markdown.d.ts +3 -3
  2. package/dist/src/components/Markdown.d.ts.map +1 -1
  3. package/dist/src/components/Markdown.js +16 -15
  4. package/dist/src/components/Markdown.js.map +1 -1
  5. package/dist/src/components/Table.d.ts +58 -0
  6. package/dist/src/components/Table.d.ts.map +1 -0
  7. package/dist/src/components/Table.js +185 -0
  8. package/dist/src/components/Table.js.map +1 -0
  9. package/dist/src/components/TextInput.js +1 -1
  10. package/dist/src/components/TextInput.js.map +1 -1
  11. package/dist/src/components/index.d.ts +1 -0
  12. package/dist/src/components/index.d.ts.map +1 -1
  13. package/dist/src/components/index.js +1 -0
  14. package/dist/src/components/index.js.map +1 -1
  15. package/dist/src/hosts/canvas.d.ts.map +1 -1
  16. package/dist/src/hosts/canvas.js +12 -5
  17. package/dist/src/hosts/canvas.js.map +1 -1
  18. package/dist/src/index.d.ts +1 -1
  19. package/dist/src/index.d.ts.map +1 -1
  20. package/dist/src/index.js +1 -1
  21. package/dist/src/index.js.map +1 -1
  22. package/dist/src/utils/border.d.ts +14 -0
  23. package/dist/src/utils/border.d.ts.map +1 -1
  24. package/dist/src/utils/border.js +21 -0
  25. package/dist/src/utils/border.js.map +1 -1
  26. package/dist/src/utils/index.d.ts +1 -1
  27. package/dist/src/utils/index.d.ts.map +1 -1
  28. package/dist/src/utils/index.js +1 -1
  29. package/dist/src/utils/index.js.map +1 -1
  30. package/dist/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +2 -2
  32. package/src/components/Markdown.tsx +20 -17
  33. package/src/components/Table.tsx +326 -0
  34. package/src/components/TextInput.tsx +1 -1
  35. package/src/components/index.ts +1 -0
  36. package/src/hosts/canvas.ts +13 -5
  37. package/src/index.ts +4 -0
  38. package/src/utils/border.ts +33 -0
  39. package/src/utils/index.ts +9 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-tui/react",
3
- "version": "0.2.4",
3
+ "version": "0.4.0",
4
4
  "description": "React bindings for @effect-tui/core",
5
5
  "type": "module",
6
6
  "files": [
@@ -83,7 +83,7 @@
83
83
  "prepublishOnly": "bun run typecheck && bun run build"
84
84
  },
85
85
  "dependencies": {
86
- "@effect-tui/core": "^0.2.4",
86
+ "@effect-tui/core": "^0.4.0",
87
87
  "@effect/platform": "^0.94.0",
88
88
  "@effect/platform-bun": "^0.87.0",
89
89
  "@effect/rpc": "^0.73.0",
@@ -52,8 +52,8 @@ export interface MarkdownProps {
52
52
  theme?: MarkdownTheme
53
53
  /** Code block theme for syntax highlighting */
54
54
  codeTheme?: BundledTheme
55
- /** Maximum width for text wrapping (default: no wrap) */
56
- maxWidth?: number
55
+ /** Enable text wrapping (default: false, text is truncated) */
56
+ wrap?: boolean
57
57
  }
58
58
 
59
59
  // Parsed markdown elements
@@ -226,39 +226,42 @@ function parseMarkdown(content: string): MdElement[] {
226
226
  }
227
227
 
228
228
  /**
229
- * Render inline spans as text elements
229
+ * Render inline spans as text elements.
230
+ * Uses <text wrap> for host-level wrapping when wrap=true.
230
231
  */
231
- function renderSpans(spans: MdSpan[], theme: Required<MarkdownTheme>) {
232
+ function renderSpans(spans: MdSpan[], theme: Required<MarkdownTheme>, wrap = false) {
232
233
  return spans.map((span, i) => {
233
234
  switch (span.type) {
234
235
  case "text":
235
236
  return (
236
- <text key={i} fg={theme.text}>
237
+ <text key={i} fg={theme.text} wrap={wrap}>
237
238
  {span.text}
238
239
  </text>
239
240
  )
240
241
  case "bold":
241
242
  return (
242
- <text key={i} fg={theme.bold} bold>
243
+ <text key={i} fg={theme.bold} bold wrap={wrap}>
243
244
  {span.text}
244
245
  </text>
245
246
  )
246
247
  case "italic":
247
248
  return (
248
- <text key={i} fg={theme.italic} italic>
249
+ <text key={i} fg={theme.italic} italic wrap={wrap}>
249
250
  {span.text}
250
251
  </text>
251
252
  )
252
253
  case "code":
253
254
  return (
254
- <text key={i} fg={theme.code} bg={theme.codeBg}>
255
+ <text key={i} fg={theme.code} bg={theme.codeBg} wrap={wrap}>
255
256
  {span.text}
256
257
  </text>
257
258
  )
258
259
  case "link":
259
260
  return (
260
261
  <hstack key={i}>
261
- <text fg={theme.link}>{span.text}</text>
262
+ <text fg={theme.link} wrap={wrap}>
263
+ {span.text}
264
+ </text>
262
265
  <text fg={theme.linkUrl}>{" ("}</text>
263
266
  <text fg={theme.linkUrl}>{span.url}</text>
264
267
  <text fg={theme.linkUrl}>{")"}</text>
@@ -294,7 +297,7 @@ function renderSpans(spans: MdSpan[], theme: Required<MarkdownTheme>) {
294
297
  * `} />
295
298
  * ```
296
299
  */
297
- export function Markdown({ content, theme: themeOverrides, codeTheme = "nord" }: MarkdownProps) {
300
+ export function Markdown({ content, theme: themeOverrides, codeTheme = "nord", wrap = false }: MarkdownProps) {
298
301
  const theme = { ...defaultTheme, ...themeOverrides }
299
302
  const elements = parseMarkdown(content)
300
303
 
@@ -304,27 +307,27 @@ export function Markdown({ content, theme: themeOverrides, codeTheme = "nord" }:
304
307
  switch (el.type) {
305
308
  case "h1":
306
309
  return (
307
- <text key={i} fg={theme.h1} bold>
310
+ <text key={i} fg={theme.h1} bold wrap={wrap}>
308
311
  {"# "}
309
312
  {el.text}
310
313
  </text>
311
314
  )
312
315
  case "h2":
313
316
  return (
314
- <text key={i} fg={theme.h2} bold>
317
+ <text key={i} fg={theme.h2} bold wrap={wrap}>
315
318
  {"## "}
316
319
  {el.text}
317
320
  </text>
318
321
  )
319
322
  case "h3":
320
323
  return (
321
- <text key={i} fg={theme.h3} bold>
324
+ <text key={i} fg={theme.h3} bold wrap={wrap}>
322
325
  {"### "}
323
326
  {el.text}
324
327
  </text>
325
328
  )
326
329
  case "paragraph":
327
- return <hstack key={i}>{renderSpans(el.spans, theme)}</hstack>
330
+ return <hstack key={i}>{renderSpans(el.spans, theme, wrap)}</hstack>
328
331
  case "code":
329
332
  return (
330
333
  <CodeBlock
@@ -340,7 +343,7 @@ export function Markdown({ content, theme: themeOverrides, codeTheme = "nord" }:
340
343
  return (
341
344
  <hstack key={i}>
342
345
  <text fg={theme.quoteBorder}>{"│ "}</text>
343
- {renderSpans(el.spans, theme)}
346
+ {renderSpans(el.spans, theme, wrap)}
344
347
  </hstack>
345
348
  )
346
349
  case "ul":
@@ -349,7 +352,7 @@ export function Markdown({ content, theme: themeOverrides, codeTheme = "nord" }:
349
352
  {el.items.map((item, j) => (
350
353
  <hstack key={j}>
351
354
  <text fg={theme.listMarker}>{" • "}</text>
352
- {renderSpans(item, theme)}
355
+ {renderSpans(item, theme, wrap)}
353
356
  </hstack>
354
357
  ))}
355
358
  </vstack>
@@ -360,7 +363,7 @@ export function Markdown({ content, theme: themeOverrides, codeTheme = "nord" }:
360
363
  {el.items.map((item, j) => (
361
364
  <hstack key={j}>
362
365
  <text fg={theme.listMarker}>{` ${el.start + j}. `}</text>
363
- {renderSpans(item, theme)}
366
+ {renderSpans(item, theme, wrap)}
364
367
  </hstack>
365
368
  ))}
366
369
  </vstack>
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Table component for rendering tabular data with box-drawing borders.
3
+ *
4
+ * @example
5
+ * ```tsx
6
+ * <Table data={users} border="square" headerSeparator>
7
+ * <Table.Column header="Name" width={20}>
8
+ * {(user) => <text fg={WHITE}>{user.name}</text>}
9
+ * </Table.Column>
10
+ * <Table.Column header="Score" width={5} align="right">
11
+ * {(user) => <text fg={user.score > 80 ? GREEN : YELLOW}>{user.score}</text>}
12
+ * </Table.Column>
13
+ * </Table>
14
+ * ```
15
+ */
16
+
17
+ import { type Color, Colors } from "@effect-tui/core"
18
+ import { Children, isValidElement, type ReactElement, type ReactNode } from "react"
19
+ import { type BorderKind, tableBorderChars } from "../utils/border.js"
20
+
21
+ // ============================================================================
22
+ // Types
23
+ // ============================================================================
24
+
25
+ type Align = "left" | "center" | "right"
26
+
27
+ export interface ColumnProps<T = unknown> {
28
+ /** Column header text or element */
29
+ header?: ReactNode
30
+ /** Fixed column width in characters */
31
+ width?: number
32
+ /** Minimum width when auto-sizing (default: header length) */
33
+ minWidth?: number
34
+ /** Maximum width when auto-sizing */
35
+ maxWidth?: number
36
+ /** Text alignment within cell (default: "left") */
37
+ align?: Align
38
+ /** Render function for cell content */
39
+ children: (item: T, index: number) => ReactNode
40
+ }
41
+
42
+ export interface TableProps<T> {
43
+ /** Data array to render rows from */
44
+ data: readonly T[]
45
+ /** Border style (default: "square") */
46
+ border?: BorderKind
47
+ /** Show separator line between header and body */
48
+ headerSeparator?: boolean
49
+ /** Show separator lines between all rows */
50
+ rowSeparator?: boolean
51
+ /** Cell padding in characters (default: 1) */
52
+ padding?: number
53
+ /** Border color (default: dim gray) */
54
+ borderColor?: Color
55
+ /** Header text color (default: dim gray) */
56
+ headerColor?: Color
57
+ /** Column definitions */
58
+ children: ReactNode
59
+ }
60
+
61
+ // ============================================================================
62
+ // Column (data holder, not rendered directly)
63
+ // ============================================================================
64
+
65
+ function Column<T>(_props: ColumnProps<T>): ReactElement | null {
66
+ return null
67
+ }
68
+
69
+ // ============================================================================
70
+ // Internal: Extract column configs from children
71
+ // ============================================================================
72
+
73
+ interface ColumnConfig<T> {
74
+ header?: ReactNode
75
+ width: number
76
+ align: Align
77
+ render: (item: T, index: number) => ReactNode
78
+ }
79
+
80
+ function extractColumns<T>(children: ReactNode, data: readonly T[]): ColumnConfig<T>[] {
81
+ const columns: ColumnConfig<T>[] = []
82
+
83
+ Children.forEach(children, (child) => {
84
+ if (!isValidElement(child)) return
85
+ // Check if it's a Column element by checking the function name
86
+ if (typeof child.type === "function" && (child.type as { name?: string }).name === "Column") {
87
+ const props = child.props as ColumnProps<T>
88
+
89
+ // Calculate width
90
+ let width = props.width ?? 0
91
+ if (width === 0) {
92
+ // Auto-size: measure header + content
93
+ const headerLen = typeof props.header === "string" ? props.header.length : 0
94
+ const minFromHeader = Math.max(headerLen, props.minWidth ?? 0)
95
+
96
+ // Measure content from data (sample first few rows)
97
+ let maxContentLen = 0
98
+ for (let i = 0; i < Math.min(data.length, 10); i++) {
99
+ const rendered = props.children(data[i]!, i)
100
+ // Rough estimate - extract text content length
101
+ const textLen = estimateTextLength(rendered)
102
+ maxContentLen = Math.max(maxContentLen, textLen)
103
+ }
104
+
105
+ width = Math.max(minFromHeader, maxContentLen, 1)
106
+ if (props.maxWidth !== undefined) {
107
+ width = Math.min(width, props.maxWidth)
108
+ }
109
+ }
110
+
111
+ columns.push({
112
+ header: props.header,
113
+ width,
114
+ align: props.align ?? "left",
115
+ render: props.children,
116
+ })
117
+ }
118
+ })
119
+
120
+ return columns
121
+ }
122
+
123
+ function estimateTextLength(node: ReactNode): number {
124
+ if (node == null) return 0
125
+ if (typeof node === "string") return node.length
126
+ if (typeof node === "number") return String(node).length
127
+ if (Array.isArray(node)) return node.reduce((sum, n) => sum + estimateTextLength(n), 0)
128
+ if (isValidElement(node)) {
129
+ const children = (node.props as { children?: ReactNode }).children
130
+ return estimateTextLength(children)
131
+ }
132
+ return 0
133
+ }
134
+
135
+ // ============================================================================
136
+ // Internal: Helpers
137
+ // ============================================================================
138
+
139
+ function alignText(text: string, width: number, align: Align): string {
140
+ if (text.length >= width) return text.slice(0, width)
141
+ const padding = width - text.length
142
+ switch (align) {
143
+ case "right":
144
+ return " ".repeat(padding) + text
145
+ case "center": {
146
+ const left = Math.floor(padding / 2)
147
+ const right = padding - left
148
+ return " ".repeat(left) + text + " ".repeat(right)
149
+ }
150
+ default:
151
+ return text + " ".repeat(padding)
152
+ }
153
+ }
154
+
155
+ function buildBorderLine(
156
+ widths: number[],
157
+ left: string,
158
+ mid: string,
159
+ right: string,
160
+ fill: string,
161
+ padding: number,
162
+ ): string {
163
+ return (
164
+ left + widths.map((w) => fill.repeat(w + padding * 2)).join(mid) + right
165
+ )
166
+ }
167
+
168
+ // ============================================================================
169
+ // Table Component
170
+ // ============================================================================
171
+
172
+ function TableRoot<T>({
173
+ data,
174
+ border = "square",
175
+ headerSeparator = false,
176
+ rowSeparator = false,
177
+ padding = 1,
178
+ borderColor = Colors.gray(12),
179
+ headerColor = Colors.gray(12),
180
+ children,
181
+ }: TableProps<T>) {
182
+ const columns = extractColumns<T>(children, data)
183
+ if (columns.length === 0) return null
184
+
185
+ const chars = tableBorderChars(border)
186
+ const widths = columns.map((c) => c.width)
187
+ const pad = " ".repeat(padding)
188
+ const noBorder = border === "none"
189
+
190
+ // Build border lines
191
+ const topLine = buildBorderLine(widths, chars.tl, chars.tt, chars.tr, chars.h, padding)
192
+ const midLine = buildBorderLine(widths, chars.lt, chars.cross, chars.rt, chars.h, padding)
193
+ const bottomLine = buildBorderLine(widths, chars.bl, chars.bt, chars.br, chars.h, padding)
194
+
195
+ // Check if any column has a header
196
+ const hasHeader = columns.some((c) => c.header !== undefined)
197
+
198
+ const rows: ReactNode[] = []
199
+
200
+ // Top border
201
+ if (!noBorder) {
202
+ rows.push(
203
+ <text key="border-top" fg={borderColor}>
204
+ {topLine}
205
+ </text>,
206
+ )
207
+ }
208
+
209
+ // Header row
210
+ if (hasHeader) {
211
+ const headerCells: ReactNode[] = []
212
+ columns.forEach((col, i) => {
213
+ // Left border or column separator
214
+ if (!noBorder) {
215
+ headerCells.push(
216
+ <text key={`h-sep-${i}`} fg={borderColor}>
217
+ {chars.v + pad}
218
+ </text>,
219
+ )
220
+ }
221
+ const headerText = typeof col.header === "string" ? col.header : ""
222
+ headerCells.push(
223
+ <text key={`h-${i}`} fg={headerColor}>
224
+ {alignText(headerText, col.width, col.align)}
225
+ </text>,
226
+ )
227
+ // Right padding (border comes from next column's left, or final border)
228
+ if (!noBorder) {
229
+ headerCells.push(
230
+ <text key={`h-pad-${i}`} fg={borderColor}>
231
+ {pad}
232
+ </text>,
233
+ )
234
+ }
235
+ })
236
+ // Final right border
237
+ if (!noBorder) {
238
+ headerCells.push(
239
+ <text key="h-sep-end" fg={borderColor}>
240
+ {chars.v}
241
+ </text>,
242
+ )
243
+ }
244
+ rows.push(<hstack key="header" spacing={0}>{headerCells}</hstack>)
245
+
246
+ // Header separator
247
+ if (headerSeparator && !noBorder) {
248
+ rows.push(
249
+ <text key="border-mid-header" fg={borderColor}>
250
+ {midLine}
251
+ </text>,
252
+ )
253
+ }
254
+ }
255
+
256
+ // Data rows
257
+ data.forEach((item, rowIdx) => {
258
+ // Row separator (between rows)
259
+ if (rowSeparator && rowIdx > 0 && !noBorder) {
260
+ rows.push(
261
+ <text key={`border-mid-${rowIdx}`} fg={borderColor}>
262
+ {midLine}
263
+ </text>,
264
+ )
265
+ }
266
+
267
+ const rowCells: ReactNode[] = []
268
+ columns.forEach((col, colIdx) => {
269
+ // Left border or column separator
270
+ if (!noBorder) {
271
+ rowCells.push(
272
+ <text key={`r${rowIdx}-sep-${colIdx}`} fg={borderColor}>
273
+ {chars.v + pad}
274
+ </text>,
275
+ )
276
+ }
277
+
278
+ // Cell content
279
+ const content = col.render(item, rowIdx)
280
+ rowCells.push(
281
+ <hstack key={`r${rowIdx}-c${colIdx}`} spacing={0} width={col.width}>
282
+ {content}
283
+ </hstack>,
284
+ )
285
+
286
+ // Right padding
287
+ if (!noBorder) {
288
+ rowCells.push(
289
+ <text key={`r${rowIdx}-pad-${colIdx}`} fg={borderColor}>
290
+ {pad}
291
+ </text>,
292
+ )
293
+ }
294
+ })
295
+ // Final right border
296
+ if (!noBorder) {
297
+ rowCells.push(
298
+ <text key={`r${rowIdx}-sep-end`} fg={borderColor}>
299
+ {chars.v}
300
+ </text>,
301
+ )
302
+ }
303
+ rows.push(<hstack key={`row-${rowIdx}`} spacing={0}>{rowCells}</hstack>)
304
+ })
305
+
306
+ // Bottom border
307
+ if (!noBorder) {
308
+ rows.push(
309
+ <text key="border-bottom" fg={borderColor}>
310
+ {bottomLine}
311
+ </text>,
312
+ )
313
+ }
314
+
315
+ return <vstack spacing={0}>{rows}</vstack>
316
+ }
317
+
318
+ // ============================================================================
319
+ // Exports
320
+ // ============================================================================
321
+
322
+ export const Table = Object.assign(TableRoot, {
323
+ Column,
324
+ })
325
+
326
+ export type { Align as TableAlign }
@@ -352,5 +352,5 @@ export function TextInput({
352
352
  ],
353
353
  )
354
354
 
355
- return <canvas draw={draw} width={width} height={1} />
355
+ return <canvas draw={draw} width={width} height={1} inheritBg={bg === undefined} />
356
356
  }
@@ -3,4 +3,5 @@ export { Markdown, type MarkdownProps, type MarkdownTheme } from "./Markdown.js"
3
3
  export { MultilineTextInput, type MultilineTextInputProps } from "./MultilineTextInput.js"
4
4
  export { Overlay, type OverlayItemProps, type OverlayProps } from "./Overlay.js"
5
5
  export { Static, type StaticProps } from "./Static.js"
6
+ export { Table, type TableAlign, type ColumnProps, type TableProps } from "./Table.js"
6
7
  export { TextInput, type TextInputProps } from "./TextInput.js"
@@ -89,10 +89,14 @@ export class CanvasHost extends BaseHost {
89
89
  if (!this.rect) return
90
90
  const { x: ox, y: oy, w, h } = this.rect
91
91
 
92
+ // Get inherited background for use in drawing functions
93
+ const { value: inheritedBgValue, styleId: inheritedBgStyleId } = this.inheritBg
94
+ ? resolveInheritedBgStyle(palette, undefined, this.parent)
95
+ : { value: undefined, styleId: 0 }
96
+
92
97
  // Pre-fill with inherited background if requested
93
- if (this.inheritBg) {
94
- const { styleId } = resolveInheritedBgStyle(palette, undefined, this.parent)
95
- buffer.fillRect(ox, oy, w, h, " ".codePointAt(0)!, styleId)
98
+ if (this.inheritBg && inheritedBgValue !== undefined) {
99
+ buffer.fillRect(ox, oy, w, h, " ".codePointAt(0)!, inheritedBgStyleId)
96
100
  }
97
101
 
98
102
  // Create draw context
@@ -103,9 +107,11 @@ export class CanvasHost extends BaseHost {
103
107
  text: (x, y, str, opts) => {
104
108
  const px = Math.round(ox + x)
105
109
  const py = Math.round(oy + y)
110
+ // Use inherited bg when inheritBg is enabled and no explicit bg provided
111
+ const effectiveBg = opts?.bg ?? (this.inheritBg ? inheritedBgValue : undefined)
106
112
  const style = styleIdFromProps(palette, {
107
113
  fg: opts?.fg,
108
- bg: opts?.bg,
114
+ bg: effectiveBg,
109
115
  bold: opts?.bold,
110
116
  italic: opts?.italic,
111
117
  underline: opts?.underline,
@@ -123,9 +129,11 @@ export class CanvasHost extends BaseHost {
123
129
  const px = Math.round(ox + x)
124
130
  const py = Math.round(oy + y)
125
131
  const cp = char.codePointAt(0)!
132
+ // Use inherited bg when inheritBg is enabled and no explicit bg provided
133
+ const effectiveBg = opts?.bg ?? (this.inheritBg ? inheritedBgValue : undefined)
126
134
  const style = styleIdFromProps(palette, {
127
135
  fg: opts?.fg,
128
- bg: opts?.bg,
136
+ bg: effectiveBg,
129
137
  bold: opts?.bold,
130
138
  italic: opts?.italic,
131
139
  underline: opts?.underline,
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export type { CodeBlockProps } from "./codeblock.js"
5
5
  export { CodeBlock } from "./codeblock.js"
6
6
  // Input components
7
7
  export {
8
+ type ColumnProps,
8
9
  Markdown,
9
10
  type MarkdownProps,
10
11
  type MarkdownTheme,
@@ -15,6 +16,9 @@ export {
15
16
  type OverlayProps,
16
17
  Static,
17
18
  type StaticProps,
19
+ Table,
20
+ type TableAlign,
21
+ type TableProps,
18
22
  TextInput,
19
23
  type TextInputProps,
20
24
  } from "./components/index.js"
@@ -15,6 +15,17 @@ export interface BorderChars {
15
15
  v: string // vertical
16
16
  }
17
17
 
18
+ /**
19
+ * Extended border chars for tables with T-connectors.
20
+ */
21
+ export interface TableBorderChars extends BorderChars {
22
+ tt: string // top T (┬)
23
+ bt: string // bottom T (┴)
24
+ lt: string // left T (├)
25
+ rt: string // right T (┤)
26
+ cross: string // cross (┼)
27
+ }
28
+
18
29
  /**
19
30
  * Get border characters for a given border style.
20
31
  * Returns empty strings for "none" (drawing code should skip when border is "none").
@@ -38,6 +49,28 @@ export function borderChars(kind: BorderKind): BorderChars {
38
49
  }
39
50
  }
40
51
 
52
+ /**
53
+ * Get table border characters (includes T-connectors and cross).
54
+ */
55
+ export function tableBorderChars(kind: BorderKind): TableBorderChars {
56
+ switch (kind) {
57
+ case "rounded":
58
+ return { tl: "╭", tr: "╮", bl: "╰", br: "╯", h: "─", v: "│", tt: "┬", bt: "┴", lt: "├", rt: "┤", cross: "┼" }
59
+ case "square":
60
+ return { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│", tt: "┬", bt: "┴", lt: "├", rt: "┤", cross: "┼" }
61
+ case "double":
62
+ return { tl: "╔", tr: "╗", bl: "╚", br: "╝", h: "═", v: "║", tt: "╦", bt: "╩", lt: "╠", rt: "╣", cross: "╬" }
63
+ case "heavy":
64
+ return { tl: "┏", tr: "┓", bl: "┗", br: "┛", h: "━", v: "┃", tt: "┳", bt: "┻", lt: "┣", rt: "┫", cross: "╋" }
65
+ case "dashed":
66
+ return { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "┄", v: "┆", tt: "┬", bt: "┴", lt: "├", rt: "┤", cross: "┼" }
67
+ case "ascii":
68
+ return { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|", tt: "+", bt: "+", lt: "+", rt: "+", cross: "+" }
69
+ default:
70
+ return { tl: "", tr: "", bl: "", br: "", h: "", v: "", tt: "", bt: "", lt: "", rt: "", cross: "" }
71
+ }
72
+ }
73
+
41
74
  export interface ClipRect {
42
75
  ox: number
43
76
  oy: number
@@ -1,5 +1,13 @@
1
1
  export { alignInRect, type HAlign, type VAlign } from "./alignment.js"
2
- export { type BorderChars, type BorderKind, borderChars, type ClipRect, drawBorder } from "./border.js"
2
+ export {
3
+ type BorderChars,
4
+ type BorderKind,
5
+ borderChars,
6
+ type ClipRect,
7
+ drawBorder,
8
+ type TableBorderChars,
9
+ tableBorderChars,
10
+ } from "./border.js"
3
11
  export { type FlexAlignment, type FlexAxis, type FlexMeasureResult, layoutFlex, measureFlex } from "./flex-layout.js"
4
12
  export { type Padding, type PaddingInput, resolvePadding } from "./padding.js"
5
13
  export {