@gridland/web 0.2.16 → 0.2.18

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 (38) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +99 -10
  3. package/dist/index.js.map +4 -4
  4. package/dist/vite-plugin.js +68 -37
  5. package/dist/vite-plugin.js.map +1 -1
  6. package/package.json +5 -4
  7. package/src/browser-buffer.ts +715 -0
  8. package/src/core-shims/index.ts +269 -0
  9. package/src/core-shims/renderable-types.ts +4 -0
  10. package/src/core-shims/rgba.ts +195 -0
  11. package/src/core-shims/types.ts +132 -0
  12. package/src/shims/bun-ffi-structs.ts +20 -0
  13. package/src/shims/bun-ffi.ts +28 -0
  14. package/src/shims/console-stub.ts +13 -0
  15. package/src/shims/console.ts +3 -0
  16. package/src/shims/devtools-polyfill-stub.ts +3 -0
  17. package/src/shims/edit-buffer-stub.ts +475 -0
  18. package/src/shims/editor-view-stub.ts +388 -0
  19. package/src/shims/events-shim.ts +81 -0
  20. package/src/shims/filters-stub.ts +4 -0
  21. package/src/shims/hast-stub.ts +8 -0
  22. package/src/shims/native-span-feed-stub.ts +7 -0
  23. package/src/shims/node-buffer.ts +39 -0
  24. package/src/shims/node-fs.ts +20 -0
  25. package/src/shims/node-os.ts +6 -0
  26. package/src/shims/node-path.ts +35 -0
  27. package/src/shims/node-stream.ts +10 -0
  28. package/src/shims/node-url.ts +8 -0
  29. package/src/shims/node-util.ts +33 -0
  30. package/src/shims/renderer-stub.ts +21 -0
  31. package/src/shims/slider-deps.ts +8 -0
  32. package/src/shims/syntax-style-shim.ts +23 -0
  33. package/src/shims/text-buffer-shim.ts +3 -0
  34. package/src/shims/text-buffer-view-shim.ts +2 -0
  35. package/src/shims/timeline-stub.ts +43 -0
  36. package/src/shims/tree-sitter-stub.ts +47 -0
  37. package/src/shims/tree-sitter-styled-text-stub.ts +8 -0
  38. package/src/shims/zig-stub.ts +20 -0
@@ -0,0 +1,388 @@
1
+ // Pure-JS EditorView implementation for browser environment.
2
+ // Wraps an EditBuffer and provides viewport/visual cursor mapping.
3
+
4
+ import type { EditBuffer, LogicalCursor } from "./edit-buffer-stub"
5
+
6
+ export interface Viewport {
7
+ offsetY: number
8
+ offsetX: number
9
+ height: number
10
+ width: number
11
+ }
12
+
13
+ export interface VisualCursor {
14
+ visualRow: number
15
+ visualCol: number
16
+ logicalRow: number
17
+ logicalCol: number
18
+ offset: number
19
+ }
20
+
21
+ export interface LineInfo {
22
+ lineIndex: number
23
+ lineCount: number
24
+ colIndex: number
25
+ colCount: number
26
+ byteIndex: number
27
+ byteCount: number
28
+ }
29
+
30
+ export class EditorView {
31
+ public readonly ptr: number = 0 // dummy pointer
32
+
33
+ private editBuffer: EditBuffer
34
+ private viewportWidth: number
35
+ private viewportHeight: number
36
+ private viewportOffsetX: number = 0
37
+ private viewportOffsetY: number = 0
38
+ private _destroyed: boolean = false
39
+ private _wrapMode: "none" | "char" | "word" = "word"
40
+ private _scrollMargin: number = 0
41
+ private _placeholderChunks: { text: string; fg?: any; bg?: any; attributes?: number }[] = []
42
+
43
+ // Selection state
44
+ private _selectionStart: number | null = null
45
+ private _selectionEnd: number | null = null
46
+
47
+ private _extmarksController: any = null
48
+
49
+ constructor(editBuffer: EditBuffer, width: number, height: number) {
50
+ this.editBuffer = editBuffer
51
+ this.viewportWidth = width
52
+ this.viewportHeight = height
53
+ }
54
+
55
+ static create(editBuffer: EditBuffer, viewportWidth: number, viewportHeight: number): EditorView {
56
+ return new EditorView(editBuffer, viewportWidth, viewportHeight)
57
+ }
58
+
59
+ // --- Viewport ---
60
+
61
+ setViewportSize(width: number, height: number): void {
62
+ this.viewportWidth = width
63
+ this.viewportHeight = height
64
+ }
65
+
66
+ setViewport(x: number, y: number, width: number, height: number, _moveCursor: boolean = true): void {
67
+ this.viewportOffsetX = x
68
+ this.viewportOffsetY = y
69
+ this.viewportWidth = width
70
+ this.viewportHeight = height
71
+ }
72
+
73
+ getViewport(): Viewport {
74
+ return {
75
+ offsetX: this.viewportOffsetX,
76
+ offsetY: this.viewportOffsetY,
77
+ width: this.viewportWidth,
78
+ height: this.viewportHeight,
79
+ }
80
+ }
81
+
82
+ setScrollMargin(margin: number): void {
83
+ this._scrollMargin = margin
84
+ }
85
+
86
+ setWrapMode(mode: "none" | "char" | "word"): void {
87
+ this._wrapMode = mode
88
+ }
89
+
90
+ // --- Line counts ---
91
+
92
+ getVirtualLineCount(): number {
93
+ // For no-wrap mode, virtual lines = logical lines
94
+ if (this._wrapMode === "none") {
95
+ return this.editBuffer.getLineCount()
96
+ }
97
+ // For wrap modes, calculate wrapped lines
98
+ return this.getTotalVirtualLineCount()
99
+ }
100
+
101
+ getTotalVirtualLineCount(): number {
102
+ if (this._wrapMode === "none" || this.viewportWidth <= 0) {
103
+ return this.editBuffer.getLineCount()
104
+ }
105
+ const text = this.editBuffer.getText()
106
+ const lines = text.split("\n")
107
+ let total = 0
108
+ for (const line of lines) {
109
+ total += Math.max(1, Math.ceil(line.length / this.viewportWidth))
110
+ }
111
+ return total
112
+ }
113
+
114
+ // --- Cursor ---
115
+
116
+ getCursor(): { row: number; col: number } {
117
+ const pos = this.editBuffer.getCursorPosition()
118
+ return { row: pos.row, col: pos.col }
119
+ }
120
+
121
+ getVisualCursor(): VisualCursor {
122
+ const pos = this.editBuffer.getCursorPosition()
123
+ // For simple single-line / no-wrap, visual = logical minus viewport offset
124
+ const visualRow = pos.row - this.viewportOffsetY
125
+ const visualCol = pos.col - this.viewportOffsetX
126
+ return {
127
+ visualRow: Math.max(0, visualRow),
128
+ visualCol: Math.max(0, visualCol),
129
+ logicalRow: pos.row,
130
+ logicalCol: pos.col,
131
+ offset: pos.offset,
132
+ }
133
+ }
134
+
135
+ setCursorByOffset(offset: number): void {
136
+ this.editBuffer.setCursorByOffset(offset)
137
+ this.ensureCursorVisible()
138
+ }
139
+
140
+ // --- Visual navigation ---
141
+
142
+ moveUpVisual(): void {
143
+ this.editBuffer.moveCursorUp()
144
+ this.ensureCursorVisible()
145
+ }
146
+
147
+ moveDownVisual(): void {
148
+ this.editBuffer.moveCursorDown()
149
+ this.ensureCursorVisible()
150
+ }
151
+
152
+ getVisualSOL(): VisualCursor {
153
+ const pos = this.editBuffer.getCursorPosition()
154
+ const offset = this.editBuffer.getLineStartOffset(pos.row)
155
+ return {
156
+ visualRow: pos.row - this.viewportOffsetY,
157
+ visualCol: 0,
158
+ logicalRow: pos.row,
159
+ logicalCol: 0,
160
+ offset,
161
+ }
162
+ }
163
+
164
+ getVisualEOL(): VisualCursor {
165
+ const eol = this.editBuffer.getEOL()
166
+ return {
167
+ visualRow: eol.row - this.viewportOffsetY,
168
+ visualCol: eol.col - this.viewportOffsetX,
169
+ logicalRow: eol.row,
170
+ logicalCol: eol.col,
171
+ offset: eol.offset,
172
+ }
173
+ }
174
+
175
+ getNextWordBoundary(): VisualCursor {
176
+ const lc = this.editBuffer.getNextWordBoundary()
177
+ return this.logicalToVisual(lc)
178
+ }
179
+
180
+ getPrevWordBoundary(): VisualCursor {
181
+ const lc = this.editBuffer.getPrevWordBoundary()
182
+ return this.logicalToVisual(lc)
183
+ }
184
+
185
+ getEOL(): VisualCursor {
186
+ const eol = this.editBuffer.getEOL()
187
+ return this.logicalToVisual(eol)
188
+ }
189
+
190
+ // --- Line info ---
191
+
192
+ getLineInfo(): LineInfo {
193
+ return this.getLogicalLineInfo()
194
+ }
195
+
196
+ getLogicalLineInfo(): LineInfo {
197
+ const pos = this.editBuffer.getCursorPosition()
198
+ const text = this.editBuffer.getText()
199
+ const lines = text.split("\n")
200
+ const line = lines[pos.row] || ""
201
+ return {
202
+ lineIndex: pos.row,
203
+ lineCount: lines.length,
204
+ colIndex: pos.col,
205
+ colCount: line.length,
206
+ byteIndex: pos.offset,
207
+ byteCount: new TextEncoder().encode(text).length,
208
+ }
209
+ }
210
+
211
+ // --- Selection ---
212
+
213
+ setSelection(start: number, end: number, _bgColor?: any, _fgColor?: any): void {
214
+ this._selectionStart = start
215
+ this._selectionEnd = end
216
+ }
217
+
218
+ updateSelection(end: number, _bgColor?: any, _fgColor?: any): void {
219
+ this._selectionEnd = end
220
+ }
221
+
222
+ resetSelection(): void {
223
+ this._selectionStart = null
224
+ this._selectionEnd = null
225
+ }
226
+
227
+ getSelection(): { start: number; end: number } | null {
228
+ if (this._selectionStart === null || this._selectionEnd === null) return null
229
+ return { start: this._selectionStart, end: this._selectionEnd }
230
+ }
231
+
232
+ hasSelection(): boolean {
233
+ return this._selectionStart !== null && this._selectionEnd !== null
234
+ }
235
+
236
+ setLocalSelection(
237
+ _anchorX: number,
238
+ _anchorY: number,
239
+ _focusX: number,
240
+ _focusY: number,
241
+ _bgColor?: any,
242
+ _fgColor?: any,
243
+ _updateCursor?: boolean,
244
+ _followCursor?: boolean,
245
+ ): boolean {
246
+ // Convert visual coords to offsets (simplified)
247
+ this._selectionStart = 0
248
+ this._selectionEnd = 0
249
+ return true
250
+ }
251
+
252
+ updateLocalSelection(
253
+ _anchorX: number,
254
+ _anchorY: number,
255
+ _focusX: number,
256
+ _focusY: number,
257
+ _bgColor?: any,
258
+ _fgColor?: any,
259
+ _updateCursor?: boolean,
260
+ _followCursor?: boolean,
261
+ ): boolean {
262
+ return true
263
+ }
264
+
265
+ resetLocalSelection(): void {
266
+ this.resetSelection()
267
+ }
268
+
269
+ getSelectedText(): string {
270
+ const sel = this.getSelection()
271
+ if (!sel) return ""
272
+ const text = this.editBuffer.getText()
273
+ const start = Math.min(sel.start, sel.end)
274
+ const end = Math.max(sel.start, sel.end)
275
+ return text.substring(start, end)
276
+ }
277
+
278
+ deleteSelectedText(): void {
279
+ const sel = this.getSelection()
280
+ if (!sel) return
281
+ const text = this.editBuffer.getText()
282
+ const start = Math.min(sel.start, sel.end)
283
+ const end = Math.max(sel.start, sel.end)
284
+ const newText = text.substring(0, start) + text.substring(end)
285
+ this.editBuffer.setText(newText)
286
+ this.editBuffer.setCursorByOffset(start)
287
+ this.resetSelection()
288
+ }
289
+
290
+ // --- Text ---
291
+
292
+ getText(): string {
293
+ return this.editBuffer.getText()
294
+ }
295
+
296
+ // --- Placeholder ---
297
+
298
+ setPlaceholderStyledText(chunks: { text: string; fg?: any; bg?: any; attributes?: number }[]): void {
299
+ this._placeholderChunks = chunks
300
+ }
301
+
302
+ // --- Tab ---
303
+
304
+ setTabIndicator(_indicator: string | number): void {}
305
+ setTabIndicatorColor(_color: any): void {}
306
+
307
+ // --- Measurement ---
308
+
309
+ measureForDimensions(width: number, _height: number): { lineCount: number; maxWidth: number } | null {
310
+ const text = this.editBuffer.getText()
311
+ const lines = text.split("\n")
312
+
313
+ if (this._wrapMode === "none" || width <= 0) {
314
+ let maxWidth = 0
315
+ for (const line of lines) {
316
+ maxWidth = Math.max(maxWidth, line.length)
317
+ }
318
+ // If text is empty but we have placeholder, use that for measurement
319
+ if (text === "" && this._placeholderChunks.length > 0) {
320
+ let placeholderLen = 0
321
+ for (const chunk of this._placeholderChunks) {
322
+ placeholderLen += chunk.text.length
323
+ }
324
+ maxWidth = Math.max(maxWidth, placeholderLen)
325
+ }
326
+ return { lineCount: lines.length, maxWidth }
327
+ }
328
+
329
+ let totalLines = 0
330
+ let maxWidth = 0
331
+ for (const line of lines) {
332
+ const wrappedLines = Math.max(1, Math.ceil(line.length / width))
333
+ totalLines += wrappedLines
334
+ maxWidth = Math.max(maxWidth, Math.min(line.length, width))
335
+ }
336
+ return { lineCount: totalLines, maxWidth }
337
+ }
338
+
339
+ // --- Extmarks ---
340
+
341
+ get extmarks(): any {
342
+ if (!this._extmarksController) {
343
+ this._extmarksController = {
344
+ destroy() {},
345
+ }
346
+ }
347
+ return this._extmarksController
348
+ }
349
+
350
+ // --- Cleanup ---
351
+
352
+ destroy(): void {
353
+ if (this._destroyed) return
354
+ this._destroyed = true
355
+ if (this._extmarksController && this._extmarksController.destroy) {
356
+ this._extmarksController.destroy()
357
+ }
358
+ }
359
+
360
+ // --- Helpers ---
361
+
362
+ private logicalToVisual(lc: LogicalCursor): VisualCursor {
363
+ return {
364
+ visualRow: lc.row - this.viewportOffsetY,
365
+ visualCol: lc.col - this.viewportOffsetX,
366
+ logicalRow: lc.row,
367
+ logicalCol: lc.col,
368
+ offset: lc.offset,
369
+ }
370
+ }
371
+
372
+ private ensureCursorVisible(): void {
373
+ const pos = this.editBuffer.getCursorPosition()
374
+ // Auto-scroll viewport to keep cursor visible
375
+ if (pos.row < this.viewportOffsetY) {
376
+ this.viewportOffsetY = pos.row
377
+ } else if (pos.row >= this.viewportOffsetY + this.viewportHeight) {
378
+ this.viewportOffsetY = pos.row - this.viewportHeight + 1
379
+ }
380
+ if (this._wrapMode === "none") {
381
+ if (pos.col < this.viewportOffsetX) {
382
+ this.viewportOffsetX = pos.col
383
+ } else if (pos.col >= this.viewportOffsetX + this.viewportWidth) {
384
+ this.viewportOffsetX = pos.col - this.viewportWidth + 1
385
+ }
386
+ }
387
+ }
388
+ }
@@ -0,0 +1,81 @@
1
+ // Browser-compatible EventEmitter shim.
2
+ // Replaces the 'events' npm package to avoid CJS/ESM issues in Vite.
3
+
4
+ type Listener = (...args: any[]) => void
5
+
6
+ export class EventEmitter {
7
+ private _listeners = new Map<string | symbol, Listener[]>()
8
+
9
+ on(event: string | symbol, listener: Listener): this {
10
+ const list = this._listeners.get(event) ?? []
11
+ list.push(listener)
12
+ this._listeners.set(event, list)
13
+ return this
14
+ }
15
+
16
+ addListener(event: string | symbol, listener: Listener): this {
17
+ return this.on(event, listener)
18
+ }
19
+
20
+ off(event: string | symbol, listener: Listener): this {
21
+ return this.removeListener(event, listener)
22
+ }
23
+
24
+ removeListener(event: string | symbol, listener: Listener): this {
25
+ const list = this._listeners.get(event)
26
+ if (list) {
27
+ const idx = list.indexOf(listener)
28
+ if (idx !== -1) list.splice(idx, 1)
29
+ if (list.length === 0) this._listeners.delete(event)
30
+ }
31
+ return this
32
+ }
33
+
34
+ removeAllListeners(event?: string | symbol): this {
35
+ if (event) {
36
+ this._listeners.delete(event)
37
+ } else {
38
+ this._listeners.clear()
39
+ }
40
+ return this
41
+ }
42
+
43
+ emit(event: string | symbol, ...args: any[]): boolean {
44
+ const list = this._listeners.get(event)
45
+ if (!list || list.length === 0) return false
46
+ for (const listener of [...list]) {
47
+ listener(...args)
48
+ }
49
+ return true
50
+ }
51
+
52
+ once(event: string | symbol, listener: Listener): this {
53
+ const wrapper = (...args: any[]) => {
54
+ this.removeListener(event, wrapper)
55
+ listener(...args)
56
+ }
57
+ return this.on(event, wrapper)
58
+ }
59
+
60
+ listenerCount(event: string | symbol): number {
61
+ return this._listeners.get(event)?.length ?? 0
62
+ }
63
+
64
+ listeners(event: string | symbol): Listener[] {
65
+ return [...(this._listeners.get(event) ?? [])]
66
+ }
67
+
68
+ eventNames(): (string | symbol)[] {
69
+ return [...this._listeners.keys()]
70
+ }
71
+
72
+ setMaxListeners(_n: number): this {
73
+ return this // no-op in browser
74
+ }
75
+
76
+ getMaxListeners(): number {
77
+ return Infinity
78
+ }
79
+ }
80
+
81
+ export default EventEmitter
@@ -0,0 +1,4 @@
1
+ // Stub for opentui/packages/core/src/post/filters.ts
2
+ export function applyFilters(..._args: any[]): any {
3
+ return null
4
+ }
@@ -0,0 +1,8 @@
1
+ // Stub for hast-styled-text
2
+ export function hastToStyledText(..._args: any[]): any {
3
+ return null
4
+ }
5
+
6
+ export function hastToTextChunks(..._args: any[]): any[] {
7
+ return []
8
+ }
@@ -0,0 +1,7 @@
1
+ // Stub for opentui/packages/core/src/NativeSpanFeed.ts
2
+ export class NativeSpanFeed {
3
+ static create(): NativeSpanFeed {
4
+ return new NativeSpanFeed()
5
+ }
6
+ destroy(): void {}
7
+ }
@@ -0,0 +1,39 @@
1
+ // Browser shim for node:buffer
2
+ // TextEncoder/TextDecoder are available in browsers
3
+ const BrowserBuffer = {
4
+ from(data: string | ArrayBuffer | Uint8Array, encoding?: string): Uint8Array {
5
+ if (typeof data === "string") {
6
+ const encoder = new TextEncoder()
7
+ return encoder.encode(data)
8
+ }
9
+ if (data instanceof ArrayBuffer) {
10
+ return new Uint8Array(data)
11
+ }
12
+ if (data instanceof Uint8Array) {
13
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
14
+ }
15
+ return new Uint8Array(0)
16
+ },
17
+
18
+ alloc(size: number): Uint8Array {
19
+ return new Uint8Array(size)
20
+ },
21
+
22
+ isBuffer(obj: any): boolean {
23
+ return obj instanceof Uint8Array
24
+ },
25
+
26
+ concat(list: Uint8Array[], totalLength?: number): Uint8Array {
27
+ const length = totalLength ?? list.reduce((acc, buf) => acc + buf.byteLength, 0)
28
+ const result = new Uint8Array(length)
29
+ let offset = 0
30
+ for (const buf of list) {
31
+ result.set(buf, offset)
32
+ offset += buf.byteLength
33
+ }
34
+ return result
35
+ },
36
+ }
37
+
38
+ export const Buffer = BrowserBuffer
39
+ export default { Buffer: BrowserBuffer }
@@ -0,0 +1,20 @@
1
+ // Browser stub for node:fs and fs/promises
2
+ export function existsSync(_path: string): boolean { return false }
3
+ export function readFileSync(_path: string, _encoding?: string): string { return "" }
4
+ export function writeFileSync(): void {}
5
+ export function mkdirSync(): void {}
6
+ export function readdirSync(): string[] { return [] }
7
+ export function statSync(): any { return { isDirectory: () => false, isFile: () => false } }
8
+ export function unlinkSync(): void {}
9
+
10
+ // fs/promises
11
+ export async function readFile(): Promise<string> { return "" }
12
+ export async function writeFile(): Promise<void> {}
13
+ export async function mkdir(): Promise<void> {}
14
+ export async function readdir(): Promise<string[]> { return [] }
15
+ export async function stat(): Promise<any> { return { isDirectory: () => false, isFile: () => false } }
16
+
17
+ export default {
18
+ existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, unlinkSync,
19
+ promises: { readFile, writeFile, mkdir, readdir, stat },
20
+ }
@@ -0,0 +1,6 @@
1
+ // Browser stub for node:os
2
+ export function homedir(): string { return "/home/user" }
3
+ export function tmpdir(): string { return "/tmp" }
4
+ export function platform(): string { return "browser" }
5
+ export function arch(): string { return "wasm" }
6
+ export default { homedir, tmpdir, platform, arch }
@@ -0,0 +1,35 @@
1
+ // Browser shim for node:path
2
+ export function join(...parts: string[]): string {
3
+ return parts.join("/").replace(/\/+/g, "/")
4
+ }
5
+ export function resolve(...parts: string[]): string {
6
+ return join(...parts)
7
+ }
8
+ export function dirname(p: string): string {
9
+ return p.split("/").slice(0, -1).join("/") || "/"
10
+ }
11
+ export function basename(p: string, ext?: string): string {
12
+ const base = p.split("/").pop() || ""
13
+ if (ext && base.endsWith(ext)) return base.slice(0, -ext.length)
14
+ return base
15
+ }
16
+ export function extname(p: string): string {
17
+ const base = basename(p)
18
+ const idx = base.lastIndexOf(".")
19
+ return idx >= 0 ? base.slice(idx) : ""
20
+ }
21
+ export function parse(p: string): { root: string; dir: string; base: string; ext: string; name: string } {
22
+ const dir = dirname(p)
23
+ const base = basename(p)
24
+ const ext = extname(p)
25
+ const name = ext ? base.slice(0, -ext.length) : base
26
+ return { root: p.startsWith("/") ? "/" : "", dir, base, ext, name }
27
+ }
28
+ export function isAbsolute(p: string): boolean {
29
+ return p.startsWith("/")
30
+ }
31
+ export function relative(from: string, to: string): string {
32
+ return to.replace(from, "").replace(/^\//, "")
33
+ }
34
+ export const sep = "/"
35
+ export default { join, resolve, dirname, basename, extname, parse, isAbsolute, relative, sep }
@@ -0,0 +1,10 @@
1
+ // Browser stub for node:stream
2
+ export class Writable {
3
+ write(_chunk: any): boolean { return true }
4
+ end(): void {}
5
+ }
6
+ export class Readable {
7
+ read(): any { return null }
8
+ }
9
+ export class Transform extends Writable {}
10
+ export default { Writable, Readable, Transform }
@@ -0,0 +1,8 @@
1
+ // Browser stub for node:url
2
+ export function fileURLToPath(url: string): string {
3
+ return url.replace("file://", "")
4
+ }
5
+ export function pathToFileURL(path: string): URL {
6
+ return new URL(`file://${path}`)
7
+ }
8
+ export default { fileURLToPath, pathToFileURL }
@@ -0,0 +1,33 @@
1
+ // Browser stub for node:util
2
+ export function inspect(obj: any, _options?: any): string {
3
+ try {
4
+ return JSON.stringify(obj, null, 2)
5
+ } catch {
6
+ return String(obj)
7
+ }
8
+ }
9
+
10
+ export function format(fmt: string, ...args: any[]): string {
11
+ let i = 0
12
+ return fmt.replace(/%[sdjifoO%]/g, (match) => {
13
+ if (match === "%%") return "%"
14
+ if (i >= args.length) return match
15
+ return String(args[i++])
16
+ })
17
+ }
18
+
19
+ export function promisify(fn: Function): Function {
20
+ return (...args: any[]) =>
21
+ new Promise((resolve, reject) => {
22
+ fn(...args, (err: any, result: any) => {
23
+ if (err) reject(err)
24
+ else resolve(result)
25
+ })
26
+ })
27
+ }
28
+
29
+ export function isDeepStrictEqual(a: any, b: any): boolean {
30
+ return JSON.stringify(a) === JSON.stringify(b)
31
+ }
32
+
33
+ export default { inspect, format, promisify, isDeepStrictEqual }
@@ -0,0 +1,21 @@
1
+ // Stub for opentui/packages/core/src/renderer.ts
2
+ import { EventEmitter } from "events"
3
+
4
+ export enum CliRenderEvents {
5
+ DESTROY = "destroy",
6
+ DEBUG_OVERLAY_TOGGLE = "debug_overlay_toggle",
7
+ }
8
+
9
+ export class CliRenderer extends EventEmitter {
10
+ root: any = null
11
+ keyInput: any = new EventEmitter()
12
+ destroy(): void {
13
+ this.emit(CliRenderEvents.DESTROY)
14
+ }
15
+ }
16
+
17
+ export type MouseEvent = any
18
+
19
+ export function createCliRenderer(): Promise<CliRenderer> {
20
+ return Promise.resolve(new CliRenderer())
21
+ }
@@ -0,0 +1,8 @@
1
+ // Minimal barrel for Slider.ts that avoids the circular dependency.
2
+ // Slider.ts imports { OptimizedBuffer, parseColor, Renderable, RGBA, RenderableOptions, RenderContext } from "../index"
3
+ // We provide these directly from the source files, bypassing any barrel that re-exports renderables.
4
+
5
+ export { Renderable, type RenderableOptions } from "../../../../opentui/packages/core/src/Renderable"
6
+ export type { RenderContext } from "../core-shims/types"
7
+ export { BrowserBuffer as OptimizedBuffer } from "../browser-buffer"
8
+ export { RGBA, parseColor, type ColorInput } from "../core-shims/rgba"