@silvery/term 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +54 -0
- package/src/adapters/canvas-adapter.ts +356 -0
- package/src/adapters/dom-adapter.ts +452 -0
- package/src/adapters/flexily-zero-adapter.ts +368 -0
- package/src/adapters/terminal-adapter.ts +305 -0
- package/src/adapters/yoga-adapter.ts +370 -0
- package/src/ansi/ansi.ts +251 -0
- package/src/ansi/constants.ts +76 -0
- package/src/ansi/detection.ts +441 -0
- package/src/ansi/hyperlink.ts +38 -0
- package/src/ansi/index.ts +201 -0
- package/src/ansi/patch-console.ts +159 -0
- package/src/ansi/sgr-codes.ts +34 -0
- package/src/ansi/storybook.ts +209 -0
- package/src/ansi/term.ts +724 -0
- package/src/ansi/types.ts +202 -0
- package/src/ansi/underline.ts +156 -0
- package/src/ansi/utils.ts +65 -0
- package/src/ansi-sanitize.ts +509 -0
- package/src/app.ts +571 -0
- package/src/bound-term.ts +94 -0
- package/src/bracketed-paste.ts +75 -0
- package/src/browser-renderer.ts +174 -0
- package/src/buffer.ts +1984 -0
- package/src/clipboard.ts +74 -0
- package/src/cursor-query.ts +85 -0
- package/src/device-attrs.ts +228 -0
- package/src/devtools.ts +123 -0
- package/src/dom/index.ts +194 -0
- package/src/errors.ts +39 -0
- package/src/focus-reporting.ts +48 -0
- package/src/hit-registry-core.ts +228 -0
- package/src/hit-registry.ts +176 -0
- package/src/index.ts +458 -0
- package/src/input.ts +119 -0
- package/src/inspector.ts +155 -0
- package/src/kitty-detect.ts +95 -0
- package/src/kitty-manager.ts +160 -0
- package/src/layout-engine.ts +296 -0
- package/src/layout.ts +26 -0
- package/src/measurer.ts +74 -0
- package/src/mode-query.ts +106 -0
- package/src/mouse-events.ts +419 -0
- package/src/mouse.ts +83 -0
- package/src/non-tty.ts +223 -0
- package/src/osc-markers.ts +32 -0
- package/src/osc-palette.ts +169 -0
- package/src/output.ts +406 -0
- package/src/pane-manager.ts +248 -0
- package/src/pipeline/CLAUDE.md +587 -0
- package/src/pipeline/content-phase-adapter.ts +976 -0
- package/src/pipeline/content-phase.ts +1765 -0
- package/src/pipeline/helpers.ts +42 -0
- package/src/pipeline/index.ts +416 -0
- package/src/pipeline/layout-phase.ts +686 -0
- package/src/pipeline/measure-phase.ts +198 -0
- package/src/pipeline/measure-stats.ts +21 -0
- package/src/pipeline/output-phase.ts +2593 -0
- package/src/pipeline/render-box.ts +343 -0
- package/src/pipeline/render-helpers.ts +243 -0
- package/src/pipeline/render-text.ts +1255 -0
- package/src/pipeline/types.ts +161 -0
- package/src/pipeline.ts +29 -0
- package/src/pixel-size.ts +119 -0
- package/src/render-adapter.ts +179 -0
- package/src/renderer.ts +1330 -0
- package/src/runtime/create-app.tsx +1845 -0
- package/src/runtime/create-buffer.ts +18 -0
- package/src/runtime/create-runtime.ts +325 -0
- package/src/runtime/diff.ts +56 -0
- package/src/runtime/event-handlers.ts +254 -0
- package/src/runtime/index.ts +119 -0
- package/src/runtime/keys.ts +8 -0
- package/src/runtime/layout.ts +164 -0
- package/src/runtime/run.tsx +318 -0
- package/src/runtime/term-provider.ts +399 -0
- package/src/runtime/terminal-lifecycle.ts +246 -0
- package/src/runtime/tick.ts +219 -0
- package/src/runtime/types.ts +210 -0
- package/src/scheduler.ts +723 -0
- package/src/screenshot.ts +57 -0
- package/src/scroll-region.ts +69 -0
- package/src/scroll-utils.ts +97 -0
- package/src/term-def.ts +267 -0
- package/src/terminal-caps.ts +5 -0
- package/src/terminal-colors.ts +216 -0
- package/src/termtest.ts +224 -0
- package/src/text-sizing.ts +109 -0
- package/src/toolbelt/index.ts +72 -0
- package/src/unicode.ts +1763 -0
- package/src/xterm/index.ts +491 -0
- package/src/xterm/xterm-provider.ts +204 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM Render Adapter
|
|
3
|
+
*
|
|
4
|
+
* Implements the RenderAdapter interface for browser DOM output.
|
|
5
|
+
* Uses a line-based approach: one <div> per row, <span> elements for styled text runs.
|
|
6
|
+
* The layout engine operates in cell units (columns x rows). This adapter
|
|
7
|
+
* converts cell coordinates to pixel coordinates when rendering to the DOM,
|
|
8
|
+
* using charWidth (fontSize * 0.6) and cellHeight (fontSize * lineHeight).
|
|
9
|
+
*
|
|
10
|
+
* Advantages over Canvas:
|
|
11
|
+
* - Native text selection and copying
|
|
12
|
+
* - Screen reader accessibility
|
|
13
|
+
* - Browser font rendering (subpixel antialiasing, ligatures)
|
|
14
|
+
* - CSS integration (theming, hover states)
|
|
15
|
+
* - DevTools inspection
|
|
16
|
+
*
|
|
17
|
+
* Architecture follows xterm.js DOM renderer approach.
|
|
18
|
+
* @see https://github.com/xtermjs/xterm.js/issues/3271
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type {
|
|
22
|
+
BorderChars,
|
|
23
|
+
RenderAdapter,
|
|
24
|
+
RenderBuffer,
|
|
25
|
+
RenderStyle,
|
|
26
|
+
TextMeasureResult,
|
|
27
|
+
TextMeasureStyle,
|
|
28
|
+
TextMeasurer,
|
|
29
|
+
} from "../render-adapter"
|
|
30
|
+
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Configuration
|
|
33
|
+
// ============================================================================
|
|
34
|
+
|
|
35
|
+
export interface DOMAdapterConfig {
|
|
36
|
+
/** Font size in pixels (default: 14) */
|
|
37
|
+
fontSize?: number
|
|
38
|
+
/** Font family (default: 'monospace') */
|
|
39
|
+
fontFamily?: string
|
|
40
|
+
/** Line height multiplier (default: 1.2) */
|
|
41
|
+
lineHeight?: number
|
|
42
|
+
/** Background color (default: '#1e1e1e') */
|
|
43
|
+
backgroundColor?: string
|
|
44
|
+
/** Default foreground color (default: '#d4d4d4') */
|
|
45
|
+
foregroundColor?: string
|
|
46
|
+
/** CSS class prefix (default: 'silvery') */
|
|
47
|
+
classPrefix?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const DEFAULT_CONFIG: Required<DOMAdapterConfig> = {
|
|
51
|
+
fontSize: 14,
|
|
52
|
+
fontFamily: "monospace",
|
|
53
|
+
lineHeight: 1.2,
|
|
54
|
+
backgroundColor: "#1e1e1e",
|
|
55
|
+
foregroundColor: "#d4d4d4",
|
|
56
|
+
classPrefix: "silvery",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// Border Characters (same as terminal/canvas for consistency)
|
|
61
|
+
// ============================================================================
|
|
62
|
+
|
|
63
|
+
const BORDER_CHARS: Record<string, BorderChars> = {
|
|
64
|
+
single: {
|
|
65
|
+
topLeft: "┌",
|
|
66
|
+
topRight: "┐",
|
|
67
|
+
bottomLeft: "└",
|
|
68
|
+
bottomRight: "┘",
|
|
69
|
+
horizontal: "─",
|
|
70
|
+
vertical: "│",
|
|
71
|
+
},
|
|
72
|
+
double: {
|
|
73
|
+
topLeft: "╔",
|
|
74
|
+
topRight: "╗",
|
|
75
|
+
bottomLeft: "╚",
|
|
76
|
+
bottomRight: "╝",
|
|
77
|
+
horizontal: "═",
|
|
78
|
+
vertical: "║",
|
|
79
|
+
},
|
|
80
|
+
round: {
|
|
81
|
+
topLeft: "╭",
|
|
82
|
+
topRight: "╮",
|
|
83
|
+
bottomLeft: "╰",
|
|
84
|
+
bottomRight: "╯",
|
|
85
|
+
horizontal: "─",
|
|
86
|
+
vertical: "│",
|
|
87
|
+
},
|
|
88
|
+
bold: {
|
|
89
|
+
topLeft: "┏",
|
|
90
|
+
topRight: "┓",
|
|
91
|
+
bottomLeft: "┗",
|
|
92
|
+
bottomRight: "┛",
|
|
93
|
+
horizontal: "━",
|
|
94
|
+
vertical: "┃",
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// Color Conversion
|
|
100
|
+
// ============================================================================
|
|
101
|
+
|
|
102
|
+
const ANSI_COLORS: Record<string, string> = {
|
|
103
|
+
black: "#000000",
|
|
104
|
+
red: "#cd0000",
|
|
105
|
+
green: "#00cd00",
|
|
106
|
+
yellow: "#cdcd00",
|
|
107
|
+
blue: "#0000ee",
|
|
108
|
+
magenta: "#cd00cd",
|
|
109
|
+
cyan: "#00cdcd",
|
|
110
|
+
white: "#e5e5e5",
|
|
111
|
+
gray: "#7f7f7f",
|
|
112
|
+
grey: "#7f7f7f",
|
|
113
|
+
brightblack: "#7f7f7f",
|
|
114
|
+
brightred: "#ff0000",
|
|
115
|
+
brightgreen: "#00ff00",
|
|
116
|
+
brightyellow: "#ffff00",
|
|
117
|
+
brightblue: "#5c5cff",
|
|
118
|
+
brightmagenta: "#ff00ff",
|
|
119
|
+
brightcyan: "#00ffff",
|
|
120
|
+
brightwhite: "#ffffff",
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function resolveColor(color: string | undefined, fallback: string): string {
|
|
124
|
+
if (!color) return fallback
|
|
125
|
+
if (color.startsWith("#") || color.startsWith("rgb")) return color
|
|
126
|
+
const named = ANSI_COLORS[color.toLowerCase()]
|
|
127
|
+
return named ?? color
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// DOM Measurer
|
|
132
|
+
// ============================================================================
|
|
133
|
+
|
|
134
|
+
function createDOMMeasurer(_config: Required<DOMAdapterConfig>): TextMeasurer {
|
|
135
|
+
// The layout engine operates in cell units (columns x rows), matching the
|
|
136
|
+
// terminal convention. For monospace fonts, text width = character count
|
|
137
|
+
// and line height = 1 row.
|
|
138
|
+
return {
|
|
139
|
+
measureText(text: string, _style?: TextMeasureStyle): TextMeasureResult {
|
|
140
|
+
// For monospace fonts, width is simply the character count (one cell per char)
|
|
141
|
+
return {
|
|
142
|
+
width: text.length,
|
|
143
|
+
height: 1,
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
getLineHeight(_style?: TextMeasureStyle): number {
|
|
148
|
+
return 1
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ============================================================================
|
|
154
|
+
// Styled Text Run
|
|
155
|
+
// ============================================================================
|
|
156
|
+
|
|
157
|
+
interface TextRun {
|
|
158
|
+
text: string
|
|
159
|
+
style: RenderStyle
|
|
160
|
+
x: number
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// DOM Render Buffer
|
|
165
|
+
// ============================================================================
|
|
166
|
+
|
|
167
|
+
export class DOMRenderBuffer implements RenderBuffer {
|
|
168
|
+
readonly width: number
|
|
169
|
+
readonly height: number
|
|
170
|
+
|
|
171
|
+
private config: Required<DOMAdapterConfig>
|
|
172
|
+
private lines: Map<number, TextRun[]>
|
|
173
|
+
private backgrounds: Map<string, { x: number; y: number; w: number; h: number; color: string }>
|
|
174
|
+
|
|
175
|
+
// Cell-to-pixel conversion factors
|
|
176
|
+
private readonly charWidth: number
|
|
177
|
+
private readonly cellHeight: number
|
|
178
|
+
|
|
179
|
+
// Container element (set when flushing)
|
|
180
|
+
private container: HTMLElement | null = null
|
|
181
|
+
|
|
182
|
+
constructor(width: number, height: number, config: Required<DOMAdapterConfig>) {
|
|
183
|
+
this.width = width
|
|
184
|
+
this.height = height
|
|
185
|
+
this.config = config
|
|
186
|
+
this.lines = new Map()
|
|
187
|
+
this.backgrounds = new Map()
|
|
188
|
+
|
|
189
|
+
// Compute cell dimensions for coordinate conversion.
|
|
190
|
+
// Width/height are in cell units (cols/rows); rendering converts to pixels.
|
|
191
|
+
this.charWidth = config.fontSize * 0.6
|
|
192
|
+
this.cellHeight = config.fontSize * config.lineHeight
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Set the container element for rendering.
|
|
197
|
+
*/
|
|
198
|
+
setContainer(container: HTMLElement): void {
|
|
199
|
+
this.container = container
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get the container element.
|
|
204
|
+
*/
|
|
205
|
+
getContainer(): HTMLElement | null {
|
|
206
|
+
return this.container
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fillRect(x: number, y: number, width: number, height: number, style: RenderStyle): void {
|
|
210
|
+
if (style.bg) {
|
|
211
|
+
const key = `${x},${y},${width},${height}`
|
|
212
|
+
this.backgrounds.set(key, {
|
|
213
|
+
x,
|
|
214
|
+
y,
|
|
215
|
+
w: width,
|
|
216
|
+
h: height,
|
|
217
|
+
color: resolveColor(style.bg, this.config.backgroundColor),
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
drawText(x: number, y: number, text: string, style: RenderStyle): void {
|
|
223
|
+
if (!this.lines.has(y)) {
|
|
224
|
+
this.lines.set(y, [])
|
|
225
|
+
}
|
|
226
|
+
this.lines.get(y)!.push({ text, style, x })
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
drawChar(x: number, y: number, char: string, style: RenderStyle): void {
|
|
230
|
+
this.drawText(x, y, char, style)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
inBounds(x: number, y: number): boolean {
|
|
234
|
+
return x >= 0 && x < this.width && y >= 0 && y < this.height
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Render the buffer to the container element.
|
|
239
|
+
* Coordinates in the buffer are in cell units (cols/rows).
|
|
240
|
+
* This method converts them to pixel coordinates for DOM positioning.
|
|
241
|
+
*/
|
|
242
|
+
render(): void {
|
|
243
|
+
if (!this.container) {
|
|
244
|
+
throw new Error("DOMRenderBuffer: No container set. Call setContainer() first.")
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const container = this.container
|
|
248
|
+
const cw = this.charWidth
|
|
249
|
+
const ch = this.cellHeight
|
|
250
|
+
|
|
251
|
+
// Container dimensions in pixels (convert cell units back to pixels)
|
|
252
|
+
const containerWidthPx = this.width * cw
|
|
253
|
+
const containerHeightPx = this.height * ch
|
|
254
|
+
|
|
255
|
+
// Clear previous content
|
|
256
|
+
container.innerHTML = ""
|
|
257
|
+
|
|
258
|
+
// Set container styles
|
|
259
|
+
container.style.cssText = `
|
|
260
|
+
position: relative;
|
|
261
|
+
font-family: ${this.config.fontFamily};
|
|
262
|
+
font-size: ${this.config.fontSize}px;
|
|
263
|
+
line-height: ${this.config.lineHeight};
|
|
264
|
+
background-color: ${this.config.backgroundColor};
|
|
265
|
+
color: ${this.config.foregroundColor};
|
|
266
|
+
white-space: pre;
|
|
267
|
+
overflow: hidden;
|
|
268
|
+
width: ${containerWidthPx}px;
|
|
269
|
+
height: ${containerHeightPx}px;
|
|
270
|
+
`
|
|
271
|
+
|
|
272
|
+
// Render background rectangles (convert cell coords to pixels)
|
|
273
|
+
for (const bg of this.backgrounds.values()) {
|
|
274
|
+
const bgDiv = document.createElement("div")
|
|
275
|
+
bgDiv.className = `${this.config.classPrefix}-bg`
|
|
276
|
+
bgDiv.style.cssText = `
|
|
277
|
+
position: absolute;
|
|
278
|
+
left: ${bg.x * cw}px;
|
|
279
|
+
top: ${bg.y * ch}px;
|
|
280
|
+
width: ${bg.w * cw}px;
|
|
281
|
+
height: ${bg.h * ch}px;
|
|
282
|
+
background-color: ${bg.color};
|
|
283
|
+
`
|
|
284
|
+
container.appendChild(bgDiv)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Render text lines (convert cell coords to pixels)
|
|
288
|
+
const sortedLines = Array.from(this.lines.entries()).sort((a, b) => a[0] - b[0])
|
|
289
|
+
|
|
290
|
+
for (const [y, runs] of sortedLines) {
|
|
291
|
+
const lineDiv = document.createElement("div")
|
|
292
|
+
lineDiv.className = `${this.config.classPrefix}-line`
|
|
293
|
+
lineDiv.style.cssText = `
|
|
294
|
+
position: absolute;
|
|
295
|
+
left: 0;
|
|
296
|
+
top: ${y * ch}px;
|
|
297
|
+
height: ${ch}px;
|
|
298
|
+
white-space: pre;
|
|
299
|
+
`
|
|
300
|
+
|
|
301
|
+
// Sort runs by x position
|
|
302
|
+
const sortedRuns = runs.sort((a, b) => a.x - b.x)
|
|
303
|
+
|
|
304
|
+
for (const run of sortedRuns) {
|
|
305
|
+
const span = document.createElement("span")
|
|
306
|
+
span.className = `${this.config.classPrefix}-text`
|
|
307
|
+
span.textContent = run.text
|
|
308
|
+
|
|
309
|
+
// Apply styles (convert cell x to pixel x)
|
|
310
|
+
const styles: string[] = [`position: absolute`, `left: ${run.x * cw}px`]
|
|
311
|
+
|
|
312
|
+
if (run.style.fg) {
|
|
313
|
+
styles.push(`color: ${resolveColor(run.style.fg, this.config.foregroundColor)}`)
|
|
314
|
+
}
|
|
315
|
+
if (run.style.bg) {
|
|
316
|
+
styles.push(`background-color: ${resolveColor(run.style.bg, this.config.backgroundColor)}`)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const attrs = run.style.attrs
|
|
320
|
+
if (attrs) {
|
|
321
|
+
if (attrs.bold) styles.push("font-weight: bold")
|
|
322
|
+
if (attrs.dim) styles.push("opacity: 0.5")
|
|
323
|
+
if (attrs.italic) styles.push("font-style: italic")
|
|
324
|
+
|
|
325
|
+
// Underline handling
|
|
326
|
+
if (attrs.underline || attrs.underlineStyle) {
|
|
327
|
+
const underlineStyle = attrs.underlineStyle ?? "single"
|
|
328
|
+
const underlineColor = attrs.underlineColor
|
|
329
|
+
? resolveColor(attrs.underlineColor, this.config.foregroundColor)
|
|
330
|
+
: "currentColor"
|
|
331
|
+
|
|
332
|
+
switch (underlineStyle) {
|
|
333
|
+
case "double":
|
|
334
|
+
styles.push(`text-decoration: underline double ${underlineColor}`)
|
|
335
|
+
break
|
|
336
|
+
case "curly":
|
|
337
|
+
styles.push(`text-decoration: underline wavy ${underlineColor}`)
|
|
338
|
+
break
|
|
339
|
+
case "dotted":
|
|
340
|
+
styles.push(`text-decoration: underline dotted ${underlineColor}`)
|
|
341
|
+
break
|
|
342
|
+
case "dashed":
|
|
343
|
+
styles.push(`text-decoration: underline dashed ${underlineColor}`)
|
|
344
|
+
break
|
|
345
|
+
default:
|
|
346
|
+
styles.push(`text-decoration: underline solid ${underlineColor}`)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (attrs.strikethrough) {
|
|
351
|
+
const existing = styles.find((s) => s.startsWith("text-decoration:"))
|
|
352
|
+
if (existing) {
|
|
353
|
+
const idx = styles.indexOf(existing)
|
|
354
|
+
styles[idx] = existing.replace("underline", "underline line-through")
|
|
355
|
+
} else {
|
|
356
|
+
styles.push("text-decoration: line-through")
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (attrs.inverse) {
|
|
361
|
+
// Swap foreground/background
|
|
362
|
+
const fg = run.style.fg
|
|
363
|
+
? resolveColor(run.style.fg, this.config.foregroundColor)
|
|
364
|
+
: this.config.foregroundColor
|
|
365
|
+
const bg = run.style.bg
|
|
366
|
+
? resolveColor(run.style.bg, this.config.backgroundColor)
|
|
367
|
+
: this.config.backgroundColor
|
|
368
|
+
styles.push(`color: ${bg}`, `background-color: ${fg}`)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
span.style.cssText = styles.join("; ")
|
|
373
|
+
lineDiv.appendChild(span)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
container.appendChild(lineDiv)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Clear the buffer.
|
|
382
|
+
*/
|
|
383
|
+
clear(): void {
|
|
384
|
+
this.lines.clear()
|
|
385
|
+
this.backgrounds.clear()
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ============================================================================
|
|
390
|
+
// DOM Adapter Factory
|
|
391
|
+
// ============================================================================
|
|
392
|
+
|
|
393
|
+
export function createDOMAdapter(config: DOMAdapterConfig = {}): RenderAdapter {
|
|
394
|
+
const cfg = { ...DEFAULT_CONFIG, ...config }
|
|
395
|
+
const measurer = createDOMMeasurer(cfg)
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
name: "dom",
|
|
399
|
+
measurer,
|
|
400
|
+
|
|
401
|
+
createBuffer(width: number, height: number): RenderBuffer {
|
|
402
|
+
return new DOMRenderBuffer(width, height, cfg)
|
|
403
|
+
},
|
|
404
|
+
|
|
405
|
+
flush(buffer: RenderBuffer, _prevBuffer: RenderBuffer | null): void {
|
|
406
|
+
// DOM buffer renders directly when render() is called
|
|
407
|
+
const domBuffer = buffer as DOMRenderBuffer
|
|
408
|
+
if (domBuffer.getContainer()) {
|
|
409
|
+
domBuffer.render()
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
|
|
413
|
+
getBorderChars(style: string): BorderChars {
|
|
414
|
+
return BORDER_CHARS[style] ?? BORDER_CHARS.single!
|
|
415
|
+
},
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ============================================================================
|
|
420
|
+
// Inject Global Styles (Optional)
|
|
421
|
+
// ============================================================================
|
|
422
|
+
|
|
423
|
+
let stylesInjected = false
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Inject global CSS styles for silvery DOM rendering.
|
|
427
|
+
* Call once at application startup if you want default styling.
|
|
428
|
+
*/
|
|
429
|
+
export function injectDOMStyles(classPrefix = "silvery"): void {
|
|
430
|
+
if (stylesInjected || typeof document === "undefined") return
|
|
431
|
+
|
|
432
|
+
const style = document.createElement("style")
|
|
433
|
+
style.textContent = `
|
|
434
|
+
.${classPrefix}-container {
|
|
435
|
+
font-family: monospace;
|
|
436
|
+
white-space: pre;
|
|
437
|
+
overflow: hidden;
|
|
438
|
+
}
|
|
439
|
+
.${classPrefix}-line {
|
|
440
|
+
white-space: pre;
|
|
441
|
+
}
|
|
442
|
+
.${classPrefix}-text {
|
|
443
|
+
white-space: pre;
|
|
444
|
+
}
|
|
445
|
+
/* Selection styling */
|
|
446
|
+
.${classPrefix}-text::selection {
|
|
447
|
+
background-color: rgba(100, 150, 255, 0.3);
|
|
448
|
+
}
|
|
449
|
+
`
|
|
450
|
+
document.head.appendChild(style)
|
|
451
|
+
stylesInjected = true
|
|
452
|
+
}
|