@nick-skriabin/glyph 0.1.38 → 0.1.40

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/dist/index.js CHANGED
@@ -1,4807 +1,15 @@
1
- import React15, { createContext, forwardRef, useState, useContext, useRef, useEffect, useLayoutEffect, useCallback, useMemo } from 'react';
2
- import ReactReconciler from 'react-reconciler';
3
- import stringWidth from 'string-width';
4
- import Yoga, { FlexDirection, Justify, Align, Direction, Edge, Wrap, Gutter, PositionType, Overflow, MeasureMode } from 'yoga-layout';
5
-
6
- // src/render.ts
7
-
8
- // src/reconciler/nodes.ts
9
- var nextFocusId = 0;
10
- function generateFocusId() {
11
- return `glyph-focus-${nextFocusId++}`;
12
- }
13
- function createGlyphNode(type, props) {
14
- const style = props.style ?? {};
15
- return {
16
- type,
17
- props,
18
- style,
19
- children: [],
20
- rawTextChildren: [],
21
- parent: null,
22
- yogaNode: null,
23
- text: null,
24
- layout: { x: 0, y: 0, width: 0, height: 0, innerX: 0, innerY: 0, innerWidth: 0, innerHeight: 0 },
25
- focusId: type === "input" ? generateFocusId() : props.focusable ? generateFocusId() : null,
26
- hidden: false
27
- };
28
- }
29
- function appendChild(parent, child) {
30
- child.parent = parent;
31
- parent.children.push(child);
32
- }
33
- function removeChild(parent, child) {
34
- const idx = parent.children.indexOf(child);
35
- if (idx !== -1) {
36
- parent.children.splice(idx, 1);
37
- child.parent = null;
38
- }
39
- }
40
- function insertBefore(parent, child, beforeChild) {
41
- child.parent = parent;
42
- const idx = parent.children.indexOf(beforeChild);
43
- if (idx !== -1) {
44
- parent.children.splice(idx, 0, child);
45
- } else {
46
- parent.children.push(child);
47
- }
48
- }
49
- function getInheritedTextStyle(node) {
50
- const result = {};
51
- let current = node;
52
- while (current) {
53
- const s = current.style;
54
- if (result.color === void 0 && s.color !== void 0) result.color = s.color;
55
- if (result.bg === void 0 && s.bg !== void 0) result.bg = s.bg;
56
- if (result.bold === void 0 && s.bold !== void 0) result.bold = s.bold;
57
- if (result.dim === void 0 && s.dim !== void 0) result.dim = s.dim;
58
- if (result.italic === void 0 && s.italic !== void 0) result.italic = s.italic;
59
- if (result.underline === void 0 && s.underline !== void 0) result.underline = s.underline;
60
- current = current.parent;
61
- }
62
- return result;
63
- }
64
- function collectTextContent(node) {
65
- if (node.text != null) return node.text;
66
- let result = "";
67
- for (const child of node.children) {
68
- result += collectTextContent(child);
69
- }
70
- return result;
71
- }
72
-
73
- // src/reconciler/hostConfig.ts
74
- var DefaultEventPriority = 32;
75
- var hostConfig = {
76
- supportsMutation: true,
77
- supportsPersistence: false,
78
- supportsHydration: false,
79
- isPrimaryRenderer: true,
80
- // Timeouts
81
- scheduleTimeout: setTimeout,
82
- cancelTimeout: clearTimeout,
83
- noTimeout: -1,
84
- supportsMicrotasks: true,
85
- scheduleMicrotask: queueMicrotask,
86
- // Priority & event methods required by react-reconciler v0.31
87
- getCurrentUpdatePriority: () => DefaultEventPriority,
88
- setCurrentUpdatePriority: (_priority) => {
89
- },
90
- resolveUpdatePriority: () => DefaultEventPriority,
91
- getCurrentEventPriority: () => DefaultEventPriority,
92
- resolveEventType: () => null,
93
- resolveEventTimeStamp: () => -1.1,
94
- shouldAttemptEagerTransition: () => false,
95
- getInstanceFromNode: () => null,
96
- beforeActiveInstanceBlur: () => {
97
- },
98
- afterActiveInstanceBlur: () => {
99
- },
100
- prepareScopeUpdate: () => {
101
- },
102
- getInstanceFromScope: () => null,
103
- detachDeletedInstance: () => {
104
- },
105
- requestPostPaintCallback: (_callback) => {
106
- },
107
- // Commit suspension stubs (required by react-reconciler v0.31)
108
- maySuspendCommit: (_type, _props) => false,
109
- preloadInstance: (_type, _props) => true,
110
- startSuspendingCommit: () => {
111
- },
112
- suspendInstance: (_type, _props) => {
113
- },
114
- waitForCommitToBeReady: () => null,
115
- // Transition stubs
116
- NotPendingTransition: null,
117
- HostTransitionContext: { $$typeof: /* @__PURE__ */ Symbol.for("react.context"), _currentValue: null },
118
- resetFormInstance: (_instance) => {
119
- },
120
- // Console binding
121
- bindToConsole: (methodName, args, _errorPrefix) => {
122
- return Function.prototype.bind.call(
123
- console[methodName],
124
- console,
125
- ...args
126
- );
127
- },
128
- // Resource/singleton stubs
129
- supportsResources: false,
130
- isHostHoistableType: (_type, _props) => false,
131
- supportsSingletons: false,
132
- isHostSingletonType: (_type) => false,
133
- supportsTestSelectors: false,
134
- createInstance(type, props, _rootContainer, _hostContext, _internalHandle) {
135
- return createGlyphNode(type, props);
136
- },
137
- createTextInstance(text, _rootContainer, _hostContext, _internalHandle) {
138
- return { type: "raw-text", text, parent: null };
139
- },
140
- appendInitialChild(parentInstance, child) {
141
- if (child.type === "raw-text") {
142
- const textChild = child;
143
- textChild.parent = parentInstance;
144
- parentInstance.rawTextChildren.push(textChild);
145
- parentInstance.text = parentInstance.rawTextChildren.map((t) => t.text).join("");
146
- } else {
147
- appendChild(parentInstance, child);
148
- }
149
- },
150
- finalizeInitialChildren(_instance, _type, _props, _rootContainer, _hostContext) {
151
- return false;
152
- },
153
- shouldSetTextContent(_type, _props) {
154
- return false;
155
- },
156
- getRootHostContext(_rootContainer) {
157
- return {};
158
- },
159
- getChildHostContext(parentHostContext, _type, _rootContainer) {
160
- return parentHostContext;
161
- },
162
- getPublicInstance(instance) {
163
- return instance;
164
- },
165
- prepareForCommit(_containerInfo) {
166
- return null;
167
- },
168
- resetAfterCommit(containerInfo) {
169
- containerInfo.onCommit();
170
- },
171
- preparePortalMount() {
172
- },
173
- // Mutation methods
174
- appendChild(parentInstance, child) {
175
- if (child.type === "raw-text") {
176
- const textChild = child;
177
- textChild.parent = parentInstance;
178
- parentInstance.rawTextChildren.push(textChild);
179
- parentInstance.text = parentInstance.rawTextChildren.map((t) => t.text).join("");
180
- } else {
181
- appendChild(parentInstance, child);
182
- }
183
- },
184
- appendChildToContainer(container, child) {
185
- if (child.type === "raw-text") return;
186
- const node = child;
187
- node.parent = null;
188
- container.children.push(node);
189
- },
190
- insertBefore(parentInstance, child, beforeChild) {
191
- if (child.type === "raw-text" || beforeChild.type === "raw-text") return;
192
- insertBefore(parentInstance, child, beforeChild);
193
- },
194
- insertInContainerBefore(container, child, beforeChild) {
195
- if (child.type === "raw-text" || beforeChild.type === "raw-text") return;
196
- const node = child;
197
- const before = beforeChild;
198
- const idx = container.children.indexOf(before);
199
- if (idx !== -1) {
200
- container.children.splice(idx, 0, node);
201
- } else {
202
- container.children.push(node);
203
- }
204
- },
205
- removeChild(parentInstance, child) {
206
- if (child.type === "raw-text") {
207
- const textChild = child;
208
- textChild.parent = null;
209
- const idx = parentInstance.rawTextChildren.indexOf(textChild);
210
- if (idx !== -1) parentInstance.rawTextChildren.splice(idx, 1);
211
- parentInstance.text = parentInstance.rawTextChildren.map((t) => t.text).join("") || null;
212
- return;
213
- }
214
- removeChild(parentInstance, child);
215
- },
216
- removeChildFromContainer(container, child) {
217
- if (child.type === "raw-text") return;
218
- const node = child;
219
- const idx = container.children.indexOf(node);
220
- if (idx !== -1) {
221
- container.children.splice(idx, 1);
222
- }
223
- },
224
- commitTextUpdate(textInstance, _oldText, newText) {
225
- textInstance.text = newText;
226
- if (textInstance.parent) {
227
- textInstance.parent.text = textInstance.parent.rawTextChildren.map((t) => t.text).join("");
228
- }
229
- },
230
- // v0.31 signature: (instance, type, oldProps, newProps, internalHandle)
231
- // updatePayload was removed in this version
232
- commitUpdate(instance, _type, _oldProps, newProps, _internalHandle) {
233
- instance.props = newProps;
234
- instance.style = newProps.style ?? {};
235
- if (newProps.focusable && !instance.focusId) {
236
- instance.focusId = `focus-${Math.random().toString(36).slice(2, 9)}`;
237
- }
238
- },
239
- hideInstance(instance) {
240
- instance.hidden = true;
241
- },
242
- hideTextInstance(textInstance) {
243
- textInstance.text = "";
244
- },
245
- unhideInstance(instance, _props) {
246
- instance.hidden = false;
247
- },
248
- unhideTextInstance(textInstance, text) {
249
- textInstance.text = text;
250
- },
251
- clearContainer(container) {
252
- container.children.length = 0;
253
- },
254
- resetTextContent(instance) {
255
- instance.text = null;
256
- }
257
- };
258
-
259
- // src/reconciler/reconciler.ts
260
- var reconciler = ReactReconciler(hostConfig);
261
- reconciler.injectIntoDevTools({
262
- bundleType: process.env.NODE_ENV === "production" ? 0 : 1,
263
- version: "0.1.0",
264
- rendererPackageName: "glyph"
265
- });
266
-
267
- // src/runtime/terminal.ts
268
- var ESC = "\x1B";
269
- var CSI = `${ESC}[`;
270
- var Terminal = class {
271
- stdout;
272
- stdin;
273
- wasRaw = false;
274
- cleanedUp = false;
275
- // Data handler dispatch - single stdin listener, filters OSC, dispatches clean data
276
- dataHandlers = /* @__PURE__ */ new Set();
277
- stdinAttached = false;
278
- // OSC response filtering state
279
- oscState = "normal";
280
- oscAccum = "";
281
- escFlushTimer = null;
282
- // Terminal palette (populated by queryPalette)
283
- palette = /* @__PURE__ */ new Map();
284
- paletteResolve = null;
285
- constructor(stdout = process.stdout, stdin = process.stdin) {
286
- this.stdout = stdout;
287
- this.stdin = stdin;
288
- }
289
- get columns() {
290
- return this.stdout.columns || 80;
291
- }
292
- get rows() {
293
- return this.stdout.rows || 24;
294
- }
295
- enterRawMode() {
296
- if (this.stdin.isTTY) {
297
- this.wasRaw = this.stdin.isRaw;
298
- this.stdin.setRawMode(true);
299
- this.stdin.resume();
300
- this.stdin.setEncoding("utf-8");
301
- }
302
- }
303
- exitRawMode() {
304
- if (this.stdin.isTTY && !this.wasRaw) {
305
- this.stdin.setRawMode(false);
306
- this.stdin.pause();
307
- }
308
- }
309
- write(data) {
310
- this.stdout.write(data);
311
- }
312
- hideCursor() {
313
- this.write(`${CSI}?25l`);
314
- }
315
- showCursor() {
316
- this.write(`${CSI}?25h`);
317
- }
318
- /** Move cursor to (x, y) position (0-indexed) */
319
- moveCursor(x, y) {
320
- this.write(`${CSI}${y + 1};${x + 1}H`);
321
- }
322
- /** Set cursor color using OSC 12 */
323
- setCursorColor(color) {
324
- this.write(`${ESC}]12;${color}\x07`);
325
- }
326
- /** Reset cursor color to terminal default */
327
- resetCursorColor() {
328
- this.write(`${ESC}]112\x07`);
329
- }
330
- enterAltScreen() {
331
- this.write(`${CSI}?1049h`);
332
- }
333
- exitAltScreen() {
334
- this.write(`${CSI}?1049l`);
335
- }
336
- clearScreen() {
337
- this.write(`${CSI}2J${CSI}H`);
338
- }
339
- resetStyles() {
340
- this.write(`${CSI}0m`);
341
- }
342
- /** Enable kitty keyboard protocol for enhanced key detection */
343
- enableKittyKeyboard() {
344
- this.write(`${CSI}>1u`);
345
- }
346
- /** Disable kitty keyboard protocol */
347
- disableKittyKeyboard() {
348
- this.write(`${CSI}<u`);
349
- }
350
- setup() {
351
- this.enterRawMode();
352
- this.enterAltScreen();
353
- this.enableKittyKeyboard();
354
- this.hideCursor();
355
- this.clearScreen();
356
- this.attachStdinListener();
357
- this.installCleanupHandlers();
358
- }
359
- cleanup() {
360
- if (this.cleanedUp) return;
361
- this.cleanedUp = true;
362
- if (this.escFlushTimer !== null) {
363
- clearTimeout(this.escFlushTimer);
364
- this.escFlushTimer = null;
365
- }
366
- this.resetStyles();
367
- this.resetCursorColor();
368
- this.disableKittyKeyboard();
369
- this.showCursor();
370
- this.exitAltScreen();
371
- this.exitRawMode();
372
- }
373
- /** Restore terminal state for background suspension (does NOT mark as cleaned up). */
374
- suspend() {
375
- if (this.escFlushTimer !== null) {
376
- clearTimeout(this.escFlushTimer);
377
- this.escFlushTimer = null;
378
- }
379
- this.oscState = "normal";
380
- this.oscAccum = "";
381
- this.resetStyles();
382
- this.resetCursorColor();
383
- this.disableKittyKeyboard();
384
- this.showCursor();
385
- this.exitAltScreen();
386
- this.exitRawMode();
387
- }
388
- /** Re-enter raw mode and alt screen after SIGCONT resume. */
389
- resume() {
390
- this.enterRawMode();
391
- this.enterAltScreen();
392
- this.enableKittyKeyboard();
393
- this.hideCursor();
394
- this.clearScreen();
395
- }
396
- // ---- Data handling with OSC filtering ----
397
- attachStdinListener() {
398
- if (this.stdinAttached) return;
399
- this.stdinAttached = true;
400
- this.stdin.on("data", (data) => {
401
- let str = typeof data === "string" ? data : data.toString("utf-8");
402
- this.dispatchFiltered(str);
403
- });
404
- }
405
- onData(handler) {
406
- this.dataHandlers.add(handler);
407
- return () => {
408
- this.dataHandlers.delete(handler);
409
- };
410
- }
411
- // ---- OSC response filtering ----
412
- dispatchFiltered(raw) {
413
- if (this.escFlushTimer !== null) {
414
- clearTimeout(this.escFlushTimer);
415
- this.escFlushTimer = null;
416
- }
417
- const clean = this.filterOsc(raw);
418
- if (clean.length > 0) {
419
- for (const handler of this.dataHandlers) {
420
- handler(clean);
421
- }
422
- }
423
- if (this.oscState === "esc") {
424
- this.escFlushTimer = setTimeout(() => {
425
- this.escFlushTimer = null;
426
- this.oscState = "normal";
427
- for (const handler of this.dataHandlers) {
428
- handler("\x1B");
429
- }
430
- }, 50);
431
- }
432
- }
433
- filterOsc(raw) {
434
- let clean = "";
435
- for (let i = 0; i < raw.length; i++) {
436
- const ch = raw[i];
437
- const code = raw.charCodeAt(i);
438
- switch (this.oscState) {
439
- case "normal":
440
- if (code === 27) {
441
- this.oscState = "esc";
442
- } else {
443
- clean += ch;
444
- }
445
- break;
446
- case "esc":
447
- if (ch === "]") {
448
- this.oscState = "osc";
449
- this.oscAccum = "";
450
- } else {
451
- clean += "\x1B" + ch;
452
- this.oscState = "normal";
453
- }
454
- break;
455
- case "osc":
456
- if (code === 7) {
457
- this.handleOscResponse(this.oscAccum);
458
- this.oscAccum = "";
459
- this.oscState = "normal";
460
- } else if (code === 27) {
461
- this.oscState = "osc_esc";
462
- } else {
463
- this.oscAccum += ch;
464
- }
465
- break;
466
- case "osc_esc":
467
- if (ch === "\\") {
468
- this.handleOscResponse(this.oscAccum);
469
- this.oscAccum = "";
470
- this.oscState = "normal";
471
- } else {
472
- this.oscAccum += "\x1B" + ch;
473
- this.oscState = "osc";
474
- }
475
- break;
476
- }
477
- }
478
- return clean;
479
- }
480
- handleOscResponse(data) {
481
- const match = data.match(
482
- /^4;(\d+);rgb:([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)/
483
- );
484
- if (match) {
485
- const index = parseInt(match[1], 10);
486
- const r = parseInt(match[2].substring(0, 2), 16);
487
- const g = parseInt(match[3].substring(0, 2), 16);
488
- const b = parseInt(match[4].substring(0, 2), 16);
489
- this.palette.set(index, [r, g, b]);
490
- if (this.palette.size >= 16 && this.paletteResolve) {
491
- this.paletteResolve();
492
- this.paletteResolve = null;
493
- }
494
- }
495
- }
496
- // ---- Palette querying ----
497
- queryPalette() {
498
- return new Promise((resolve) => {
499
- const done = () => resolve(this.palette);
500
- const timeout = setTimeout(done, 200);
501
- this.paletteResolve = () => {
502
- clearTimeout(timeout);
503
- done();
504
- };
505
- let query = "";
506
- for (let i = 0; i < 16; i++) {
507
- query += `\x1B]4;${i};?\x07`;
508
- }
509
- this.write(query);
510
- });
511
- }
512
- // ---- Event handling ----
513
- onResize(handler) {
514
- this.stdout.on("resize", handler);
515
- return () => {
516
- this.stdout.off("resize", handler);
517
- };
518
- }
519
- installCleanupHandlers() {
520
- const doCleanup = () => this.cleanup();
521
- process.on("exit", doCleanup);
522
- const handleSignal = (signal) => {
523
- doCleanup();
524
- process.kill(process.pid, signal);
525
- };
526
- process.once("SIGINT", () => handleSignal("SIGINT"));
527
- process.once("SIGTERM", () => handleSignal("SIGTERM"));
528
- process.on("uncaughtException", (err) => {
529
- doCleanup();
530
- console.error(err);
531
- process.exit(1);
532
- });
533
- process.on("unhandledRejection", (err) => {
534
- doCleanup();
535
- console.error(err);
536
- process.exit(1);
537
- });
538
- }
539
- };
540
-
541
- // src/runtime/input.ts
542
- function getKeyNameFromCode(code) {
543
- switch (code) {
544
- // Standard ASCII
545
- case 9:
546
- return "tab";
547
- case 13:
548
- return "return";
549
- case 27:
550
- return "escape";
551
- case 32:
552
- return " ";
553
- case 127:
554
- return "backspace";
555
- // Kitty protocol special keys
556
- case 57358:
557
- return "capslock";
558
- case 57359:
559
- return "scrolllock";
560
- case 57360:
561
- return "numlock";
562
- case 57361:
563
- return "printscreen";
564
- case 57362:
565
- return "pause";
566
- case 57363:
567
- return "menu";
568
- // Function keys (kitty uses these codes)
569
- case 57364:
570
- return "f13";
571
- case 57365:
572
- return "f14";
573
- case 57366:
574
- return "f15";
575
- case 57367:
576
- return "f16";
577
- case 57368:
578
- return "f17";
579
- case 57369:
580
- return "f18";
581
- case 57370:
582
- return "f19";
583
- case 57371:
584
- return "f20";
585
- case 57372:
586
- return "f21";
587
- case 57373:
588
- return "f22";
589
- case 57374:
590
- return "f23";
591
- case 57375:
592
- return "f24";
593
- case 57376:
594
- return "f25";
595
- // Keypad keys
596
- case 57399:
597
- return "kp0";
598
- case 57400:
599
- return "kp1";
600
- case 57401:
601
- return "kp2";
602
- case 57402:
603
- return "kp3";
604
- case 57403:
605
- return "kp4";
606
- case 57404:
607
- return "kp5";
608
- case 57405:
609
- return "kp6";
610
- case 57406:
611
- return "kp7";
612
- case 57407:
613
- return "kp8";
614
- case 57408:
615
- return "kp9";
616
- case 57409:
617
- return "kpdecimal";
618
- case 57410:
619
- return "kpdivide";
620
- case 57411:
621
- return "kpmultiply";
622
- case 57412:
623
- return "kpminus";
624
- case 57413:
625
- return "kpplus";
626
- case 57414:
627
- return "kpenter";
628
- case 57415:
629
- return "kpequal";
630
- // Navigation (kitty protocol)
631
- case 57416:
632
- return "kpleft";
633
- case 57417:
634
- return "kpright";
635
- case 57418:
636
- return "kpup";
637
- case 57419:
638
- return "kpdown";
639
- case 57420:
640
- return "kppageup";
641
- case 57421:
642
- return "kppagedown";
643
- case 57422:
644
- return "kphome";
645
- case 57423:
646
- return "kpend";
647
- case 57424:
648
- return "kpinsert";
649
- case 57425:
650
- return "kpdelete";
651
- // Media keys
652
- case 57428:
653
- return "mediaplaypause";
654
- case 57429:
655
- return "mediastop";
656
- case 57430:
657
- return "mediaprev";
658
- case 57431:
659
- return "medianext";
660
- case 57432:
661
- return "mediarewind";
662
- case 57433:
663
- return "mediafastforward";
664
- case 57434:
665
- return "mediamute";
666
- case 57435:
667
- return "volumedown";
668
- case 57436:
669
- return "volumeup";
670
- default:
671
- if (code >= 32 && code <= 126) {
672
- return String.fromCharCode(code).toLowerCase();
673
- }
674
- return "unknown";
675
- }
676
- }
677
- function getTildeKeyName(param) {
678
- const baseParam = param.split(";")[0];
679
- switch (baseParam) {
680
- case "1":
681
- return "home";
682
- case "2":
683
- return "insert";
684
- case "3":
685
- return "delete";
686
- case "4":
687
- return "end";
688
- case "5":
689
- return "pageup";
690
- case "6":
691
- return "pagedown";
692
- case "7":
693
- return "home";
694
- case "8":
695
- return "end";
696
- case "11":
697
- return "f1";
698
- case "12":
699
- return "f2";
700
- case "13":
701
- return "f3";
702
- case "14":
703
- return "f4";
704
- case "15":
705
- return "f5";
706
- case "17":
707
- return "f6";
708
- case "18":
709
- return "f7";
710
- case "19":
711
- return "f8";
712
- case "20":
713
- return "f9";
714
- case "21":
715
- return "f10";
716
- case "23":
717
- return "f11";
718
- case "24":
719
- return "f12";
720
- case "25":
721
- return "f13";
722
- case "26":
723
- return "f14";
724
- case "28":
725
- return "f15";
726
- case "29":
727
- return "f16";
728
- case "31":
729
- return "f17";
730
- case "32":
731
- return "f18";
732
- case "33":
733
- return "f19";
734
- case "34":
735
- return "f20";
736
- default:
737
- return "unknown";
738
- }
739
- }
740
- function applyModifiers(key, mod) {
741
- const m = mod - 1;
742
- if (m & 1) key.shift = true;
743
- if (m & 2) key.alt = true;
744
- if (m & 4) key.ctrl = true;
745
- if (m & 8) key.meta = true;
746
- }
747
- function parseKeySequence(data) {
748
- const keys = [];
749
- let i = 0;
750
- while (i < data.length) {
751
- const ch = data[i];
752
- const code = data.charCodeAt(i);
753
- if (ch === "\x1B") {
754
- if (data[i + 1] === "[") {
755
- const seq = parseCsiSequence(data, i);
756
- if (seq) {
757
- keys.push(seq.key);
758
- i = seq.end;
759
- continue;
760
- }
761
- }
762
- if (data[i + 1] === "O") {
763
- const seq = parseSs3Sequence(data, i);
764
- if (seq) {
765
- keys.push(seq.key);
766
- i = seq.end;
767
- continue;
768
- }
769
- }
770
- if (i + 1 < data.length && data.charCodeAt(i + 1) >= 32) {
771
- keys.push({
772
- name: data[i + 1].toLowerCase(),
773
- sequence: data.substring(i, i + 2),
774
- alt: true
775
- });
776
- i += 2;
777
- continue;
778
- }
779
- keys.push({ name: "escape", sequence: "\x1B" });
780
- i++;
781
- continue;
782
- }
783
- if (code >= 1 && code <= 26) {
784
- const letter = String.fromCharCode(code + 96);
785
- if (code === 13) {
786
- keys.push({ name: "return", sequence: "\r" });
787
- } else if (code === 9) {
788
- keys.push({ name: "tab", sequence: " " });
789
- } else if (code === 8) {
790
- keys.push({ name: "backspace", sequence: "\b" });
791
- } else {
792
- keys.push({ name: letter, sequence: ch, ctrl: true });
793
- }
794
- i++;
795
- continue;
796
- }
797
- if (code === 127) {
798
- keys.push({ name: "backspace", sequence: ch });
799
- i++;
800
- continue;
801
- }
802
- keys.push({ name: ch, sequence: ch });
803
- i++;
804
- }
805
- return keys;
806
- }
807
- function parseSs3Sequence(data, start) {
808
- if (start + 2 >= data.length) return null;
809
- const final = data[start + 2];
810
- const sequence = data.substring(start, start + 3);
811
- let key;
812
- switch (final) {
813
- // Arrow keys (some terminals)
814
- case "A":
815
- key = { name: "up", sequence };
816
- break;
817
- case "B":
818
- key = { name: "down", sequence };
819
- break;
820
- case "C":
821
- key = { name: "right", sequence };
822
- break;
823
- case "D":
824
- key = { name: "left", sequence };
825
- break;
826
- // Home/End (some terminals)
827
- case "H":
828
- key = { name: "home", sequence };
829
- break;
830
- case "F":
831
- key = { name: "end", sequence };
832
- break;
833
- // Function keys F1-F4
834
- case "P":
835
- key = { name: "f1", sequence };
836
- break;
837
- case "Q":
838
- key = { name: "f2", sequence };
839
- break;
840
- case "R":
841
- key = { name: "f3", sequence };
842
- break;
843
- case "S":
844
- key = { name: "f4", sequence };
845
- break;
846
- // Keypad (application mode)
847
- case "j":
848
- key = { name: "kpmultiply", sequence };
849
- break;
850
- case "k":
851
- key = { name: "kpplus", sequence };
852
- break;
853
- case "l":
854
- key = { name: "kpcomma", sequence };
855
- break;
856
- case "m":
857
- key = { name: "kpminus", sequence };
858
- break;
859
- case "n":
860
- key = { name: "kpdecimal", sequence };
861
- break;
862
- case "o":
863
- key = { name: "kpdivide", sequence };
864
- break;
865
- case "p":
866
- key = { name: "kp0", sequence };
867
- break;
868
- case "q":
869
- key = { name: "kp1", sequence };
870
- break;
871
- case "r":
872
- key = { name: "kp2", sequence };
873
- break;
874
- case "s":
875
- key = { name: "kp3", sequence };
876
- break;
877
- case "t":
878
- key = { name: "kp4", sequence };
879
- break;
880
- case "u":
881
- key = { name: "kp5", sequence };
882
- break;
883
- case "v":
884
- key = { name: "kp6", sequence };
885
- break;
886
- case "w":
887
- key = { name: "kp7", sequence };
888
- break;
889
- case "x":
890
- key = { name: "kp8", sequence };
891
- break;
892
- case "y":
893
- key = { name: "kp9", sequence };
894
- break;
895
- case "M":
896
- key = { name: "kpenter", sequence };
897
- break;
898
- default:
899
- return null;
900
- }
901
- return { key, end: start + 3 };
902
- }
903
- function parseCsiSequence(data, start) {
904
- let i = start + 2;
905
- let params = "";
906
- while (i < data.length) {
907
- const code = data.charCodeAt(i);
908
- if (code >= 48 && code <= 63) {
909
- params += data[i];
910
- i++;
911
- } else {
912
- break;
913
- }
914
- }
915
- if (i >= data.length) return null;
916
- const final = data[i];
917
- const sequence = data.substring(start, i + 1);
918
- i++;
919
- let key;
920
- switch (final) {
921
- // Arrow keys
922
- case "A":
923
- key = { name: "up", sequence };
924
- break;
925
- case "B":
926
- key = { name: "down", sequence };
927
- break;
928
- case "C":
929
- key = { name: "right", sequence };
930
- break;
931
- case "D":
932
- key = { name: "left", sequence };
933
- break;
934
- // Home/End
935
- case "H":
936
- key = { name: "home", sequence };
937
- break;
938
- case "F":
939
- key = { name: "end", sequence };
940
- break;
941
- // Shift+Tab
942
- case "Z":
943
- key = { name: "tab", sequence, shift: true };
944
- break;
945
- // Function keys (some terminals)
946
- case "P":
947
- key = { name: "f1", sequence };
948
- break;
949
- case "Q":
950
- key = { name: "f2", sequence };
951
- break;
952
- case "R":
953
- key = { name: "f3", sequence };
954
- break;
955
- case "S":
956
- key = { name: "f4", sequence };
957
- break;
958
- // ~ terminated sequences (VT-style)
959
- case "~": {
960
- if (params.startsWith("27;")) {
961
- const modParts = params.split(";");
962
- const mod = parseInt(modParts[1] ?? "1", 10);
963
- const keyCode = parseInt(modParts[2] ?? "0", 10);
964
- key = { name: getKeyNameFromCode(keyCode), sequence };
965
- applyModifiers(key, mod);
966
- break;
967
- }
968
- key = { name: getTildeKeyName(params), sequence };
969
- if (params.includes(";")) {
970
- const parts = params.split(";");
971
- const mod = parseInt(parts[1] ?? "1", 10);
972
- applyModifiers(key, mod);
973
- }
974
- break;
975
- }
976
- // Kitty keyboard protocol: CSI code;mod u
977
- case "u": {
978
- const parts = params.split(";");
979
- const keyCode = parseInt(parts[0] ?? "0", 10);
980
- const mod = parseInt(parts[1] ?? "1", 10);
981
- key = { name: getKeyNameFromCode(keyCode), sequence };
982
- applyModifiers(key, mod);
983
- break;
984
- }
985
- // Focus events (if terminal reports them)
986
- case "I":
987
- key = { name: "focus", sequence };
988
- break;
989
- case "O":
990
- key = { name: "blur", sequence };
991
- break;
992
- default:
993
- key = { name: "unknown", sequence };
994
- }
995
- if (params.includes(";") && !["~", "u"].includes(final)) {
996
- const parts = params.split(";");
997
- const mod = parseInt(parts[parts.length - 1] ?? "1", 10);
998
- if (mod >= 1 && mod <= 16) {
999
- applyModifiers(key, mod);
1000
- }
1001
- }
1002
- return { key, end: i };
1003
- }
1004
-
1005
- // src/paint/color.ts
1006
- var NAMED_FG = {
1007
- black: "\x1B[30m",
1008
- red: "\x1B[31m",
1009
- green: "\x1B[32m",
1010
- yellow: "\x1B[33m",
1011
- blue: "\x1B[34m",
1012
- magenta: "\x1B[35m",
1013
- cyan: "\x1B[36m",
1014
- white: "\x1B[37m",
1015
- blackBright: "\x1B[90m",
1016
- redBright: "\x1B[91m",
1017
- greenBright: "\x1B[92m",
1018
- yellowBright: "\x1B[93m",
1019
- blueBright: "\x1B[94m",
1020
- magentaBright: "\x1B[95m",
1021
- cyanBright: "\x1B[96m",
1022
- whiteBright: "\x1B[97m"
1023
- };
1024
- var NAMED_BG = {
1025
- black: "\x1B[40m",
1026
- red: "\x1B[41m",
1027
- green: "\x1B[42m",
1028
- yellow: "\x1B[43m",
1029
- blue: "\x1B[44m",
1030
- magenta: "\x1B[45m",
1031
- cyan: "\x1B[46m",
1032
- white: "\x1B[47m",
1033
- blackBright: "\x1B[100m",
1034
- redBright: "\x1B[101m",
1035
- greenBright: "\x1B[102m",
1036
- yellowBright: "\x1B[103m",
1037
- blueBright: "\x1B[104m",
1038
- magentaBright: "\x1B[105m",
1039
- cyanBright: "\x1B[106m",
1040
- whiteBright: "\x1B[107m"
1041
- };
1042
- function parseHex(hex) {
1043
- const h = hex.replace("#", "");
1044
- const r = parseInt(h.substring(0, 2), 16);
1045
- const g = parseInt(h.substring(2, 4), 16);
1046
- const b = parseInt(h.substring(4, 6), 16);
1047
- return { r, g, b };
1048
- }
1049
- function colorToFg(color) {
1050
- if (typeof color === "string") {
1051
- if (color.startsWith("#")) {
1052
- const { r: r2, g: g2, b: b2 } = parseHex(color);
1053
- return `\x1B[38;2;${r2};${g2};${b2}m`;
1054
- }
1055
- return NAMED_FG[color] ?? "\x1B[39m";
1056
- }
1057
- if (typeof color === "number") {
1058
- return `\x1B[38;5;${color}m`;
1059
- }
1060
- const { r, g, b } = color;
1061
- return `\x1B[38;2;${r};${g};${b}m`;
1062
- }
1063
- function colorToBg(color) {
1064
- if (typeof color === "string") {
1065
- if (color.startsWith("#")) {
1066
- const { r: r2, g: g2, b: b2 } = parseHex(color);
1067
- return `\x1B[48;2;${r2};${g2};${b2}m`;
1068
- }
1069
- return NAMED_BG[color] ?? "\x1B[49m";
1070
- }
1071
- if (typeof color === "number") {
1072
- return `\x1B[48;5;${color}m`;
1073
- }
1074
- const { r, g, b } = color;
1075
- return `\x1B[48;2;${r};${g};${b}m`;
1076
- }
1077
- var NAMED_RGB = {
1078
- black: [0, 0, 0],
1079
- red: [170, 0, 0],
1080
- green: [0, 170, 0],
1081
- yellow: [170, 170, 0],
1082
- blue: [0, 0, 170],
1083
- magenta: [170, 0, 170],
1084
- cyan: [0, 170, 170],
1085
- white: [170, 170, 170],
1086
- blackBright: [85, 85, 85],
1087
- redBright: [255, 85, 85],
1088
- greenBright: [85, 255, 85],
1089
- yellowBright: [255, 255, 85],
1090
- blueBright: [85, 85, 255],
1091
- magentaBright: [255, 85, 255],
1092
- cyanBright: [85, 255, 255],
1093
- whiteBright: [255, 255, 255]
1094
- };
1095
- var NAMED_INDEX = [
1096
- "black",
1097
- "red",
1098
- "green",
1099
- "yellow",
1100
- "blue",
1101
- "magenta",
1102
- "cyan",
1103
- "white",
1104
- "blackBright",
1105
- "redBright",
1106
- "greenBright",
1107
- "yellowBright",
1108
- "blueBright",
1109
- "magentaBright",
1110
- "cyanBright",
1111
- "whiteBright"
1112
- ];
1113
- var terminalPalette = null;
1114
- function setTerminalPalette(palette) {
1115
- if (palette.size > 0) {
1116
- terminalPalette = palette;
1117
- }
1118
- }
1119
- function resolveNamedRgb(name) {
1120
- if (terminalPalette) {
1121
- const idx = NAMED_INDEX.indexOf(name);
1122
- if (idx !== -1) {
1123
- const tp = terminalPalette.get(idx);
1124
- if (tp) return tp;
1125
- }
1126
- }
1127
- return NAMED_RGB[name] ?? null;
1128
- }
1129
- function colorToRgb(color) {
1130
- if (typeof color === "string") {
1131
- if (color.startsWith("#")) {
1132
- const c = parseHex(color);
1133
- return [c.r, c.g, c.b];
1134
- }
1135
- return resolveNamedRgb(color);
1136
- }
1137
- if (typeof color === "number") {
1138
- if (color < 16) {
1139
- if (terminalPalette) {
1140
- const tp = terminalPalette.get(color);
1141
- if (tp) return tp;
1142
- }
1143
- return NAMED_RGB[NAMED_INDEX[color]];
1144
- }
1145
- if (color >= 232) {
1146
- const g2 = (color - 232) * 10 + 8;
1147
- return [g2, g2, g2];
1148
- }
1149
- const idx = color - 16;
1150
- const b = idx % 6 * 51;
1151
- const g = Math.floor(idx / 6) % 6 * 51;
1152
- const r = Math.floor(idx / 36) * 51;
1153
- return [r, g, b];
1154
- }
1155
- return [color.r, color.g, color.b];
1156
- }
1157
- function isLightColor(color) {
1158
- const rgb = colorToRgb(color);
1159
- if (!rgb) return false;
1160
- const [r, g, b] = rgb.map((c) => {
1161
- const s = c / 255;
1162
- return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
1163
- });
1164
- const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
1165
- return luminance > 0.4;
1166
- }
1167
- function colorsEqual(a, b) {
1168
- if (a === b) return true;
1169
- if (a == null || b == null) return false;
1170
- if (typeof a === "object" && typeof b === "object") {
1171
- return a.r === b.r && a.g === b.g && a.b === b.b;
1172
- }
1173
- return false;
1174
- }
1175
- function getContrastCursorColor(bg) {
1176
- if (!bg) return "white";
1177
- return isLightColor(bg) ? "black" : "white";
1178
- }
1179
-
1180
- // src/paint/framebuffer.ts
1181
- var Framebuffer = class _Framebuffer {
1182
- width;
1183
- height;
1184
- cells;
1185
- constructor(width, height) {
1186
- this.width = width;
1187
- this.height = height;
1188
- this.cells = new Array(width * height);
1189
- this.clear();
1190
- }
1191
- clear() {
1192
- for (let i = 0; i < this.cells.length; i++) {
1193
- this.cells[i] = { ch: " " };
1194
- }
1195
- }
1196
- resize(width, height) {
1197
- this.width = width;
1198
- this.height = height;
1199
- this.cells = new Array(width * height);
1200
- this.clear();
1201
- }
1202
- get(x, y) {
1203
- if (x < 0 || x >= this.width || y < 0 || y >= this.height) return void 0;
1204
- return this.cells[y * this.width + x];
1205
- }
1206
- set(x, y, cell) {
1207
- if (x < 0 || x >= this.width || y < 0 || y >= this.height) return;
1208
- this.cells[y * this.width + x] = cell;
1209
- }
1210
- setChar(x, y, ch, fg, bg, bold, dim, italic, underline) {
1211
- if (x < 0 || x >= this.width || y < 0 || y >= this.height) return;
1212
- this.cells[y * this.width + x] = { ch, fg, bg, bold, dim, italic, underline };
1213
- }
1214
- fillRect(x, y, w, h, ch, fg, bg) {
1215
- for (let row = y; row < y + h; row++) {
1216
- for (let col = x; col < x + w; col++) {
1217
- this.setChar(col, row, ch, fg, bg);
1218
- }
1219
- }
1220
- }
1221
- clone() {
1222
- const fb = new _Framebuffer(this.width, this.height);
1223
- for (let i = 0; i < this.cells.length; i++) {
1224
- const c = this.cells[i];
1225
- fb.cells[i] = { ...c };
1226
- }
1227
- return fb;
1228
- }
1229
- cellsEqual(a, b) {
1230
- return a.ch === b.ch && colorsEqual(a.fg, b.fg) && colorsEqual(a.bg, b.bg) && (a.bold ?? false) === (b.bold ?? false) && (a.dim ?? false) === (b.dim ?? false) && (a.italic ?? false) === (b.italic ?? false) && (a.underline ?? false) === (b.underline ?? false);
1231
- }
1232
- };
1233
-
1234
- // src/paint/borders.ts
1235
- var BORDER_CHARS = {
1236
- single: {
1237
- topLeft: "\u250C",
1238
- topRight: "\u2510",
1239
- bottomLeft: "\u2514",
1240
- bottomRight: "\u2518",
1241
- horizontal: "\u2500",
1242
- vertical: "\u2502"
1243
- },
1244
- double: {
1245
- topLeft: "\u2554",
1246
- topRight: "\u2557",
1247
- bottomLeft: "\u255A",
1248
- bottomRight: "\u255D",
1249
- horizontal: "\u2550",
1250
- vertical: "\u2551"
1251
- },
1252
- round: {
1253
- topLeft: "\u256D",
1254
- topRight: "\u256E",
1255
- bottomLeft: "\u2570",
1256
- bottomRight: "\u256F",
1257
- horizontal: "\u2500",
1258
- vertical: "\u2502"
1259
- },
1260
- ascii: {
1261
- topLeft: "+",
1262
- topRight: "+",
1263
- bottomLeft: "+",
1264
- bottomRight: "+",
1265
- horizontal: "-",
1266
- vertical: "|"
1267
- }
1268
- };
1269
- function getBorderChars(style) {
1270
- if (style === "none") return null;
1271
- return BORDER_CHARS[style];
1272
- }
1273
- function measureText(text, maxWidth, widthMode, wrapMode) {
1274
- if (text.length === 0) {
1275
- return { width: 0, height: 0 };
1276
- }
1277
- const lines = text.split("\n");
1278
- if (widthMode === MeasureMode.Undefined || wrapMode === "none") {
1279
- let maxW2 = 0;
1280
- for (const line of lines) {
1281
- const w = stringWidth(line);
1282
- if (w > maxW2) maxW2 = w;
1283
- }
1284
- return { width: maxW2, height: lines.length };
1285
- }
1286
- const availWidth = Math.max(1, Math.floor(maxWidth));
1287
- const wrappedLines = wrapLines(lines, availWidth, wrapMode);
1288
- let maxW = 0;
1289
- for (const line of wrappedLines) {
1290
- const w = stringWidth(line);
1291
- if (w > maxW) maxW = w;
1292
- }
1293
- return { width: maxW, height: wrappedLines.length };
1294
- }
1295
- function wrapLines(lines, maxWidth, wrapMode) {
1296
- const result = [];
1297
- for (const line of lines) {
1298
- const lineWidth = stringWidth(line);
1299
- if (lineWidth <= maxWidth) {
1300
- result.push(line);
1301
- continue;
1302
- }
1303
- if (wrapMode === "truncate") {
1304
- result.push(truncateLine(line, maxWidth));
1305
- continue;
1306
- }
1307
- if (wrapMode === "ellipsis") {
1308
- result.push(truncateWithEllipsis(line, maxWidth));
1309
- continue;
1310
- }
1311
- const wrapped = wordWrap(line, maxWidth);
1312
- result.push(...wrapped);
1313
- }
1314
- return result;
1315
- }
1316
- function truncateLine(text, maxWidth) {
1317
- let result = "";
1318
- let width = 0;
1319
- for (const char of text) {
1320
- const charWidth = stringWidth(char);
1321
- if (width + charWidth > maxWidth) break;
1322
- result += char;
1323
- width += charWidth;
1324
- }
1325
- return result;
1326
- }
1327
- function truncateWithEllipsis(text, maxWidth) {
1328
- if (maxWidth <= 1) {
1329
- return maxWidth === 1 ? "\u2026" : "";
1330
- }
1331
- const truncated = truncateLine(text, maxWidth - 1);
1332
- if (stringWidth(truncated) < stringWidth(text)) {
1333
- return truncated + "\u2026";
1334
- }
1335
- return text;
1336
- }
1337
- function wordWrap(text, maxWidth) {
1338
- const lines = [];
1339
- let currentLine = "";
1340
- let currentWidth = 0;
1341
- let wordBuffer = "";
1342
- let wordBufferWidth = 0;
1343
- for (let i = 0; i <= text.length; i++) {
1344
- const char = text[i];
1345
- const isEnd = i === text.length;
1346
- const isSpace = char === " ";
1347
- if (isEnd || isSpace) {
1348
- if (wordBuffer.length > 0) {
1349
- if (currentWidth + wordBufferWidth <= maxWidth) {
1350
- currentLine += wordBuffer;
1351
- currentWidth += wordBufferWidth;
1352
- } else if (wordBufferWidth <= maxWidth) {
1353
- if (currentLine.length > 0) {
1354
- lines.push(currentLine);
1355
- }
1356
- currentLine = wordBuffer;
1357
- currentWidth = wordBufferWidth;
1358
- } else {
1359
- for (const c of wordBuffer) {
1360
- const cw = stringWidth(c);
1361
- if (currentWidth + cw > maxWidth && currentLine.length > 0) {
1362
- lines.push(currentLine);
1363
- currentLine = "";
1364
- currentWidth = 0;
1365
- }
1366
- currentLine += c;
1367
- currentWidth += cw;
1368
- }
1369
- }
1370
- wordBuffer = "";
1371
- wordBufferWidth = 0;
1372
- }
1373
- if (isSpace) {
1374
- if (currentWidth + 1 <= maxWidth) {
1375
- currentLine += " ";
1376
- currentWidth += 1;
1377
- } else {
1378
- if (currentLine.length > 0) {
1379
- lines.push(currentLine);
1380
- }
1381
- currentLine = " ";
1382
- currentWidth = 1;
1383
- }
1384
- }
1385
- } else {
1386
- wordBuffer += char;
1387
- wordBufferWidth += stringWidth(char);
1388
- }
1389
- }
1390
- if (currentLine.length > 0) {
1391
- lines.push(currentLine);
1392
- }
1393
- return lines.length > 0 ? lines : [""];
1394
- }
1395
- function paintTree(roots, fb, options = {}) {
1396
- fb.clear();
1397
- const result = {};
1398
- const entries = [];
1399
- const screenClip = { x: 0, y: 0, width: fb.width, height: fb.height };
1400
- for (const root of roots) {
1401
- if (root.hidden) continue;
1402
- collectPaintEntries(root, screenClip, root.style.zIndex ?? 0, entries);
1403
- }
1404
- entries.sort((a, b) => a.zIndex - b.zIndex);
1405
- for (const entry of entries) {
1406
- const nodeResult = paintNode(entry.node, fb, entry.clip, options);
1407
- if (nodeResult?.cursorPosition) {
1408
- result.cursorPosition = nodeResult.cursorPosition;
1409
- }
1410
- }
1411
- return result;
1412
- }
1413
- function collectPaintEntries(node, parentClip, parentZ, entries) {
1414
- if (node.hidden) return;
1415
- const zIndex = node.style.zIndex ?? parentZ;
1416
- const clip = node.style.clip ? intersectClip(parentClip, {
1417
- x: node.layout.innerX,
1418
- y: node.layout.innerY,
1419
- width: node.layout.innerWidth,
1420
- height: node.layout.innerHeight
1421
- }) : parentClip;
1422
- entries.push({ node, clip: parentClip, zIndex });
1423
- if (node.type !== "text" && node.type !== "input") {
1424
- for (const child of node.children) {
1425
- collectPaintEntries(child, clip, zIndex, entries);
1426
- }
1427
- }
1428
- }
1429
- function intersectClip(a, b) {
1430
- const x = Math.max(a.x, b.x);
1431
- const y = Math.max(a.y, b.y);
1432
- const right = Math.min(a.x + a.width, b.x + b.width);
1433
- const bottom = Math.min(a.y + a.height, b.y + b.height);
1434
- return {
1435
- x,
1436
- y,
1437
- width: Math.max(0, right - x),
1438
- height: Math.max(0, bottom - y)
1439
- };
1440
- }
1441
- function isInClip(x, y, clip) {
1442
- return x >= clip.x && x < clip.x + clip.width && y >= clip.y && y < clip.y + clip.height;
1443
- }
1444
- function paintNode(node, fb, clip, options = {}) {
1445
- const { x, y, width, height, innerX, innerY, innerWidth, innerHeight } = node.layout;
1446
- const style = node.style;
1447
- if (width <= 0 || height <= 0) return;
1448
- const inherited = getInheritedTextStyle(node);
1449
- const effectiveBg = inherited.bg;
1450
- if (style.bg) {
1451
- for (let row = y; row < y + height; row++) {
1452
- for (let col = x; col < x + width; col++) {
1453
- if (isInClip(col, row, clip)) {
1454
- fb.setChar(col, row, " ", void 0, style.bg);
1455
- }
1456
- }
1457
- }
1458
- }
1459
- const borderChars = style.border ? getBorderChars(style.border) : null;
1460
- if (borderChars && width >= 2 && height >= 2) {
1461
- const bc = style.borderColor;
1462
- const bg = effectiveBg;
1463
- setClipped(fb, clip, x, y, borderChars.topLeft, bc, bg);
1464
- for (let col = x + 1; col < x + width - 1; col++) {
1465
- setClipped(fb, clip, col, y, borderChars.horizontal, bc, bg);
1466
- }
1467
- setClipped(fb, clip, x + width - 1, y, borderChars.topRight, bc, bg);
1468
- setClipped(fb, clip, x, y + height - 1, borderChars.bottomLeft, bc, bg);
1469
- for (let col = x + 1; col < x + width - 1; col++) {
1470
- setClipped(fb, clip, col, y + height - 1, borderChars.horizontal, bc, bg);
1471
- }
1472
- setClipped(fb, clip, x + width - 1, y + height - 1, borderChars.bottomRight, bc, bg);
1473
- for (let row = y + 1; row < y + height - 1; row++) {
1474
- setClipped(fb, clip, x, row, borderChars.vertical, bc, bg);
1475
- setClipped(fb, clip, x + width - 1, row, borderChars.vertical, bc, bg);
1476
- }
1477
- }
1478
- if (node.type === "text") {
1479
- paintText(node, fb, clip);
1480
- } else if (node.type === "input") {
1481
- return paintInput(node, fb, clip, options);
1482
- }
1483
- return void 0;
1484
- }
1485
- function setClipped(fb, clip, x, y, ch, fg, bg, bold, dim, italic, underline) {
1486
- if (isInClip(x, y, clip)) {
1487
- fb.setChar(x, y, ch, fg, bg, bold, dim, italic, underline);
1488
- }
1489
- }
1490
- function autoContrastFg(explicitColor, bg) {
1491
- if (explicitColor !== void 0) return explicitColor;
1492
- if (bg === void 0) return void 0;
1493
- return isLightColor(bg) ? "black" : "white";
1494
- }
1495
- function paintText(node, fb, clip) {
1496
- const { innerX, innerY, innerWidth, innerHeight } = node.layout;
1497
- const inherited = getInheritedTextStyle(node);
1498
- const text = collectTextContent(node);
1499
- if (!text) return;
1500
- const fg = autoContrastFg(inherited.color, inherited.bg);
1501
- const wrapMode = node.style.wrap ?? "wrap";
1502
- const textAlign = node.style.textAlign ?? "left";
1503
- const rawLines = text.split("\n");
1504
- const lines = wrapLines(rawLines, innerWidth, wrapMode);
1505
- for (let lineIdx = 0; lineIdx < lines.length && lineIdx < innerHeight; lineIdx++) {
1506
- const line = lines[lineIdx];
1507
- const lineWidth = stringWidth(line);
1508
- let offsetX = 0;
1509
- if (textAlign === "center") {
1510
- offsetX = Math.max(0, Math.floor((innerWidth - lineWidth) / 2));
1511
- } else if (textAlign === "right") {
1512
- offsetX = Math.max(0, innerWidth - lineWidth);
1513
- }
1514
- let col = 0;
1515
- for (const char of line) {
1516
- const charWidth = stringWidth(char);
1517
- if (charWidth > 0) {
1518
- setClipped(
1519
- fb,
1520
- clip,
1521
- innerX + offsetX + col,
1522
- innerY + lineIdx,
1523
- char,
1524
- fg,
1525
- inherited.bg,
1526
- inherited.bold,
1527
- inherited.dim,
1528
- inherited.italic,
1529
- inherited.underline
1530
- );
1531
- }
1532
- col += charWidth;
1533
- }
1534
- }
1535
- }
1536
- function paintInput(node, fb, clip, options = {}) {
1537
- const { cursorInfo, useNativeCursor } = options;
1538
- const { innerX, innerY, innerWidth, innerHeight } = node.layout;
1539
- if (innerWidth <= 0 || innerHeight <= 0) return;
1540
- const value = node.props.value ?? node.props.defaultValue ?? "";
1541
- const placeholder = node.props.placeholder ?? "";
1542
- const displayText = value || placeholder;
1543
- const isPlaceholder = !value && !!placeholder;
1544
- const multiline = node.props.multiline ?? false;
1545
- const inherited = getInheritedTextStyle(node);
1546
- const autoFg = autoContrastFg(inherited.color, inherited.bg);
1547
- const placeholderFg = inherited.bg ? isLightColor(inherited.bg) ? "blackBright" : "whiteBright" : "blackBright";
1548
- const fg = isPlaceholder ? placeholderFg : autoFg ?? inherited.color ?? node.style.color;
1549
- const textFg = isPlaceholder ? placeholderFg : fg;
1550
- const textDim = isPlaceholder ? true : inherited.dim;
1551
- const isFocused = cursorInfo && cursorInfo.nodeId === node.focusId;
1552
- let result;
1553
- if (multiline && !isPlaceholder) {
1554
- const wrapMode = node.style.wrap ?? "wrap";
1555
- const rawLines = displayText.split("\n");
1556
- const wrappedLines = wrapLines(rawLines, innerWidth, wrapMode);
1557
- let cursorScreenLine = 0;
1558
- let cursorScreenCol = 0;
1559
- if (isFocused) {
1560
- const pos = cursorInfo.position;
1561
- let logicalLine = 0;
1562
- let offsetInLogicalLine = pos;
1563
- let runningPos = 0;
1564
- for (let i = 0; i < rawLines.length; i++) {
1565
- const lineLen = rawLines[i].length;
1566
- if (pos <= runningPos + lineLen) {
1567
- logicalLine = i;
1568
- offsetInLogicalLine = pos - runningPos;
1569
- break;
1570
- }
1571
- runningPos += lineLen + 1;
1572
- }
1573
- let wrappedLinesBefore = 0;
1574
- for (let i = 0; i < logicalLine; i++) {
1575
- wrappedLinesBefore += wrapLines([rawLines[i]], innerWidth, wrapMode).length;
1576
- }
1577
- const wrappedCurrentLine = wrapLines([rawLines[logicalLine]], innerWidth, wrapMode);
1578
- let charsProcessed = 0;
1579
- let subLineIdx = 0;
1580
- for (let i = 0; i < wrappedCurrentLine.length; i++) {
1581
- const subLine = wrappedCurrentLine[i];
1582
- if (offsetInLogicalLine <= charsProcessed + subLine.length) {
1583
- subLineIdx = i;
1584
- break;
1585
- }
1586
- charsProcessed += subLine.length;
1587
- }
1588
- cursorScreenLine = wrappedLinesBefore + subLineIdx;
1589
- cursorScreenCol = stringWidth(rawLines[logicalLine].slice(charsProcessed, charsProcessed + (offsetInLogicalLine - charsProcessed)));
1590
- }
1591
- const scrollOffset = Math.max(0, cursorScreenLine - innerHeight + 1);
1592
- for (let rowIdx = 0; rowIdx < innerHeight; rowIdx++) {
1593
- const lineNum = scrollOffset + rowIdx;
1594
- if (lineNum >= wrappedLines.length) break;
1595
- const line = wrappedLines[lineNum];
1596
- let col = 0;
1597
- for (const char of line) {
1598
- if (col >= innerWidth) break;
1599
- const charWidth = stringWidth(char);
1600
- if (charWidth > 0) {
1601
- setClipped(
1602
- fb,
1603
- clip,
1604
- innerX + col,
1605
- innerY + rowIdx,
1606
- char,
1607
- textFg,
1608
- inherited.bg,
1609
- inherited.bold,
1610
- textDim,
1611
- inherited.italic,
1612
- inherited.underline
1613
- );
1614
- }
1615
- col += charWidth;
1616
- }
1617
- }
1618
- if (isFocused) {
1619
- const screenRow = cursorScreenLine - scrollOffset;
1620
- if (screenRow >= 0 && screenRow < innerHeight) {
1621
- const cCol = Math.min(cursorScreenCol, innerWidth - 1);
1622
- const cursorX = innerX + cCol;
1623
- const cursorY = innerY + screenRow;
1624
- if (isInClip(cursorX, cursorY, clip) && cursorX < innerX + innerWidth) {
1625
- if (useNativeCursor) {
1626
- result = { cursorPosition: { x: cursorX, y: cursorY, bg: inherited.bg } };
1627
- } else {
1628
- const existing = fb.get(cursorX, cursorY);
1629
- const cursorChar = existing?.ch && existing.ch !== " " ? existing.ch : "\u258C";
1630
- const cursorFg = inherited.bg ?? "black";
1631
- const cursorBg = inherited.color ?? "white";
1632
- fb.setChar(
1633
- cursorX,
1634
- cursorY,
1635
- cursorChar,
1636
- cursorFg,
1637
- cursorBg,
1638
- existing?.bold,
1639
- existing?.dim,
1640
- existing?.italic,
1641
- false
1642
- );
1643
- }
1644
- }
1645
- }
1646
- }
1647
- } else {
1648
- let col = 0;
1649
- for (const char of displayText) {
1650
- if (col >= innerWidth) break;
1651
- const charWidth = stringWidth(char);
1652
- if (charWidth > 0) {
1653
- setClipped(
1654
- fb,
1655
- clip,
1656
- innerX + col,
1657
- innerY,
1658
- char,
1659
- textFg,
1660
- inherited.bg,
1661
- inherited.bold,
1662
- textDim,
1663
- inherited.italic,
1664
- inherited.underline
1665
- );
1666
- }
1667
- col += charWidth;
1668
- }
1669
- if (isFocused) {
1670
- const cursorCol = Math.min(cursorInfo.position, innerWidth - 1);
1671
- const cursorX = innerX + cursorCol;
1672
- if (isInClip(cursorX, innerY, clip) && cursorX < innerX + innerWidth) {
1673
- if (useNativeCursor) {
1674
- result = { cursorPosition: { x: cursorX, y: innerY, bg: inherited.bg } };
1675
- } else {
1676
- const existing = fb.get(cursorX, innerY);
1677
- const cursorChar = existing?.ch && existing.ch !== " " ? existing.ch : "\u258C";
1678
- const cursorFg = inherited.bg ?? "black";
1679
- const cursorBg = inherited.color ?? "white";
1680
- fb.setChar(
1681
- cursorX,
1682
- innerY,
1683
- cursorChar,
1684
- cursorFg,
1685
- cursorBg,
1686
- existing?.bold,
1687
- existing?.dim,
1688
- existing?.italic,
1689
- false
1690
- );
1691
- }
1692
- }
1693
- }
1694
- }
1695
- return result;
1696
- }
1697
-
1698
- // src/paint/diff.ts
1699
- var ESC2 = "\x1B";
1700
- var CSI2 = `${ESC2}[`;
1701
- function moveCursor(x, y) {
1702
- return `${CSI2}${y + 1};${x + 1}H`;
1703
- }
1704
- function buildSGR(cell) {
1705
- let seq = `${CSI2}0m`;
1706
- if (cell.bold) seq += `${CSI2}1m`;
1707
- if (cell.dim) seq += `${CSI2}2m`;
1708
- if (cell.italic) seq += `${CSI2}3m`;
1709
- if (cell.underline) seq += `${CSI2}4m`;
1710
- if (cell.fg != null) seq += colorToFg(cell.fg);
1711
- if (cell.bg != null) seq += colorToBg(cell.bg);
1712
- return seq;
1713
- }
1714
- function diffFramebuffers(prev, next, fullRedraw) {
1715
- let out = "";
1716
- let lastX = -1;
1717
- let lastY = -1;
1718
- let lastSGR = "";
1719
- for (let y = 0; y < next.height; y++) {
1720
- for (let x = 0; x < next.width; x++) {
1721
- const nc = next.get(x, y);
1722
- if (!fullRedraw) {
1723
- const pc = prev.get(x, y);
1724
- if (pc && next.cellsEqual(nc, pc)) continue;
1725
- }
1726
- if (lastY !== y || lastX !== x) {
1727
- out += moveCursor(x, y);
1728
- }
1729
- const sgr = buildSGR(nc);
1730
- if (sgr !== lastSGR) {
1731
- out += sgr;
1732
- lastSGR = sgr;
1733
- }
1734
- out += nc.ch;
1735
- lastX = x + 1;
1736
- lastY = y;
1737
- }
1738
- }
1739
- if (out.length > 0) {
1740
- out += `${CSI2}0m`;
1741
- }
1742
- return out;
1743
- }
1744
- var FLEX_DIR_MAP = {
1745
- row: FlexDirection.Row,
1746
- column: FlexDirection.Column
1747
- };
1748
- var JUSTIFY_MAP = {
1749
- "flex-start": Justify.FlexStart,
1750
- center: Justify.Center,
1751
- "flex-end": Justify.FlexEnd,
1752
- "space-between": Justify.SpaceBetween,
1753
- "space-around": Justify.SpaceAround
1754
- };
1755
- var ALIGN_MAP = {
1756
- "flex-start": Align.FlexStart,
1757
- center: Align.Center,
1758
- "flex-end": Align.FlexEnd,
1759
- stretch: Align.Stretch
1760
- };
1761
- function setDimension(node, setter, value) {
1762
- if (value === void 0) return;
1763
- if (typeof value === "string" && value.endsWith("%")) {
1764
- setter(value);
1765
- } else {
1766
- setter(value);
1767
- }
1768
- }
1769
- function setPosition(node, edge, value) {
1770
- if (value === void 0) return;
1771
- if (typeof value === "string" && value.endsWith("%")) {
1772
- node.setPositionPercent(edge, parseFloat(value));
1773
- } else {
1774
- node.setPosition(edge, value);
1775
- }
1776
- }
1777
- function applyStyleToYogaNode(yogaNode, style, nodeType) {
1778
- setDimension(yogaNode, (v) => yogaNode.setWidth(v), style.width);
1779
- setDimension(yogaNode, (v) => yogaNode.setHeight(v), style.height);
1780
- if (style.minWidth !== void 0) yogaNode.setMinWidth(style.minWidth);
1781
- if (style.minHeight !== void 0) yogaNode.setMinHeight(style.minHeight);
1782
- if (style.maxWidth !== void 0) yogaNode.setMaxWidth(style.maxWidth);
1783
- if (style.maxHeight !== void 0) yogaNode.setMaxHeight(style.maxHeight);
1784
- if (style.padding !== void 0) yogaNode.setPadding(Edge.All, style.padding);
1785
- if (style.paddingX !== void 0) yogaNode.setPadding(Edge.Horizontal, style.paddingX);
1786
- if (style.paddingY !== void 0) yogaNode.setPadding(Edge.Vertical, style.paddingY);
1787
- if (style.paddingTop !== void 0) yogaNode.setPadding(Edge.Top, style.paddingTop);
1788
- if (style.paddingRight !== void 0) yogaNode.setPadding(Edge.Right, style.paddingRight);
1789
- if (style.paddingBottom !== void 0) yogaNode.setPadding(Edge.Bottom, style.paddingBottom);
1790
- if (style.paddingLeft !== void 0) yogaNode.setPadding(Edge.Left, style.paddingLeft);
1791
- const hasBorder = style.border != null && style.border !== "none";
1792
- yogaNode.setBorder(Edge.All, hasBorder ? 1 : 0);
1793
- if (style.flexDirection) {
1794
- yogaNode.setFlexDirection(FLEX_DIR_MAP[style.flexDirection] ?? FlexDirection.Column);
1795
- }
1796
- if (style.flexWrap) {
1797
- yogaNode.setFlexWrap(style.flexWrap === "wrap" ? Wrap.Wrap : Wrap.NoWrap);
1798
- }
1799
- if (style.justifyContent) {
1800
- yogaNode.setJustifyContent(JUSTIFY_MAP[style.justifyContent] ?? Justify.FlexStart);
1801
- }
1802
- if (style.alignItems) {
1803
- yogaNode.setAlignItems(ALIGN_MAP[style.alignItems] ?? Align.Stretch);
1804
- }
1805
- if (style.flexGrow !== void 0) yogaNode.setFlexGrow(style.flexGrow);
1806
- if (style.flexShrink !== void 0) yogaNode.setFlexShrink(style.flexShrink);
1807
- if (style.gap !== void 0) yogaNode.setGap(Gutter.All, style.gap);
1808
- if (style.position === "absolute") {
1809
- yogaNode.setPositionType(PositionType.Absolute);
1810
- } else {
1811
- yogaNode.setPositionType(PositionType.Relative);
1812
- }
1813
- if (style.inset !== void 0) {
1814
- setPosition(yogaNode, Edge.Top, style.inset);
1815
- setPosition(yogaNode, Edge.Right, style.inset);
1816
- setPosition(yogaNode, Edge.Bottom, style.inset);
1817
- setPosition(yogaNode, Edge.Left, style.inset);
1818
- }
1819
- setPosition(yogaNode, Edge.Top, style.top);
1820
- setPosition(yogaNode, Edge.Right, style.right);
1821
- setPosition(yogaNode, Edge.Bottom, style.bottom);
1822
- setPosition(yogaNode, Edge.Left, style.left);
1823
- if (style.clip) {
1824
- yogaNode.setOverflow(Overflow.Hidden);
1825
- }
1826
- }
1827
- function buildYogaTree(node) {
1828
- const yogaNode = Yoga.Node.create();
1829
- node.yogaNode = yogaNode;
1830
- applyStyleToYogaNode(yogaNode, node.style, node.type);
1831
- if (node.type === "text" || node.type === "input") {
1832
- yogaNode.setMeasureFunc((width, widthMode, height, heightMode) => {
1833
- let text;
1834
- if (node.type === "input") {
1835
- text = node.props.value ?? node.props.defaultValue ?? node.props.placeholder ?? "";
1836
- if (text.length === 0) text = " ";
1837
- } else {
1838
- text = collectAllText(node);
1839
- }
1840
- return measureText(
1841
- text,
1842
- width,
1843
- widthMode,
1844
- node.style.wrap ?? "wrap"
1845
- );
1846
- });
1847
- } else {
1848
- for (let i = 0; i < node.children.length; i++) {
1849
- const child = node.children[i];
1850
- if (child.hidden) continue;
1851
- buildYogaTree(child);
1852
- yogaNode.insertChild(child.yogaNode, yogaNode.getChildCount());
1853
- }
1854
- }
1855
- }
1856
- function collectAllText(node) {
1857
- if (node.text != null) return node.text;
1858
- let result = "";
1859
- for (const child of node.children) {
1860
- result += collectAllText(child);
1861
- }
1862
- if (result === "" && node.props.children != null) {
1863
- if (typeof node.props.children === "string") return node.props.children;
1864
- if (typeof node.props.children === "number") return String(node.props.children);
1865
- }
1866
- return result;
1867
- }
1868
- function extractLayout(node, parentX, parentY) {
1869
- const yn = node.yogaNode;
1870
- const computedLayout = yn.getComputedLayout();
1871
- const x = parentX + computedLayout.left;
1872
- const y = parentY + computedLayout.top;
1873
- const width = computedLayout.width;
1874
- const height = computedLayout.height;
1875
- const borderWidth = node.style.border && node.style.border !== "none" ? 1 : 0;
1876
- const paddingTop = yn.getComputedPadding(Edge.Top);
1877
- const paddingRight = yn.getComputedPadding(Edge.Right);
1878
- const paddingBottom = yn.getComputedPadding(Edge.Bottom);
1879
- const paddingLeft = yn.getComputedPadding(Edge.Left);
1880
- const innerX = x + borderWidth + paddingLeft;
1881
- const innerY = y + borderWidth + paddingTop;
1882
- const innerWidth = Math.max(0, width - borderWidth * 2 - paddingLeft - paddingRight);
1883
- const innerHeight = Math.max(0, height - borderWidth * 2 - paddingTop - paddingBottom);
1884
- node.layout = { x, y, width, height, innerX, innerY, innerWidth, innerHeight };
1885
- for (const child of node.children) {
1886
- if (child.hidden || !child.yogaNode) continue;
1887
- extractLayout(child, x, y);
1888
- }
1889
- }
1890
- function computeLayout(roots, screenWidth, screenHeight) {
1891
- const rootYoga = Yoga.Node.create();
1892
- rootYoga.setWidth(screenWidth);
1893
- rootYoga.setHeight(screenHeight);
1894
- rootYoga.setFlexDirection(FlexDirection.Column);
1895
- for (const child of roots) {
1896
- if (child.hidden) continue;
1897
- buildYogaTree(child);
1898
- rootYoga.insertChild(child.yogaNode, rootYoga.getChildCount());
1899
- }
1900
- rootYoga.calculateLayout(screenWidth, screenHeight, Direction.LTR);
1901
- for (const child of roots) {
1902
- if (child.hidden || !child.yogaNode) continue;
1903
- extractLayout(child, 0, 0);
1904
- }
1905
- rootYoga.freeRecursive();
1906
- clearYogaRefs(roots);
1907
- }
1908
- function clearYogaRefs(nodes) {
1909
- for (const node of nodes) {
1910
- node.yogaNode = null;
1911
- clearYogaRefs(node.children);
1912
- }
1913
- }
1914
- var InputContext = createContext(null);
1915
- var FocusContext = createContext(null);
1916
- var LayoutContext = createContext(null);
1917
- var AppContext = createContext(null);
1918
-
1919
- // src/render.ts
1920
- function render(element, opts = {}) {
1921
- const stdout = opts.stdout ?? process.stdout;
1922
- const stdin = opts.stdin ?? process.stdin;
1923
- const debug = opts.debug ?? false;
1924
- const useNativeCursor = opts.useNativeCursor ?? true;
1925
- const terminal = new Terminal(stdout, stdin);
1926
- terminal.setup();
1927
- let nativeCursorVisible = false;
1928
- terminal.queryPalette().then((palette) => {
1929
- setTerminalPalette(palette);
1930
- fullRedraw = true;
1931
- scheduleRender();
1932
- });
1933
- const prevFb = new Framebuffer(terminal.columns, terminal.rows);
1934
- const currentFb = new Framebuffer(terminal.columns, terminal.rows);
1935
- let fullRedraw = true;
1936
- const inputHandlers = /* @__PURE__ */ new Set();
1937
- const priorityHandlers = /* @__PURE__ */ new Set();
1938
- const focusedInputHandlers = /* @__PURE__ */ new Map();
1939
- const inputContextValue = {
1940
- subscribe(handler) {
1941
- inputHandlers.add(handler);
1942
- return () => inputHandlers.delete(handler);
1943
- },
1944
- subscribePriority(handler) {
1945
- priorityHandlers.add(handler);
1946
- return () => priorityHandlers.delete(handler);
1947
- },
1948
- registerInputHandler(focusId, handler) {
1949
- focusedInputHandlers.set(focusId, handler);
1950
- return () => focusedInputHandlers.delete(focusId);
1951
- }
1952
- };
1953
- let focusedId = null;
1954
- const focusRegistry = /* @__PURE__ */ new Map();
1955
- const focusOrder = [];
1956
- const skippableIds = /* @__PURE__ */ new Set();
1957
- let trapStack = [];
1958
- const focusChangeHandlers = /* @__PURE__ */ new Set();
1959
- function setFocusedId(id) {
1960
- if (focusedId !== id) {
1961
- focusedId = id;
1962
- scheduleRender();
1963
- for (const handler of focusChangeHandlers) {
1964
- handler(focusedId);
1965
- }
1966
- }
1967
- }
1968
- function getActiveFocusableIds() {
1969
- let ids = [...focusOrder];
1970
- if (trapStack.length > 0) {
1971
- const trap = trapStack[trapStack.length - 1];
1972
- ids = ids.filter((id) => trap.has(id));
1973
- }
1974
- ids = ids.filter((id) => !skippableIds.has(id));
1975
- ids.sort((a, b) => {
1976
- const nodeA = focusRegistry.get(a);
1977
- const nodeB = focusRegistry.get(b);
1978
- if (!nodeA || !nodeB) return 0;
1979
- const layoutA = nodeA.layout;
1980
- const layoutB = nodeB.layout;
1981
- if (layoutA.y !== layoutB.y) {
1982
- return layoutA.y - layoutB.y;
1983
- }
1984
- return layoutA.x - layoutB.x;
1985
- });
1986
- return ids;
1987
- }
1988
- const focusContextValue = {
1989
- get focusedId() {
1990
- return focusedId;
1991
- },
1992
- register(id, node) {
1993
- focusRegistry.set(id, node);
1994
- if (!focusOrder.includes(id)) {
1995
- focusOrder.push(id);
1996
- }
1997
- if (trapStack.length > 0) {
1998
- trapStack[trapStack.length - 1].add(id);
1999
- }
2000
- if (focusedId === null) {
2001
- const activeIds = getActiveFocusableIds();
2002
- if (activeIds.length > 0) {
2003
- setFocusedId(activeIds[0]);
2004
- }
2005
- }
2006
- return () => {
2007
- focusRegistry.delete(id);
2008
- const idx = focusOrder.indexOf(id);
2009
- if (idx !== -1) focusOrder.splice(idx, 1);
2010
- if (focusedId === id) {
2011
- const activeIds = getActiveFocusableIds();
2012
- setFocusedId(activeIds[0] ?? null);
2013
- }
2014
- };
2015
- },
2016
- requestFocus(id) {
2017
- setFocusedId(id);
2018
- },
2019
- focusNext() {
2020
- const ids = getActiveFocusableIds();
2021
- if (ids.length === 0) return;
2022
- const currentIdx = focusedId ? ids.indexOf(focusedId) : -1;
2023
- const nextIdx = (currentIdx + 1) % ids.length;
2024
- setFocusedId(ids[nextIdx]);
2025
- },
2026
- focusPrev() {
2027
- const ids = getActiveFocusableIds();
2028
- if (ids.length === 0) return;
2029
- const currentIdx = focusedId ? ids.indexOf(focusedId) : 0;
2030
- const prevIdx = (currentIdx - 1 + ids.length) % ids.length;
2031
- setFocusedId(ids[prevIdx]);
2032
- },
2033
- setSkippable(id, skippable) {
2034
- if (skippable) {
2035
- skippableIds.add(id);
2036
- if (focusedId === id) {
2037
- const ids = getActiveFocusableIds();
2038
- if (ids.length > 0) {
2039
- setFocusedId(ids[0]);
2040
- }
2041
- }
2042
- } else {
2043
- skippableIds.delete(id);
2044
- }
2045
- },
2046
- trapIds: null,
2047
- pushTrap(ids) {
2048
- trapStack.push(ids);
2049
- return () => {
2050
- const idx = trapStack.indexOf(ids);
2051
- if (idx !== -1) trapStack.splice(idx, 1);
2052
- };
2053
- },
2054
- onFocusChange(handler) {
2055
- focusChangeHandlers.add(handler);
2056
- return () => {
2057
- focusChangeHandlers.delete(handler);
2058
- };
2059
- },
2060
- getRegisteredElements() {
2061
- const result = [];
2062
- for (const id of focusOrder) {
2063
- if (skippableIds.has(id)) continue;
2064
- const node = focusRegistry.get(id);
2065
- if (node) {
2066
- result.push({ id, node });
2067
- }
2068
- }
2069
- return result;
2070
- },
2071
- getActiveElements() {
2072
- const activeIds = getActiveFocusableIds();
2073
- const result = [];
2074
- for (const id of activeIds) {
2075
- if (skippableIds.has(id)) continue;
2076
- const node = focusRegistry.get(id);
2077
- if (node) {
2078
- result.push({ id, node });
2079
- }
2080
- }
2081
- return result;
2082
- }
2083
- };
2084
- const layoutSubscriptions = /* @__PURE__ */ new Map();
2085
- const layoutContextValue = {
2086
- getLayout(node) {
2087
- return node.layout;
2088
- },
2089
- subscribe(node, handler) {
2090
- if (!layoutSubscriptions.has(node)) {
2091
- layoutSubscriptions.set(node, /* @__PURE__ */ new Set());
2092
- }
2093
- layoutSubscriptions.get(node).add(handler);
2094
- return () => {
2095
- const subs = layoutSubscriptions.get(node);
2096
- if (subs) {
2097
- subs.delete(handler);
2098
- if (subs.size === 0) layoutSubscriptions.delete(node);
2099
- }
2100
- };
2101
- }
2102
- };
2103
- const appContextValue = {
2104
- registerNode() {
2105
- },
2106
- unregisterNode() {
2107
- },
2108
- scheduleRender,
2109
- exit(code) {
2110
- handle.exit(code);
2111
- },
2112
- get columns() {
2113
- return terminal.columns;
2114
- },
2115
- get rows() {
2116
- return terminal.rows;
2117
- }
2118
- };
2119
- const container = {
2120
- type: "root",
2121
- children: [],
2122
- onCommit() {
2123
- scheduleRender();
2124
- }
2125
- };
2126
- let renderScheduled = false;
2127
- function scheduleRender() {
2128
- if (renderScheduled) return;
2129
- renderScheduled = true;
2130
- queueMicrotask(() => {
2131
- renderScheduled = false;
2132
- performRender();
2133
- });
2134
- }
2135
- function performRender() {
2136
- const cols = terminal.columns;
2137
- const rows = terminal.rows;
2138
- if (currentFb.width !== cols || currentFb.height !== rows) {
2139
- currentFb.resize(cols, rows);
2140
- prevFb.resize(cols, rows);
2141
- fullRedraw = true;
2142
- }
2143
- computeLayout(container.children, cols, rows);
2144
- notifyLayoutSubscribers(container.children);
2145
- let cursorInfo;
2146
- if (focusedId) {
2147
- const focusedNode = focusRegistry.get(focusedId);
2148
- if (focusedNode?.type === "input") {
2149
- cursorInfo = {
2150
- nodeId: focusedId,
2151
- position: focusedNode.props.cursorPosition ?? (focusedNode.props.value?.length ?? 0)
2152
- };
2153
- }
2154
- }
2155
- const paintResult = paintTree(container.children, currentFb, {
2156
- cursorInfo,
2157
- useNativeCursor
2158
- });
2159
- const output = diffFramebuffers(prevFb, currentFb, fullRedraw);
2160
- if (output.length > 0) {
2161
- terminal.write(output);
2162
- }
2163
- if (useNativeCursor) {
2164
- if (paintResult.cursorPosition) {
2165
- const cursorColor = getContrastCursorColor(paintResult.cursorPosition.bg);
2166
- terminal.setCursorColor(cursorColor);
2167
- terminal.moveCursor(paintResult.cursorPosition.x, paintResult.cursorPosition.y);
2168
- if (!nativeCursorVisible) {
2169
- terminal.showCursor();
2170
- nativeCursorVisible = true;
2171
- }
2172
- } else {
2173
- if (nativeCursorVisible) {
2174
- terminal.hideCursor();
2175
- nativeCursorVisible = false;
2176
- }
2177
- }
2178
- }
2179
- for (let i = 0; i < currentFb.cells.length; i++) {
2180
- prevFb.cells[i] = { ...currentFb.cells[i] };
2181
- }
2182
- fullRedraw = false;
2183
- }
2184
- function notifyLayoutSubscribers(nodes) {
2185
- for (const node of nodes) {
2186
- const subs = layoutSubscriptions.get(node);
2187
- if (subs) {
2188
- for (const handler of subs) {
2189
- handler(node.layout);
2190
- }
2191
- }
2192
- notifyLayoutSubscribers(node.children);
2193
- }
2194
- }
2195
- const removeDataListener = terminal.onData((data) => {
2196
- const keys = parseKeySequence(data);
2197
- for (const key of keys) {
2198
- if (key.ctrl && key.name === "c") {
2199
- handle.exit();
2200
- return;
2201
- }
2202
- if (key.ctrl && key.name === "z") {
2203
- terminal.suspend();
2204
- process.kill(0, "SIGSTOP");
2205
- return;
2206
- }
2207
- if (key.name === "tab" && !key.ctrl && !key.alt) {
2208
- if (key.shift) {
2209
- focusContextValue.focusPrev();
2210
- } else {
2211
- focusContextValue.focusNext();
2212
- }
2213
- continue;
2214
- }
2215
- let consumed = false;
2216
- for (const handler of priorityHandlers) {
2217
- if (handler(key)) {
2218
- consumed = true;
2219
- break;
2220
- }
2221
- }
2222
- if (!consumed && focusedId) {
2223
- const inputHandler = focusedInputHandlers.get(focusedId);
2224
- if (inputHandler) {
2225
- consumed = inputHandler(key);
2226
- }
2227
- }
2228
- if (!consumed) {
2229
- for (const handler of inputHandlers) {
2230
- handler(key);
2231
- }
2232
- }
2233
- }
2234
- });
2235
- const removeResizeListener = terminal.onResize(() => {
2236
- fullRedraw = true;
2237
- scheduleRender();
2238
- });
2239
- const handleSigcont = () => {
2240
- terminal.resume();
2241
- fullRedraw = true;
2242
- scheduleRender();
2243
- };
2244
- process.on("SIGCONT", handleSigcont);
2245
- const wrappedElement = React15.createElement(
2246
- AppContext.Provider,
2247
- { value: appContextValue },
2248
- React15.createElement(
2249
- InputContext.Provider,
2250
- { value: inputContextValue },
2251
- React15.createElement(
2252
- FocusContext.Provider,
2253
- { value: focusContextValue },
2254
- React15.createElement(
2255
- LayoutContext.Provider,
2256
- { value: layoutContextValue },
2257
- element
2258
- )
2259
- )
2260
- )
2261
- );
2262
- const onUncaughtError = (error) => {
2263
- if (debug) console.error("Uncaught error:", error);
2264
- };
2265
- const onCaughtError = (error) => {
2266
- if (debug) console.error("Error caught by boundary:", error);
2267
- };
2268
- const onRecoverableError = (error) => {
2269
- if (debug) console.error("Recoverable error:", error);
2270
- };
2271
- const root = reconciler.createContainer(
2272
- container,
2273
- 0,
2274
- // LegacyRoot tag
2275
- null,
2276
- // hydrationCallbacks
2277
- false,
2278
- // isStrictMode
2279
- null,
2280
- // concurrentUpdatesByDefaultOverride
2281
- "",
2282
- // identifierPrefix
2283
- onUncaughtError,
2284
- onCaughtError,
2285
- onRecoverableError,
2286
- null
2287
- // transitionCallbacks
2288
- );
2289
- reconciler.updateContainer(wrappedElement, root, null, null);
2290
- const handle = {
2291
- unmount() {
2292
- reconciler.updateContainer(null, root, null, null);
2293
- removeDataListener();
2294
- removeResizeListener();
2295
- process.off("SIGCONT", handleSigcont);
2296
- terminal.cleanup();
2297
- },
2298
- exit(code) {
2299
- handle.unmount();
2300
- process.exit(code ?? 0);
2301
- }
2302
- };
2303
- return handle;
2304
- }
2305
- var Box = forwardRef(
2306
- function Box2({ children, style, focusable }, ref) {
2307
- return React15.createElement("box", { style, focusable, ref }, children);
2308
- }
2309
- );
2310
- var Text = forwardRef(
2311
- function Text2({ children, style, wrap }, ref) {
2312
- const mergedStyle = wrap ? { ...style, wrap } : style;
2313
- return React15.createElement("text", { style: mergedStyle, ref }, children);
2314
- }
2315
- );
2316
- function cursorToVisualLine(text, pos, width) {
2317
- if (width <= 0) {
2318
- return { visualLine: 0, visualCol: pos, totalVisualLines: 1, lineStartOffset: 0, lineLength: text.length };
2319
- }
2320
- const logicalLines = text.split("\n");
2321
- const allVisualLines = [];
2322
- let logicalOffset = 0;
2323
- for (const logicalLine of logicalLines) {
2324
- const wrapped = wrapLines([logicalLine], width, "wrap");
2325
- let offsetInLogical = 0;
2326
- for (const wrappedLine of wrapped) {
2327
- allVisualLines.push({
2328
- text: wrappedLine,
2329
- logicalOffset: logicalOffset + offsetInLogical
2330
- });
2331
- offsetInLogical += wrappedLine.length;
2332
- }
2333
- logicalOffset += logicalLine.length + 1;
2334
- }
2335
- let charCount = 0;
2336
- for (let i = 0; i < allVisualLines.length; i++) {
2337
- const vl = allVisualLines[i];
2338
- const lineLen = vl.text.length;
2339
- const isEndOfLogicalLine = i + 1 < allVisualLines.length && allVisualLines[i + 1].logicalOffset !== vl.logicalOffset + lineLen;
2340
- const effectiveLen = lineLen + (isEndOfLogicalLine ? 1 : 0);
2341
- if (pos < charCount + lineLen || i === allVisualLines.length - 1) {
2342
- return {
2343
- visualLine: i,
2344
- visualCol: Math.min(pos - charCount, lineLen),
2345
- totalVisualLines: allVisualLines.length,
2346
- lineStartOffset: charCount,
2347
- lineLength: lineLen
2348
- };
2349
- }
2350
- charCount += effectiveLen;
2351
- }
2352
- const lastIdx = allVisualLines.length - 1;
2353
- return {
2354
- visualLine: lastIdx,
2355
- visualCol: allVisualLines[lastIdx].text.length,
2356
- totalVisualLines: allVisualLines.length,
2357
- lineStartOffset: charCount - allVisualLines[lastIdx].text.length,
2358
- lineLength: allVisualLines[lastIdx].text.length
2359
- };
2360
- }
2361
- function visualLineToCursor(text, visualLine, visualCol, width) {
2362
- if (width <= 0) {
2363
- return Math.min(visualCol, text.length);
2364
- }
2365
- const logicalLines = text.split("\n");
2366
- const allVisualLines = [];
2367
- let offset = 0;
2368
- for (const logicalLine of logicalLines) {
2369
- const wrapped = wrapLines([logicalLine], width, "wrap");
2370
- let offsetInLogical = 0;
2371
- for (const wrappedLine of wrapped) {
2372
- allVisualLines.push({
2373
- text: wrappedLine,
2374
- startOffset: offset + offsetInLogical
2375
- });
2376
- offsetInLogical += wrappedLine.length;
2377
- }
2378
- offset += logicalLine.length + 1;
2379
- }
2380
- const targetLine = Math.max(0, Math.min(visualLine, allVisualLines.length - 1));
2381
- const vl = allVisualLines[targetLine];
2382
- const col = Math.min(visualCol, vl.text.length);
2383
- return vl.startOffset + col;
2384
- }
2385
- function cursorToLineCol(text, pos) {
2386
- const lines = text.split("\n");
2387
- let remaining = pos;
2388
- for (let i = 0; i < lines.length; i++) {
2389
- if (remaining <= lines[i].length) {
2390
- return { line: i, col: remaining, lines };
2391
- }
2392
- remaining -= lines[i].length + 1;
2393
- }
2394
- const last = lines.length - 1;
2395
- return { line: last, col: lines[last].length, lines };
2396
- }
2397
- function lineColToCursor(lines, line, col) {
2398
- let pos = 0;
2399
- for (let i = 0; i < line && i < lines.length; i++) {
2400
- pos += lines[i].length + 1;
2401
- }
2402
- return pos + Math.min(col, lines[line]?.length ?? 0);
2403
- }
2404
- function Input(props) {
2405
- const {
2406
- value: controlledValue,
2407
- defaultValue = "",
2408
- onChange,
2409
- onKeyPress,
2410
- onBeforeChange,
2411
- placeholder,
2412
- style,
2413
- focusedStyle,
2414
- multiline,
2415
- autoFocus,
2416
- type = "text"
2417
- } = props;
2418
- const [internalValue, setInternalValue] = useState(defaultValue);
2419
- const [cursorPos, setCursorPos] = useState(defaultValue.length);
2420
- const [innerWidth, setInnerWidth] = useState(0);
2421
- const [isFocused, setIsFocused] = useState(false);
2422
- const [nodeReady, setNodeReady] = useState(false);
2423
- const inputCtx = useContext(InputContext);
2424
- const focusCtx = useContext(FocusContext);
2425
- const layoutCtx = useContext(LayoutContext);
2426
- const nodeRef = useRef(null);
2427
- const focusIdRef = useRef(null);
2428
- const isControlled = controlledValue !== void 0;
2429
- const value = isControlled ? controlledValue : internalValue;
2430
- useEffect(() => {
2431
- if (!layoutCtx || !nodeRef.current) return;
2432
- const layout = layoutCtx.getLayout(nodeRef.current);
2433
- setInnerWidth(layout.innerWidth);
2434
- return layoutCtx.subscribe(nodeRef.current, (rect) => {
2435
- setInnerWidth(rect.innerWidth);
2436
- });
2437
- }, [layoutCtx]);
2438
- const workingValueRef = useRef(value);
2439
- const workingCursorRef = useRef(cursorPos);
2440
- useEffect(() => {
2441
- workingValueRef.current = value;
2442
- if (workingCursorRef.current > value.length) {
2443
- workingCursorRef.current = value.length;
2444
- setCursorPos(value.length);
2445
- }
2446
- }, [value]);
2447
- useEffect(() => {
2448
- workingCursorRef.current = cursorPos;
2449
- }, [cursorPos]);
2450
- const stateRef = useRef({
2451
- isControlled,
2452
- onChange,
2453
- onKeyPress,
2454
- onBeforeChange,
2455
- multiline: multiline ?? false,
2456
- innerWidth,
2457
- type
2458
- });
2459
- stateRef.current = {
2460
- isControlled,
2461
- onChange,
2462
- onKeyPress,
2463
- onBeforeChange,
2464
- multiline: multiline ?? false,
2465
- innerWidth,
2466
- type
2467
- };
2468
- useEffect(() => {
2469
- if (!focusCtx || !focusIdRef.current || !nodeRef.current) return;
2470
- return focusCtx.register(focusIdRef.current, nodeRef.current);
2471
- }, [focusCtx, nodeReady]);
2472
- const autoFocusedRef = useRef(false);
2473
- useEffect(() => {
2474
- if (autoFocus && !autoFocusedRef.current && focusCtx && focusIdRef.current) {
2475
- autoFocusedRef.current = true;
2476
- const fid = focusIdRef.current;
2477
- queueMicrotask(() => {
2478
- focusCtx.requestFocus(fid);
2479
- });
2480
- }
2481
- }, [autoFocus, focusCtx, nodeReady]);
2482
- useEffect(() => {
2483
- if (!focusCtx || !focusIdRef.current) return;
2484
- const fid = focusIdRef.current;
2485
- setIsFocused(focusCtx.focusedId === fid);
2486
- return focusCtx.onFocusChange((newId) => {
2487
- setIsFocused(newId === fid);
2488
- });
2489
- }, [focusCtx, nodeReady]);
2490
- useEffect(() => {
2491
- if (!inputCtx || !focusIdRef.current) return;
2492
- const fid = focusIdRef.current;
2493
- const handler = (key) => {
2494
- const {
2495
- isControlled: ctrl,
2496
- onChange: cb,
2497
- onKeyPress: onKey,
2498
- onBeforeChange: onBefore,
2499
- multiline: ml
2500
- } = stateRef.current;
2501
- if (onKey?.(key) === true) {
2502
- return true;
2503
- }
2504
- const val = workingValueRef.current;
2505
- const pos = workingCursorRef.current;
2506
- if (key.name === "escape") return false;
2507
- const updateValue = (newVal, newCursor) => {
2508
- let finalVal = newVal;
2509
- let finalCursor = newCursor;
2510
- if (onBefore) {
2511
- const result = onBefore(newVal, val);
2512
- if (result === false) {
2513
- return;
2514
- }
2515
- if (typeof result === "string") {
2516
- finalVal = result;
2517
- finalCursor = result.length;
2518
- }
2519
- }
2520
- workingValueRef.current = finalVal;
2521
- workingCursorRef.current = finalCursor;
2522
- if (!ctrl) setInternalValue(finalVal);
2523
- cb?.(finalVal);
2524
- setCursorPos(finalCursor);
2525
- };
2526
- const updateCursor = (newCursor) => {
2527
- workingCursorRef.current = newCursor;
2528
- setCursorPos(newCursor);
2529
- };
2530
- if (key.name === "return") {
2531
- if (ml) {
2532
- const newVal = val.slice(0, pos) + "\n" + val.slice(pos);
2533
- updateValue(newVal, pos + 1);
2534
- return true;
2535
- }
2536
- return false;
2537
- }
2538
- if (key.ctrl) {
2539
- if (key.name === "w") {
2540
- if (pos > 0) {
2541
- let i = pos;
2542
- while (i > 0 && val[i - 1] === " ") i--;
2543
- while (i > 0 && val[i - 1] !== " " && (!ml || val[i - 1] !== "\n"))
2544
- i--;
2545
- const newVal = val.slice(0, i) + val.slice(pos);
2546
- updateValue(newVal, i);
2547
- }
2548
- return true;
2549
- }
2550
- if (key.name === "a") {
2551
- if (ml) {
2552
- const { line, lines } = cursorToLineCol(val, pos);
2553
- updateCursor(lineColToCursor(lines, line, 0));
2554
- } else {
2555
- updateCursor(0);
2556
- }
2557
- return true;
2558
- }
2559
- if (key.name === "e") {
2560
- if (ml) {
2561
- const { line, lines } = cursorToLineCol(val, pos);
2562
- updateCursor(lineColToCursor(lines, line, lines[line].length));
2563
- } else {
2564
- updateCursor(val.length);
2565
- }
2566
- return true;
2567
- }
2568
- if (key.name === "k") {
2569
- if (ml) {
2570
- const { line, lines } = cursorToLineCol(val, pos);
2571
- const lineEnd = lineColToCursor(lines, line, lines[line].length);
2572
- if (pos < lineEnd) {
2573
- const newVal = val.slice(0, pos) + val.slice(lineEnd);
2574
- updateValue(newVal, pos);
2575
- }
2576
- } else {
2577
- if (pos < val.length) {
2578
- const newVal = val.slice(0, pos);
2579
- updateValue(newVal, pos);
2580
- }
2581
- }
2582
- return true;
2583
- }
2584
- return false;
2585
- }
2586
- if (key.alt) {
2587
- if (key.name === "left" || key.name === "b") {
2588
- let i = pos;
2589
- while (i > 0 && val[i - 1] === " ") i--;
2590
- while (i > 0 && val[i - 1] !== " " && val[i - 1] !== "\n") i--;
2591
- updateCursor(i);
2592
- return true;
2593
- }
2594
- if (key.name === "right" || key.name === "f") {
2595
- let i = pos;
2596
- while (i < val.length && val[i] !== " " && val[i] !== "\n") i++;
2597
- while (i < val.length && val[i] === " ") i++;
2598
- updateCursor(i);
2599
- return true;
2600
- }
2601
- if (key.name === "backspace" || key.name === "d") {
2602
- if (key.name === "backspace") {
2603
- if (pos > 0) {
2604
- let i = pos;
2605
- while (i > 0 && val[i - 1] === " ") i--;
2606
- while (i > 0 && val[i - 1] !== " " && val[i - 1] !== "\n") i--;
2607
- const newVal = val.slice(0, i) + val.slice(pos);
2608
- updateValue(newVal, i);
2609
- }
2610
- return true;
2611
- } else {
2612
- if (pos < val.length) {
2613
- let i = pos;
2614
- while (i < val.length && val[i] !== " " && val[i] !== "\n") i++;
2615
- while (i < val.length && val[i] === " ") i++;
2616
- const newVal = val.slice(0, pos) + val.slice(i);
2617
- updateValue(newVal, pos);
2618
- }
2619
- return true;
2620
- }
2621
- }
2622
- return false;
2623
- }
2624
- if (key.name === "left") {
2625
- updateCursor(Math.max(0, pos - 1));
2626
- return true;
2627
- }
2628
- if (key.name === "right") {
2629
- updateCursor(Math.min(val.length, pos + 1));
2630
- return true;
2631
- }
2632
- if (key.name === "up") {
2633
- const { innerWidth: w } = stateRef.current;
2634
- const info = cursorToVisualLine(val, pos, w);
2635
- if (info.visualLine > 0) {
2636
- updateCursor(visualLineToCursor(val, info.visualLine - 1, info.visualCol, w));
2637
- }
2638
- return true;
2639
- }
2640
- if (key.name === "down") {
2641
- const { innerWidth: w } = stateRef.current;
2642
- const info = cursorToVisualLine(val, pos, w);
2643
- if (info.visualLine < info.totalVisualLines - 1) {
2644
- updateCursor(visualLineToCursor(val, info.visualLine + 1, info.visualCol, w));
2645
- }
2646
- return true;
2647
- }
2648
- if (key.name === "home") {
2649
- if (ml) {
2650
- const { line, lines } = cursorToLineCol(val, pos);
2651
- updateCursor(lineColToCursor(lines, line, 0));
2652
- } else {
2653
- updateCursor(0);
2654
- }
2655
- return true;
2656
- }
2657
- if (key.name === "end") {
2658
- if (ml) {
2659
- const { line, lines } = cursorToLineCol(val, pos);
2660
- updateCursor(lineColToCursor(lines, line, lines[line].length));
2661
- } else {
2662
- updateCursor(val.length);
2663
- }
2664
- return true;
2665
- }
2666
- if (key.name === "backspace") {
2667
- if (pos > 0) {
2668
- const newVal = val.slice(0, pos - 1) + val.slice(pos);
2669
- updateValue(newVal, pos - 1);
2670
- }
2671
- return true;
2672
- }
2673
- if (key.name === "delete") {
2674
- if (pos < val.length) {
2675
- const newVal = val.slice(0, pos) + val.slice(pos + 1);
2676
- updateValue(newVal, pos);
2677
- }
2678
- return true;
2679
- }
2680
- if (key.name.length > 1) return false;
2681
- const ch = key.sequence;
2682
- if (ch.length === 1 && ch.charCodeAt(0) >= 32) {
2683
- const { type: inputType } = stateRef.current;
2684
- if (inputType === "number") {
2685
- const isDigit = /[0-9]/.test(ch);
2686
- const isDecimal = ch === "." && !val.includes(".");
2687
- const isMinus = ch === "-" && pos === 0 && !val.includes("-");
2688
- if (!isDigit && !isDecimal && !isMinus) {
2689
- return true;
2690
- }
2691
- }
2692
- const newVal = val.slice(0, pos) + ch + val.slice(pos);
2693
- updateValue(newVal, pos + 1);
2694
- return true;
2695
- }
2696
- return false;
2697
- };
2698
- return inputCtx.registerInputHandler(fid, handler);
2699
- }, [inputCtx, nodeReady]);
2700
- const mergedStyle = {
2701
- ...style,
2702
- ...isFocused && focusedStyle ? focusedStyle : {}
2703
- };
2704
- return React15.createElement("input", {
2705
- style: mergedStyle,
2706
- value,
2707
- defaultValue,
2708
- placeholder,
2709
- onChange,
2710
- cursorPosition: cursorPos,
2711
- multiline: multiline ?? false,
2712
- focused: isFocused,
2713
- ref: (node) => {
2714
- if (node) {
2715
- nodeRef.current = node;
2716
- focusIdRef.current = node.focusId;
2717
- setNodeReady(true);
2718
- } else {
2719
- nodeRef.current = null;
2720
- focusIdRef.current = null;
2721
- setNodeReady(false);
2722
- }
2723
- }
2724
- });
2725
- }
2726
- function FocusScope({ trap = false, children }) {
2727
- const focusCtx = useContext(FocusContext);
2728
- const prevFocusRef = useRef(null);
2729
- const scopeIdsRef = useRef(/* @__PURE__ */ new Set());
2730
- useLayoutEffect(() => {
2731
- if (!trap || !focusCtx) return;
2732
- prevFocusRef.current = focusCtx.focusedId;
2733
- const cleanup = focusCtx.pushTrap(scopeIdsRef.current);
2734
- return () => {
2735
- cleanup();
2736
- if (prevFocusRef.current) {
2737
- focusCtx.requestFocus(prevFocusRef.current);
2738
- }
2739
- };
2740
- }, [trap, focusCtx]);
2741
- useEffect(() => {
2742
- if (!trap || !focusCtx) return;
2743
- if (scopeIdsRef.current.size > 0) {
2744
- const firstId = scopeIdsRef.current.values().next().value;
2745
- if (firstId) focusCtx.requestFocus(firstId);
2746
- }
2747
- }, [trap, focusCtx]);
2748
- return React15.createElement(React15.Fragment, null, children);
2749
- }
2750
- function Spacer({ size }) {
2751
- return React15.createElement("box", {
2752
- style: { flexGrow: size ?? 1 }
2753
- });
2754
- }
2755
- function parseKeyDescriptor(descriptor) {
2756
- const parts = descriptor.toLowerCase().split("+");
2757
- const name = parts[parts.length - 1];
2758
- return {
2759
- name,
2760
- ctrl: parts.includes("ctrl"),
2761
- alt: parts.includes("alt"),
2762
- shift: parts.includes("shift"),
2763
- meta: parts.includes("meta") || parts.includes("cmd") || parts.includes("super") || parts.includes("win")
2764
- };
2765
- }
2766
- function matchesKey(matcher, key) {
2767
- if (key.name !== matcher.name) return false;
2768
- if (matcher.ctrl !== !!key.ctrl) return false;
2769
- if (matcher.alt !== !!key.alt) return false;
2770
- if (matcher.shift !== !!key.shift) return false;
2771
- if (matcher.meta !== !!key.meta) return false;
2772
- return true;
2773
- }
2774
- function Keybind({
2775
- keypress,
2776
- onPress,
2777
- whenFocused,
2778
- priority,
2779
- disabled
2780
- }) {
2781
- const inputCtx = useContext(InputContext);
2782
- const focusCtx = useContext(FocusContext);
2783
- const onPressRef = useRef(onPress);
2784
- onPressRef.current = onPress;
2785
- const matcherRef = useRef(parseKeyDescriptor(keypress));
2786
- matcherRef.current = parseKeyDescriptor(keypress);
2787
- useEffect(() => {
2788
- if (!inputCtx || disabled) return;
2789
- if (priority) {
2790
- const handler = (key) => {
2791
- if (!matchesKey(matcherRef.current, key)) return false;
2792
- if (whenFocused && focusCtx?.focusedId !== whenFocused) return false;
2793
- onPressRef.current();
2794
- return true;
2795
- };
2796
- return inputCtx.subscribePriority(handler);
2797
- } else {
2798
- const handler = (key) => {
2799
- if (!matchesKey(matcherRef.current, key)) return;
2800
- if (whenFocused && focusCtx?.focusedId !== whenFocused) return;
2801
- onPressRef.current();
2802
- };
2803
- return inputCtx.subscribe(handler);
2804
- }
2805
- }, [inputCtx, focusCtx, whenFocused, priority, disabled]);
2806
- return null;
2807
- }
2808
- function Portal({ children, zIndex = 1e3 }) {
2809
- return React15.createElement(
2810
- "box",
2811
- {
2812
- style: {
2813
- position: "absolute",
2814
- top: 0,
2815
- left: 0,
2816
- width: "100%",
2817
- height: "100%",
2818
- zIndex
2819
- }
2820
- },
2821
- children
2822
- );
2823
- }
2824
- function Button({
2825
- onPress,
2826
- style,
2827
- focusedStyle,
2828
- children,
2829
- disabled
2830
- }) {
2831
- const focusCtx = useContext(FocusContext);
2832
- const inputCtx = useContext(InputContext);
2833
- const nodeRef = useRef(null);
2834
- const focusIdRef = useRef(null);
2835
- const onPressRef = useRef(onPress);
2836
- onPressRef.current = onPress;
2837
- const [nodeReady, setNodeReady] = useState(false);
2838
- const [isFocused, setIsFocused] = useState(false);
2839
- useEffect(() => {
2840
- if (!focusCtx || !focusIdRef.current || !nodeRef.current || disabled) return;
2841
- return focusCtx.register(focusIdRef.current, nodeRef.current);
2842
- }, [focusCtx, disabled, nodeReady]);
2843
- useEffect(() => {
2844
- if (!focusCtx || !focusIdRef.current) return;
2845
- const fid = focusIdRef.current;
2846
- setIsFocused(focusCtx.focusedId === fid);
2847
- return focusCtx.onFocusChange((newId) => {
2848
- setIsFocused(newId === fid);
2849
- });
2850
- }, [focusCtx, nodeReady]);
2851
- useEffect(() => {
2852
- if (!inputCtx || !focusIdRef.current || disabled) return;
2853
- const fid = focusIdRef.current;
2854
- const handler = (key) => {
2855
- if (focusCtx?.focusedId !== fid) return false;
2856
- if (key.name === "return" || key.name === " " || key.sequence === " ") {
2857
- onPressRef.current?.();
2858
- return true;
2859
- }
2860
- return false;
2861
- };
2862
- return inputCtx.registerInputHandler(fid, handler);
2863
- }, [inputCtx, focusCtx, disabled, nodeReady]);
2864
- const mergedStyle = {
2865
- ...style,
2866
- ...isFocused && focusedStyle ? focusedStyle : {}
2867
- };
2868
- return React15.createElement(
2869
- "box",
2870
- {
2871
- style: mergedStyle,
2872
- focusable: !disabled,
2873
- ref: (node) => {
2874
- if (node) {
2875
- nodeRef.current = node;
2876
- focusIdRef.current = node.focusId;
2877
- setNodeReady(true);
2878
- } else {
2879
- nodeRef.current = null;
2880
- focusIdRef.current = null;
2881
- setNodeReady(false);
2882
- }
2883
- }
2884
- },
2885
- children
2886
- );
2887
- }
2888
- var DEFAULT_RECT = {
2889
- x: 0,
2890
- y: 0,
2891
- width: 0,
2892
- height: 0,
2893
- innerX: 0,
2894
- innerY: 0,
2895
- innerWidth: 0,
2896
- innerHeight: 0
2897
- };
2898
- function useLayout(nodeRef) {
2899
- const ctx = useContext(LayoutContext);
2900
- const [layout, setLayout] = useState(DEFAULT_RECT);
2901
- useEffect(() => {
2902
- if (!ctx || !nodeRef?.current) return;
2903
- setLayout(ctx.getLayout(nodeRef.current));
2904
- return ctx.subscribe(nodeRef.current, setLayout);
2905
- }, [ctx, nodeRef]);
2906
- return layout;
2907
- }
2908
- function useInput(handler, deps = []) {
2909
- const ctx = useContext(InputContext);
2910
- useEffect(() => {
2911
- if (!ctx) return;
2912
- return ctx.subscribe(handler);
2913
- }, [ctx, ...deps]);
2914
- }
2915
-
2916
- // src/components/ScrollView.tsx
2917
- function ScrollView({
2918
- children,
2919
- style,
2920
- scrollOffset: controlledOffset,
2921
- onScroll,
2922
- defaultScrollOffset = 0,
2923
- scrollStep = 1,
2924
- disableKeyboard,
2925
- scrollToFocus = true,
2926
- showScrollbar = true,
2927
- focusable = true,
2928
- focusedStyle
2929
- }) {
2930
- const isControlled = controlledOffset !== void 0;
2931
- const [internalOffset, setInternalOffset] = useState(defaultScrollOffset);
2932
- const offset = isControlled ? controlledOffset : internalOffset;
2933
- const viewportRef = useRef(null);
2934
- const contentRef = useRef(null);
2935
- const viewportLayout = useLayout(viewportRef);
2936
- const contentLayout = useLayout(contentRef);
2937
- const focusCtx = useContext(FocusContext);
2938
- const layoutCtx = useContext(LayoutContext);
2939
- const focusIdRef = useRef(null);
2940
- if (focusable && !focusIdRef.current) {
2941
- focusIdRef.current = `scrollview-${Math.random().toString(36).slice(2, 9)}`;
2942
- }
2943
- const focusId = focusable ? focusIdRef.current : null;
2944
- useEffect(() => {
2945
- if (!focusable || !focusId || !focusCtx || !viewportRef.current) return;
2946
- return focusCtx.register(focusId, viewportRef.current);
2947
- }, [focusable, focusId, focusCtx]);
2948
- const isSelfFocused = focusable && focusId && focusCtx?.focusedId === focusId;
2949
- const viewportHeight = viewportLayout.innerHeight;
2950
- const contentHeight = contentLayout.height;
2951
- const maxOffset = Math.max(0, contentHeight - viewportHeight);
2952
- const effectiveOffset = Math.max(0, Math.min(offset, maxOffset));
2953
- const setOffset = useCallback(
2954
- (next) => {
2955
- const clamped = Math.max(0, Math.min(next, maxOffset));
2956
- if (isControlled) {
2957
- onScroll?.(clamped);
2958
- } else {
2959
- setInternalOffset(clamped);
2960
- }
2961
- },
2962
- [isControlled, onScroll, maxOffset]
2963
- );
2964
- useEffect(() => {
2965
- if (offset > maxOffset && maxOffset >= 0) {
2966
- setOffset(maxOffset);
2967
- }
2968
- }, [offset, maxOffset, setOffset]);
2969
- useEffect(() => {
2970
- if (!scrollToFocus || !focusCtx || !layoutCtx || !contentRef.current) return;
2971
- const unsubscribe = focusCtx.onFocusChange((focusedId) => {
2972
- if (!focusedId || !contentRef.current) return;
2973
- const findNode = (node) => {
2974
- if (node.focusId === focusedId) return node;
2975
- for (const child of node.children) {
2976
- const found = findNode(child);
2977
- if (found) return found;
2978
- }
2979
- return null;
2980
- };
2981
- const focusedNode = findNode(contentRef.current);
2982
- if (!focusedNode) return;
2983
- const focusedLayout = layoutCtx.getLayout(focusedNode);
2984
- const contentTopY = contentRef.current.layout?.y ?? 0;
2985
- const elementTop = focusedLayout.y - contentTopY;
2986
- const elementBottom = elementTop + focusedLayout.height;
2987
- const visibleTop = offset;
2988
- const visibleBottom = offset + viewportHeight;
2989
- if (elementTop < visibleTop) {
2990
- setOffset(elementTop);
2991
- } else if (elementBottom > visibleBottom) {
2992
- setOffset(elementBottom - viewportHeight);
2993
- }
2994
- });
2995
- return unsubscribe;
2996
- }, [scrollToFocus, focusCtx, layoutCtx, offset, viewportHeight, setOffset]);
2997
- const containsFocus = useCallback(() => {
2998
- if (!focusCtx) return false;
2999
- const currentFocusId = focusCtx.focusedId;
3000
- if (!currentFocusId) return false;
3001
- if (focusable && focusId && currentFocusId === focusId) return true;
3002
- if (!contentRef.current) return false;
3003
- const findNode = (node) => {
3004
- if (node.focusId === currentFocusId) return true;
3005
- for (const child of node.children) {
3006
- if (findNode(child)) return true;
3007
- }
3008
- return false;
3009
- };
3010
- return findNode(contentRef.current);
3011
- }, [focusCtx, focusable, focusId]);
3012
- useInput((key) => {
3013
- if (disableKeyboard) return;
3014
- if (!containsFocus()) return;
3015
- const halfPage = Math.max(1, Math.floor(viewportHeight / 2));
3016
- const fullPage = Math.max(1, viewportHeight);
3017
- switch (key.name) {
3018
- // Page keys - always safe, inputs don't use these
3019
- case "pageup":
3020
- setOffset(offset - fullPage);
3021
- break;
3022
- case "pagedown":
3023
- setOffset(offset + fullPage);
3024
- break;
3025
- default:
3026
- if (key.ctrl) {
3027
- if (key.name === "d") {
3028
- setOffset(offset + halfPage);
3029
- } else if (key.name === "u") {
3030
- setOffset(offset - halfPage);
3031
- } else if (key.name === "f") {
3032
- setOffset(offset + fullPage);
3033
- } else if (key.name === "b") {
3034
- setOffset(offset - fullPage);
3035
- }
3036
- }
3037
- break;
3038
- }
3039
- }, [offset, scrollStep, viewportHeight, maxOffset, disableKeyboard, setOffset, containsFocus]);
3040
- const {
3041
- padding: _pad,
3042
- paddingX: _px,
3043
- paddingY: _py,
3044
- paddingTop: _pt,
3045
- paddingRight: _pr,
3046
- paddingBottom: _pb,
3047
- paddingLeft: _pl,
3048
- ...styleRest
3049
- } = style ?? {};
3050
- const hasBorder = styleRest.border != null && styleRest.border !== "none";
3051
- const borderHeight = hasBorder ? 2 : 0;
3052
- const intrinsicHeight = contentHeight > 0 ? contentHeight + borderHeight : void 0;
3053
- const outerStyle = {
3054
- ...styleRest,
3055
- ...isSelfFocused ? focusedStyle : {},
3056
- clip: true,
3057
- // Only set intrinsic height if user didn't set explicit height
3058
- ...styleRest.height === void 0 && intrinsicHeight !== void 0 ? {
3059
- height: intrinsicHeight,
3060
- flexShrink: styleRest.flexShrink ?? 1,
3061
- minHeight: styleRest.minHeight ?? 0
3062
- } : {}
3063
- };
3064
- const innerStyle = {
3065
- position: "absolute",
3066
- top: -effectiveOffset,
3067
- left: 0,
3068
- right: 0,
3069
- flexDirection: "column",
3070
- ..._pad !== void 0 && { padding: _pad },
3071
- ..._px !== void 0 && { paddingX: _px },
3072
- ..._py !== void 0 && { paddingY: _py },
3073
- ..._pt !== void 0 && { paddingTop: _pt },
3074
- ..._pr !== void 0 && { paddingRight: _pr },
3075
- ..._pb !== void 0 && { paddingBottom: _pb },
3076
- ..._pl !== void 0 && { paddingLeft: _pl }
3077
- };
3078
- const isScrollable = contentHeight > viewportHeight && viewportHeight > 0;
3079
- const scrollbarVisible = showScrollbar && isScrollable;
3080
- const thumbHeight = Math.max(1, Math.floor(viewportHeight / contentHeight * viewportHeight));
3081
- const scrollableRange = contentHeight - viewportHeight;
3082
- const thumbPosition = scrollableRange > 0 ? Math.floor(effectiveOffset / scrollableRange * (viewportHeight - thumbHeight)) : 0;
3083
- const scrollbarChars = [];
3084
- if (scrollbarVisible) {
3085
- for (let i = 0; i < viewportHeight; i++) {
3086
- if (i >= thumbPosition && i < thumbPosition + thumbHeight) {
3087
- scrollbarChars.push("\u2588");
3088
- } else {
3089
- scrollbarChars.push("\u2591");
3090
- }
3091
- }
3092
- }
3093
- const scrollbarStyle = {
3094
- position: "absolute",
3095
- top: 0,
3096
- right: 0,
3097
- width: 1,
3098
- height: viewportHeight,
3099
- flexDirection: "column"
3100
- };
3101
- return React15.createElement(
3102
- "box",
3103
- {
3104
- style: outerStyle,
3105
- ref: (node) => {
3106
- viewportRef.current = node ?? null;
3107
- },
3108
- ...focusable ? { focusable: true, focusId } : {}
3109
- },
3110
- // Content (absolutely positioned, scrolls via top offset)
3111
- React15.createElement(
3112
- "box",
3113
- {
3114
- style: {
3115
- ...innerStyle,
3116
- // Reserve space for scrollbar when visible
3117
- paddingRight: scrollbarVisible ? (innerStyle.paddingRight ?? 0) + 1 : innerStyle.paddingRight
3118
- },
3119
- ref: (node) => {
3120
- contentRef.current = node ?? null;
3121
- }
3122
- },
3123
- children
3124
- ),
3125
- // Scrollbar
3126
- scrollbarVisible && React15.createElement(
3127
- "box",
3128
- { style: scrollbarStyle },
3129
- React15.createElement(
3130
- "text",
3131
- { style: { color: "blackBright" } },
3132
- scrollbarChars.join("\n")
3133
- )
3134
- )
3135
- );
3136
- }
3137
- function List({
3138
- count,
3139
- renderItem,
3140
- selectedIndex: controlledIndex,
3141
- onSelectionChange,
3142
- onSelect,
3143
- defaultSelectedIndex = 0,
3144
- disabledIndices,
3145
- style,
3146
- focusable = true
3147
- }) {
3148
- const isControlled = controlledIndex !== void 0;
3149
- const [internalIndex, setInternalIndex] = useState(defaultSelectedIndex);
3150
- const selectedIndex = isControlled ? controlledIndex : internalIndex;
3151
- const focusCtx = useContext(FocusContext);
3152
- const inputCtx = useContext(InputContext);
3153
- const nodeRef = useRef(null);
3154
- const focusIdRef = useRef(null);
3155
- const onSelectRef = useRef(onSelect);
3156
- onSelectRef.current = onSelect;
3157
- const [nodeReady, setNodeReady] = useState(false);
3158
- const [isFocused, setIsFocused] = useState(false);
3159
- const lastKeyRef = useRef(null);
3160
- const setIndex = useCallback(
3161
- (next) => {
3162
- const clamped = Math.max(0, Math.min(next, count - 1));
3163
- if (isControlled) {
3164
- onSelectionChange?.(clamped);
3165
- } else {
3166
- setInternalIndex(clamped);
3167
- }
3168
- },
3169
- [isControlled, onSelectionChange, count]
3170
- );
3171
- const findNextEnabled = useCallback(
3172
- (from, direction) => {
3173
- if (!disabledIndices || disabledIndices.size === 0) {
3174
- return Math.max(0, Math.min(from + direction, count - 1));
3175
- }
3176
- let next = from + direction;
3177
- while (next >= 0 && next < count && disabledIndices.has(next)) {
3178
- next += direction;
3179
- }
3180
- if (next < 0 || next >= count) return from;
3181
- return next;
3182
- },
3183
- [disabledIndices, count]
3184
- );
3185
- useEffect(() => {
3186
- if (!focusCtx || !focusIdRef.current || !nodeRef.current || !focusable) return;
3187
- return focusCtx.register(focusIdRef.current, nodeRef.current);
3188
- }, [focusCtx, focusable, nodeReady]);
3189
- useEffect(() => {
3190
- if (!focusCtx || !focusIdRef.current) return;
3191
- const fid = focusIdRef.current;
3192
- setIsFocused(focusCtx.focusedId === fid);
3193
- return focusCtx.onFocusChange((newId) => {
3194
- setIsFocused(newId === fid);
3195
- });
3196
- }, [focusCtx, nodeReady]);
3197
- const findFirstEnabled = useCallback(
3198
- (fromEnd) => {
3199
- const start = fromEnd ? count - 1 : 0;
3200
- const direction = fromEnd ? -1 : 1;
3201
- let index = start;
3202
- while (index >= 0 && index < count && disabledIndices?.has(index)) {
3203
- index += direction;
3204
- }
3205
- return index >= 0 && index < count ? index : fromEnd ? count - 1 : 0;
3206
- },
3207
- [disabledIndices, count]
3208
- );
3209
- useEffect(() => {
3210
- if (!inputCtx || !focusIdRef.current || !focusable) return;
3211
- const fid = focusIdRef.current;
3212
- const handler = (key) => {
3213
- if (focusCtx?.focusedId !== fid) return false;
3214
- if (key.name === "g" && !key.ctrl && !key.alt) {
3215
- if (lastKeyRef.current === "g") {
3216
- setIndex(findFirstEnabled(false));
3217
- lastKeyRef.current = null;
3218
- return true;
3219
- }
3220
- lastKeyRef.current = "g";
3221
- return true;
3222
- }
3223
- if (key.name === "G" || key.name === "g" && key.shift) {
3224
- lastKeyRef.current = null;
3225
- setIndex(findFirstEnabled(true));
3226
- return true;
3227
- }
3228
- lastKeyRef.current = null;
3229
- if (key.name === "up" || key.name === "k") {
3230
- setIndex(findNextEnabled(selectedIndex, -1));
3231
- return true;
3232
- }
3233
- if (key.name === "down" || key.name === "j") {
3234
- setIndex(findNextEnabled(selectedIndex, 1));
3235
- return true;
3236
- }
3237
- if (key.name === "return") {
3238
- if (!disabledIndices?.has(selectedIndex)) {
3239
- onSelectRef.current?.(selectedIndex);
3240
- }
3241
- return true;
3242
- }
3243
- return false;
3244
- };
3245
- return inputCtx.registerInputHandler(fid, handler);
3246
- }, [inputCtx, focusCtx, focusable, selectedIndex, setIndex, findNextEnabled, findFirstEnabled, disabledIndices, nodeReady]);
3247
- const items = [];
3248
- for (let i = 0; i < count; i++) {
3249
- items.push(
3250
- React15.createElement(
3251
- React15.Fragment,
3252
- { key: i },
3253
- renderItem({ index: i, selected: i === selectedIndex, focused: isFocused })
3254
- )
3255
- );
3256
- }
3257
- return React15.createElement(
3258
- "box",
3259
- {
3260
- style: { flexDirection: "column", ...style },
3261
- focusable,
3262
- ref: (node) => {
3263
- if (node) {
3264
- nodeRef.current = node;
3265
- focusIdRef.current = node.focusId;
3266
- setNodeReady(true);
3267
- } else {
3268
- nodeRef.current = null;
3269
- focusIdRef.current = null;
3270
- setNodeReady(false);
3271
- }
3272
- }
3273
- },
3274
- ...items
3275
- );
3276
- }
3277
- function Menu({
3278
- items,
3279
- selectedIndex,
3280
- onSelectionChange,
3281
- onSelect,
3282
- defaultSelectedIndex = 0,
3283
- style,
3284
- highlightColor = "cyan",
3285
- focusable = true
3286
- }) {
3287
- const disabledIndices = /* @__PURE__ */ new Set();
3288
- for (let i = 0; i < items.length; i++) {
3289
- if (items[i].disabled) disabledIndices.add(i);
3290
- }
3291
- const handleSelect = (index) => {
3292
- const item = items[index];
3293
- if (item && !item.disabled) {
3294
- onSelect?.(item.value, index);
3295
- }
3296
- };
3297
- return React15.createElement(List, {
3298
- count: items.length,
3299
- selectedIndex,
3300
- onSelectionChange,
3301
- onSelect: handleSelect,
3302
- defaultSelectedIndex,
3303
- disabledIndices: disabledIndices.size > 0 ? disabledIndices : void 0,
3304
- style,
3305
- focusable,
3306
- renderItem: ({ index, selected, focused }) => {
3307
- const item = items[index];
3308
- const isDisabled = item.disabled;
3309
- const isHighlighted = selected && focused;
3310
- const indicator = selected ? ">" : " ";
3311
- return React15.createElement(
3312
- "box",
3313
- {
3314
- style: {
3315
- flexDirection: "row",
3316
- ...isHighlighted ? { bg: highlightColor } : {}
3317
- }
3318
- },
3319
- React15.createElement(
3320
- "text",
3321
- {
3322
- style: isHighlighted ? { bold: true, color: "black" } : isDisabled ? { dim: true } : {}
3323
- },
3324
- `${indicator} ${item.label}`
3325
- )
3326
- );
3327
- }
3328
- });
3329
- }
3330
- function Progress({
3331
- value,
3332
- indeterminate = false,
3333
- width = "100%",
3334
- label,
3335
- showPercent = false,
3336
- style,
3337
- filled = "\u2588",
3338
- empty = "\u2591"
3339
- }) {
3340
- const trackRef = useRef(null);
3341
- const trackLayout = useLayout(trackRef);
3342
- const trackWidth = trackLayout.innerWidth;
3343
- const [indeterminatePos, setIndeterminatePos] = useState(0);
3344
- useEffect(() => {
3345
- if (!indeterminate) return;
3346
- const timer = setInterval(() => {
3347
- setIndeterminatePos((p) => (p + 1) % Math.max(1, trackWidth + 6));
3348
- }, 100);
3349
- return () => clearInterval(timer);
3350
- }, [indeterminate, trackWidth]);
3351
- const clamped = Math.max(0, Math.min(1, value ?? 0));
3352
- const pctText = showPercent ? ` ${Math.round(clamped * 100)}%` : "";
3353
- let barText = "";
3354
- if (trackWidth > 0) {
3355
- if (indeterminate && value === void 0) {
3356
- const chunkSize = Math.max(1, Math.min(3, Math.floor(trackWidth / 4)));
3357
- const chars = [];
3358
- for (let i = 0; i < trackWidth; i++) {
3359
- if (i >= indeterminatePos - chunkSize && i < indeterminatePos) {
3360
- chars.push(filled);
3361
- } else {
3362
- chars.push(empty);
3363
- }
3364
- }
3365
- barText = chars.join("");
3366
- } else {
3367
- const filledCount = Math.round(clamped * trackWidth);
3368
- barText = filled.repeat(filledCount) + empty.repeat(trackWidth - filledCount);
3369
- }
3370
- }
3371
- const children = [];
3372
- if (label) {
3373
- children.push(
3374
- React15.createElement("text", { key: "label", style: { bold: true } }, label + " ")
3375
- );
3376
- }
3377
- children.push(
3378
- React15.createElement(
3379
- "box",
3380
- {
3381
- key: "track",
3382
- style: { flexGrow: 1, flexShrink: 1 },
3383
- ref: (node) => {
3384
- trackRef.current = node ?? null;
3385
- }
3386
- },
3387
- React15.createElement("text", { key: "bar", style: {} }, barText)
3388
- )
3389
- );
3390
- if (showPercent) {
3391
- children.push(
3392
- React15.createElement("text", { key: "pct", style: { bold: true } }, pctText)
3393
- );
3394
- }
3395
- return React15.createElement(
3396
- "box",
3397
- {
3398
- style: {
3399
- flexDirection: "row",
3400
- width,
3401
- ...style
3402
- }
3403
- },
3404
- ...children
3405
- );
3406
- }
3407
- var BRAILLE_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3408
- function Spinner({
3409
- frames = BRAILLE_FRAMES,
3410
- intervalMs = 80,
3411
- label,
3412
- style
3413
- }) {
3414
- const [frameIndex, setFrameIndex] = useState(0);
3415
- useEffect(() => {
3416
- const timer = setInterval(() => {
3417
- setFrameIndex((i) => (i + 1) % frames.length);
3418
- }, intervalMs);
3419
- return () => clearInterval(timer);
3420
- }, [frames.length, intervalMs]);
3421
- const children = [
3422
- React15.createElement("text", { key: "frame", style }, frames[frameIndex])
3423
- ];
3424
- if (label) {
3425
- children.push(
3426
- React15.createElement("text", { key: "label", style: {} }, " " + label)
3427
- );
3428
- }
3429
- return React15.createElement(
3430
- "box",
3431
- { style: { flexDirection: "row" } },
3432
- ...children
3433
- );
3434
- }
3435
- var ToastContext = createContext(null);
3436
- var nextToastId = 0;
3437
- function useToast() {
3438
- const ctx = useContext(ToastContext);
3439
- if (!ctx) throw new Error("useToast must be used within a <ToastHost>");
3440
- return ctx.push;
3441
- }
3442
- var VARIANT_COLORS = {
3443
- info: { bg: "blackBright", title: "cyanBright", text: "white" },
3444
- success: { bg: "blackBright", title: "greenBright", text: "white" },
3445
- warning: { bg: "blackBright", title: "yellowBright", text: "white" },
3446
- error: { bg: "blackBright", title: "redBright", text: "white" }
3447
- };
3448
- function ToastHost({
3449
- position = "bottom-right",
3450
- maxVisible = 5,
3451
- children
3452
- }) {
3453
- const [toasts, setToasts] = useState([]);
3454
- const timersRef = useRef(/* @__PURE__ */ new Map());
3455
- const push = useCallback((toast) => {
3456
- const id = `toast-${nextToastId++}`;
3457
- const full = { id, durationMs: 3e3, variant: "info", ...toast };
3458
- setToasts((prev) => [...prev, full]);
3459
- if (full.durationMs && full.durationMs > 0) {
3460
- const timer = setTimeout(() => {
3461
- timersRef.current.delete(id);
3462
- setToasts((prev) => prev.filter((t) => t.id !== id));
3463
- }, full.durationMs);
3464
- timersRef.current.set(id, timer);
3465
- }
3466
- }, []);
3467
- useEffect(() => {
3468
- return () => {
3469
- for (const timer of timersRef.current.values()) {
3470
- clearTimeout(timer);
3471
- }
3472
- timersRef.current.clear();
3473
- };
3474
- }, []);
3475
- const ctxValue = useRef({ push });
3476
- ctxValue.current.push = push;
3477
- const isTop = position.startsWith("top");
3478
- const isRight = position.endsWith("right");
3479
- const portalStyle = {
3480
- position: "absolute",
3481
- top: 0,
3482
- left: 0,
3483
- width: "100%",
3484
- height: "100%",
3485
- zIndex: 900,
3486
- flexDirection: "column",
3487
- justifyContent: isTop ? "flex-start" : "flex-end",
3488
- alignItems: isRight ? "flex-end" : "flex-start",
3489
- padding: 1
3490
- };
3491
- const visible = toasts.slice(-maxVisible);
3492
- const toastElements = visible.map((toast) => {
3493
- const variant = toast.variant ?? "info";
3494
- const colors = VARIANT_COLORS[variant];
3495
- const innerChildren = [];
3496
- if (toast.title) {
3497
- innerChildren.push(
3498
- React15.createElement("text", {
3499
- key: "title",
3500
- style: { bold: true, color: colors.title }
3501
- }, toast.title)
3502
- );
3503
- }
3504
- innerChildren.push(
3505
- React15.createElement("text", {
3506
- key: "msg",
3507
- style: { color: colors.text }
3508
- }, toast.message)
3509
- );
3510
- return React15.createElement(
3511
- "box",
3512
- {
3513
- key: toast.id,
3514
- style: {
3515
- bg: colors.bg,
3516
- paddingX: 1,
3517
- flexDirection: "column",
3518
- minWidth: 20,
3519
- maxWidth: 50
3520
- }
3521
- },
3522
- ...innerChildren
3523
- );
3524
- });
3525
- return React15.createElement(
3526
- ToastContext.Provider,
3527
- { value: ctxValue.current },
3528
- children,
3529
- toastElements.length > 0 ? React15.createElement("box", { style: portalStyle }, ...toastElements) : null
3530
- );
3531
- }
3532
- function Select({
3533
- items,
3534
- value,
3535
- onChange,
3536
- placeholder = "Select...",
3537
- style,
3538
- focusedStyle,
3539
- dropdownStyle,
3540
- highlightColor = "cyan",
3541
- maxVisible = 8,
3542
- searchable = true,
3543
- disabled
3544
- }) {
3545
- const focusCtx = useContext(FocusContext);
3546
- const inputCtx = useContext(InputContext);
3547
- const appCtx = useContext(AppContext);
3548
- const nodeRef = useRef(null);
3549
- const focusIdRef = useRef(null);
3550
- const onChangeRef = useRef(onChange);
3551
- onChangeRef.current = onChange;
3552
- const [nodeReady, setNodeReady] = useState(false);
3553
- const [isFocused, setIsFocused] = useState(false);
3554
- const [isOpen, setIsOpen] = useState(false);
3555
- const [highlightIndex, setHighlightIndex] = useState(0);
3556
- const [searchText, setSearchText] = useState("");
3557
- const [scrollOffset, setScrollOffset] = useState(0);
3558
- const triggerLayout = useLayout(nodeRef);
3559
- const screenRows = appCtx?.rows ?? 24;
3560
- const selectedItem = items.find((item) => item.value === value);
3561
- const selectedLabel = selectedItem?.label ?? "";
3562
- const filteredItems = useMemo(() => {
3563
- if (!searchText) return items;
3564
- const lower = searchText.toLowerCase();
3565
- return items.filter((item) => item.label.toLowerCase().includes(lower));
3566
- }, [items, searchText]);
3567
- const visibleCount = Math.min(maxVisible, filteredItems.length);
3568
- const visibleItems = filteredItems.slice(
3569
- scrollOffset,
3570
- scrollOffset + visibleCount
3571
- );
3572
- useEffect(() => {
3573
- setHighlightIndex(0);
3574
- setScrollOffset(0);
3575
- }, [searchText]);
3576
- useEffect(() => {
3577
- if (!isFocused && isOpen) {
3578
- setIsOpen(false);
3579
- setSearchText("");
3580
- }
3581
- }, [isFocused, isOpen]);
3582
- useEffect(() => {
3583
- if (!focusCtx || !focusIdRef.current || !nodeRef.current) return;
3584
- return focusCtx.register(focusIdRef.current, nodeRef.current);
3585
- }, [focusCtx, nodeReady]);
3586
- useEffect(() => {
3587
- if (!focusCtx || !focusIdRef.current) return;
3588
- focusCtx.setSkippable(focusIdRef.current, !!disabled);
3589
- }, [focusCtx, disabled, nodeReady]);
3590
- useEffect(() => {
3591
- if (!focusCtx || !focusIdRef.current) return;
3592
- const fid = focusIdRef.current;
3593
- setIsFocused(focusCtx.focusedId === fid);
3594
- return focusCtx.onFocusChange((newId) => {
3595
- setIsFocused(newId === fid);
3596
- });
3597
- }, [focusCtx, nodeReady]);
3598
- const findNextEnabled = useCallback(
3599
- (from, direction) => {
3600
- let next = from + direction;
3601
- while (next >= 0 && next < filteredItems.length) {
3602
- if (!filteredItems[next].disabled) return next;
3603
- next += direction;
3604
- }
3605
- return from;
3606
- },
3607
- [filteredItems]
3608
- );
3609
- const ensureVisible = useCallback(
3610
- (index) => {
3611
- if (index < scrollOffset) {
3612
- setScrollOffset(index);
3613
- } else if (index >= scrollOffset + visibleCount) {
3614
- setScrollOffset(index - visibleCount + 1);
3615
- }
3616
- },
3617
- [scrollOffset, visibleCount]
3618
- );
3619
- useEffect(() => {
3620
- if (!inputCtx || !focusIdRef.current || disabled) return;
3621
- const fid = focusIdRef.current;
3622
- const handler = (key) => {
3623
- if (focusCtx?.focusedId !== fid) return false;
3624
- if (!isOpen) {
3625
- if (key.name === "return" || key.name === " " || key.sequence === " " || key.name === "down") {
3626
- setIsOpen(true);
3627
- setSearchText("");
3628
- const idx = filteredItems.findIndex((item) => item.value === value);
3629
- const start = idx >= 0 ? idx : 0;
3630
- setHighlightIndex(start);
3631
- setScrollOffset(
3632
- Math.max(0, start - Math.floor(maxVisible / 2))
3633
- );
3634
- return true;
3635
- }
3636
- return false;
3637
- }
3638
- if (key.name === "tab") {
3639
- setIsOpen(false);
3640
- setSearchText("");
3641
- return false;
3642
- }
3643
- if (key.name === "escape") {
3644
- setIsOpen(false);
3645
- setSearchText("");
3646
- return true;
3647
- }
3648
- if (key.name === "return") {
3649
- const item = filteredItems[highlightIndex];
3650
- if (item && !item.disabled) {
3651
- onChangeRef.current?.(item.value);
3652
- setIsOpen(false);
3653
- setSearchText("");
3654
- }
3655
- return true;
3656
- }
3657
- if (key.name === "up") {
3658
- const next = findNextEnabled(highlightIndex, -1);
3659
- setHighlightIndex(next);
3660
- ensureVisible(next);
3661
- return true;
3662
- }
3663
- if (key.name === "down") {
3664
- const next = findNextEnabled(highlightIndex, 1);
3665
- setHighlightIndex(next);
3666
- ensureVisible(next);
3667
- return true;
3668
- }
3669
- if (key.name === "backspace") {
3670
- if (searchable && searchText.length > 0) {
3671
- setSearchText((prev) => prev.slice(0, -1));
3672
- }
3673
- return true;
3674
- }
3675
- if (key.name === "home") {
3676
- const first = findNextEnabled(-1, 1);
3677
- setHighlightIndex(first);
3678
- ensureVisible(first);
3679
- return true;
3680
- }
3681
- if (key.name === "end") {
3682
- const last = findNextEnabled(filteredItems.length, -1);
3683
- setHighlightIndex(last);
3684
- ensureVisible(last);
3685
- return true;
3686
- }
3687
- if (searchable && key.sequence && key.sequence.length === 1 && !key.ctrl && !key.alt) {
3688
- const ch = key.sequence;
3689
- if (ch >= " " && ch <= "~") {
3690
- setSearchText((prev) => prev + ch);
3691
- return true;
3692
- }
3693
- }
3694
- return true;
3695
- };
3696
- return inputCtx.registerInputHandler(fid, handler);
3697
- }, [
3698
- inputCtx,
3699
- focusCtx,
3700
- disabled,
3701
- isOpen,
3702
- highlightIndex,
3703
- filteredItems,
3704
- value,
3705
- maxVisible,
3706
- searchable,
3707
- searchText,
3708
- findNextEnabled,
3709
- ensureVisible,
3710
- nodeReady
3711
- ]);
3712
- const useDefaultBorder = !style?.bg && style?.border === void 0;
3713
- const triggerStyle = {
3714
- flexDirection: "row",
3715
- width: "100%",
3716
- ...useDefaultBorder ? { border: "single" } : {},
3717
- ...style,
3718
- ...isFocused && focusedStyle ? focusedStyle : {}
3719
- };
3720
- const labelColor = selectedLabel ? style?.color ?? void 0 : "blackBright";
3721
- const triggerChildren = [
3722
- React15.createElement(
3723
- "text",
3724
- {
3725
- key: "label",
3726
- style: {
3727
- flexGrow: 1,
3728
- flexShrink: 1,
3729
- color: labelColor,
3730
- wrap: "ellipsis",
3731
- ...selectedLabel ? {} : { dim: true }
3732
- }
3733
- },
3734
- selectedLabel || placeholder
3735
- ),
3736
- React15.createElement(
3737
- "text",
3738
- {
3739
- key: "arrow",
3740
- style: { flexShrink: 0, color: isFocused ? highlightColor : "blackBright" }
3741
- },
3742
- isOpen ? " \u25B2" : " \u25BC"
3743
- )
3744
- ];
3745
- let dropdownElement = null;
3746
- if (isOpen) {
3747
- const dropdownChildren = [];
3748
- if (searchable && searchText) {
3749
- dropdownChildren.push(
3750
- React15.createElement(
3751
- "box",
3752
- { key: "search", style: { paddingX: 1 } },
3753
- React15.createElement(
3754
- "text",
3755
- { style: { color: "blackBright", dim: true } },
3756
- `/${searchText}`
3757
- )
3758
- )
3759
- );
3760
- }
3761
- if (filteredItems.length === 0) {
3762
- dropdownChildren.push(
3763
- React15.createElement(
3764
- "box",
3765
- { key: "empty", style: { paddingX: 1 } },
3766
- React15.createElement(
3767
- "text",
3768
- { style: { dim: true, color: "blackBright" } },
3769
- "No matches"
3770
- )
3771
- )
3772
- );
3773
- }
3774
- if (scrollOffset > 0) {
3775
- dropdownChildren.push(
3776
- React15.createElement(
3777
- "box",
3778
- {
3779
- key: "scroll-up",
3780
- style: { justifyContent: "center", alignItems: "center" }
3781
- },
3782
- React15.createElement(
3783
- "text",
3784
- { style: { dim: true, color: "blackBright" } },
3785
- "\u25B2"
3786
- )
3787
- )
3788
- );
3789
- }
3790
- visibleItems.forEach((item, vi) => {
3791
- const actualIndex = scrollOffset + vi;
3792
- const isHighlighted = actualIndex === highlightIndex;
3793
- const isDisabled = item.disabled;
3794
- const itemStyle = {
3795
- paddingX: 1,
3796
- ...isHighlighted && !isDisabled ? { bg: highlightColor } : {}
3797
- };
3798
- const textStyle = {
3799
- ...isHighlighted && !isDisabled ? { color: "black", bold: true } : {},
3800
- ...isDisabled ? { dim: true, color: "blackBright" } : {}
3801
- };
3802
- dropdownChildren.push(
3803
- React15.createElement(
3804
- "box",
3805
- { key: `item-${item.value}`, style: itemStyle },
3806
- React15.createElement(
3807
- "text",
3808
- { style: textStyle },
3809
- item.label
3810
- )
3811
- )
3812
- );
3813
- });
3814
- if (scrollOffset + visibleCount < filteredItems.length) {
3815
- dropdownChildren.push(
3816
- React15.createElement(
3817
- "box",
3818
- {
3819
- key: "scroll-down",
3820
- style: { justifyContent: "center", alignItems: "center" }
3821
- },
3822
- React15.createElement(
3823
- "text",
3824
- { style: { dim: true, color: "blackBright" } },
3825
- "\u25BC"
3826
- )
3827
- )
3828
- );
3829
- }
3830
- const hasScrollUp = scrollOffset > 0;
3831
- const hasScrollDown = scrollOffset + visibleCount < filteredItems.length;
3832
- const hasSearch = searchable && searchText;
3833
- const hasNoMatches = filteredItems.length === 0;
3834
- const useDropdownBorder = !dropdownStyle?.bg && dropdownStyle?.border === void 0;
3835
- const borderSize = useDropdownBorder ? 2 : 0;
3836
- let dropdownHeight = visibleCount + borderSize;
3837
- if (hasScrollUp) dropdownHeight += 1;
3838
- if (hasScrollDown) dropdownHeight += 1;
3839
- if (hasSearch) dropdownHeight += 1;
3840
- if (hasNoMatches) dropdownHeight += 1;
3841
- const triggerBottom = triggerLayout.y + triggerLayout.height;
3842
- const spaceBelow = screenRows - triggerBottom;
3843
- const spaceAbove = triggerLayout.y;
3844
- const openUpward = spaceBelow < dropdownHeight && spaceAbove >= dropdownHeight;
3845
- const dropdownTop = openUpward ? -dropdownHeight : triggerLayout.height || 1;
3846
- dropdownElement = React15.createElement(
3847
- "box",
3848
- {
3849
- style: {
3850
- position: "absolute",
3851
- top: dropdownTop,
3852
- left: 0,
3853
- right: 0,
3854
- zIndex: 9999,
3855
- ...useDropdownBorder ? { border: "single" } : {},
3856
- bg: "black",
3857
- flexDirection: "column",
3858
- ...dropdownStyle
3859
- }
3860
- },
3861
- ...dropdownChildren
3862
- );
3863
- }
3864
- const outerStyle = {
3865
- flexDirection: "column",
3866
- width: triggerStyle.width ?? "100%",
3867
- minWidth: triggerStyle.minWidth,
3868
- maxWidth: triggerStyle.maxWidth,
3869
- flexGrow: triggerStyle.flexGrow,
3870
- flexShrink: triggerStyle.flexShrink ?? 1
3871
- };
3872
- return React15.createElement(
3873
- "box",
3874
- { style: outerStyle },
3875
- // Trigger
3876
- React15.createElement(
3877
- "box",
3878
- {
3879
- style: triggerStyle,
3880
- // Always focusable - disabled state is handled in input handler
3881
- // This ensures focusId is assigned on mount, even if initially disabled
3882
- focusable: true,
3883
- ref: (node) => {
3884
- if (node) {
3885
- nodeRef.current = node;
3886
- focusIdRef.current = node.focusId;
3887
- setNodeReady(true);
3888
- } else {
3889
- nodeRef.current = null;
3890
- focusIdRef.current = null;
3891
- setNodeReady(false);
3892
- }
3893
- }
3894
- },
3895
- ...triggerChildren
3896
- ),
3897
- // Dropdown overlay
3898
- dropdownElement
3899
- );
3900
- }
3901
- function Checkbox({
3902
- checked,
3903
- onChange,
3904
- label,
3905
- style,
3906
- focusedStyle,
3907
- disabled,
3908
- checkedChar = "\u2713",
3909
- uncheckedChar = " "
3910
- }) {
3911
- const focusCtx = useContext(FocusContext);
3912
- const inputCtx = useContext(InputContext);
3913
- const nodeRef = useRef(null);
3914
- const focusIdRef = useRef(null);
3915
- const onChangeRef = useRef(onChange);
3916
- onChangeRef.current = onChange;
3917
- const checkedRef = useRef(checked);
3918
- checkedRef.current = checked;
3919
- const [nodeReady, setNodeReady] = useState(false);
3920
- const [isFocused, setIsFocused] = useState(false);
3921
- useEffect(() => {
3922
- if (!focusCtx || !focusIdRef.current || !nodeRef.current || disabled) return;
3923
- return focusCtx.register(focusIdRef.current, nodeRef.current);
3924
- }, [focusCtx, disabled, nodeReady]);
3925
- useEffect(() => {
3926
- if (!focusCtx || !focusIdRef.current) return;
3927
- const fid = focusIdRef.current;
3928
- setIsFocused(focusCtx.focusedId === fid);
3929
- return focusCtx.onFocusChange((newId) => {
3930
- setIsFocused(newId === fid);
3931
- });
3932
- }, [focusCtx, nodeReady]);
3933
- useEffect(() => {
3934
- if (!inputCtx || !focusIdRef.current || disabled) return;
3935
- const fid = focusIdRef.current;
3936
- const handler = (key) => {
3937
- if (focusCtx?.focusedId !== fid) return false;
3938
- if (key.name === "return" || key.name === " " || key.sequence === " ") {
3939
- onChangeRef.current(!checkedRef.current);
3940
- return true;
3941
- }
3942
- return false;
3943
- };
3944
- return inputCtx.registerInputHandler(fid, handler);
3945
- }, [inputCtx, focusCtx, disabled, nodeReady]);
3946
- const mergedStyle = {
3947
- flexDirection: "row",
3948
- gap: 1,
3949
- ...style,
3950
- ...isFocused && focusedStyle ? focusedStyle : {}
3951
- };
3952
- const boxChar = checked ? checkedChar : uncheckedChar;
3953
- const boxStyle = {
3954
- color: disabled ? "blackBright" : isFocused ? "white" : style?.color
3955
- };
3956
- const labelStyle = {
3957
- color: disabled ? "blackBright" : style?.color
3958
- };
3959
- return React15.createElement(
3960
- "box",
3961
- {
3962
- style: mergedStyle,
3963
- focusable: !disabled,
3964
- ref: (node) => {
3965
- if (node) {
3966
- nodeRef.current = node;
3967
- focusIdRef.current = node.focusId;
3968
- setNodeReady(true);
3969
- } else {
3970
- nodeRef.current = null;
3971
- focusIdRef.current = null;
3972
- setNodeReady(false);
3973
- }
3974
- }
3975
- },
3976
- React15.createElement(
3977
- "text",
3978
- { key: "box", style: boxStyle },
3979
- `[${boxChar}]`
3980
- ),
3981
- label ? React15.createElement(
3982
- "text",
3983
- { key: "label", style: labelStyle },
3984
- label
3985
- ) : null
3986
- );
3987
- }
3988
- function Radio({
3989
- items,
3990
- value,
3991
- onChange,
3992
- style,
3993
- itemStyle,
3994
- focusedItemStyle,
3995
- selectedItemStyle,
3996
- disabled,
3997
- direction = "column",
3998
- gap = 0,
3999
- selectedChar = "\u25CF",
4000
- unselectedChar = "\u25CB"
4001
- }) {
4002
- const focusCtx = useContext(FocusContext);
4003
- const inputCtx = useContext(InputContext);
4004
- const nodeRef = useRef(null);
4005
- const focusIdRef = useRef(null);
4006
- const onChangeRef = useRef(onChange);
4007
- onChangeRef.current = onChange;
4008
- const [nodeReady, setNodeReady] = useState(false);
4009
- const [isFocused, setIsFocused] = useState(false);
4010
- const [highlightedIndex, setHighlightedIndex] = useState(() => {
4011
- const selectedIdx = items.findIndex((item) => item.value === value);
4012
- if (selectedIdx >= 0) return selectedIdx;
4013
- return items.findIndex((item) => !item.disabled);
4014
- });
4015
- const findNextEnabled = useCallback(
4016
- (startIndex, direction2) => {
4017
- let index = startIndex;
4018
- for (let i = 0; i < items.length; i++) {
4019
- index = (index + direction2 + items.length) % items.length;
4020
- if (!items[index]?.disabled) return index;
4021
- }
4022
- return startIndex;
4023
- },
4024
- [items]
4025
- );
4026
- useEffect(() => {
4027
- if (!focusCtx || !focusIdRef.current || !nodeRef.current || disabled) return;
4028
- return focusCtx.register(focusIdRef.current, nodeRef.current);
4029
- }, [focusCtx, disabled, nodeReady]);
4030
- useEffect(() => {
4031
- if (!focusCtx || !focusIdRef.current) return;
4032
- const fid = focusIdRef.current;
4033
- setIsFocused(focusCtx.focusedId === fid);
4034
- return focusCtx.onFocusChange((newId) => {
4035
- setIsFocused(newId === fid);
4036
- });
4037
- }, [focusCtx, nodeReady]);
4038
- useEffect(() => {
4039
- if (!inputCtx || !focusIdRef.current || disabled) return;
4040
- const fid = focusIdRef.current;
4041
- const handler = (key) => {
4042
- if (focusCtx?.focusedId !== fid) return false;
4043
- if (key.name === "up" || key.name === "left" || key.name === "k" || key.name === "tab" && key.shift) {
4044
- setHighlightedIndex((idx) => findNextEnabled(idx, -1));
4045
- return true;
4046
- }
4047
- if (key.name === "down" || key.name === "right" || key.name === "j" || key.name === "tab" && !key.shift) {
4048
- setHighlightedIndex((idx) => findNextEnabled(idx, 1));
4049
- return true;
4050
- }
4051
- if (key.name === "return" || key.name === " " || key.sequence === " ") {
4052
- const item = items[highlightedIndex];
4053
- if (item && !item.disabled) {
4054
- onChangeRef.current(item.value);
4055
- }
4056
- return true;
4057
- }
4058
- return false;
4059
- };
4060
- return inputCtx.registerInputHandler(fid, handler);
4061
- }, [inputCtx, focusCtx, disabled, items, highlightedIndex, findNextEnabled, nodeReady]);
4062
- useEffect(() => {
4063
- const selectedIdx = items.findIndex((item) => item.value === value);
4064
- if (selectedIdx >= 0) {
4065
- setHighlightedIndex(selectedIdx);
4066
- }
4067
- }, [value, items]);
4068
- const containerStyle = {
4069
- flexDirection: direction,
4070
- gap,
4071
- ...style
4072
- };
4073
- const radioItems = items.map((item, index) => {
4074
- const isSelected = item.value === value;
4075
- const isHighlighted = index === highlightedIndex;
4076
- const isItemDisabled = disabled || item.disabled;
4077
- const radioChar = isSelected ? selectedChar : unselectedChar;
4078
- let computedStyle = {
4079
- flexDirection: "row",
4080
- gap: 1,
4081
- ...itemStyle
4082
- };
4083
- if (isSelected && selectedItemStyle) {
4084
- computedStyle = { ...computedStyle, ...selectedItemStyle };
4085
- }
4086
- if (isFocused && isHighlighted && focusedItemStyle) {
4087
- computedStyle = { ...computedStyle, ...focusedItemStyle };
4088
- }
4089
- const textColor = isItemDisabled ? "blackBright" : isFocused && isHighlighted ? focusedItemStyle?.color ?? "white" : isSelected ? selectedItemStyle?.color ?? itemStyle?.color : itemStyle?.color;
4090
- return React15.createElement(
4091
- "box",
4092
- { key: index, style: computedStyle },
4093
- React15.createElement(
4094
- "text",
4095
- { key: "radio", style: { color: textColor } },
4096
- `(${radioChar})`
4097
- ),
4098
- React15.createElement(
4099
- "text",
4100
- { key: "label", style: { color: textColor } },
4101
- item.label
4102
- )
4103
- );
4104
- });
4105
- return React15.createElement(
4106
- "box",
4107
- {
4108
- style: containerStyle,
4109
- focusable: !disabled,
4110
- ref: (node) => {
4111
- if (node) {
4112
- nodeRef.current = node;
4113
- focusIdRef.current = node.focusId;
4114
- setNodeReady(true);
4115
- } else {
4116
- nodeRef.current = null;
4117
- focusIdRef.current = null;
4118
- setNodeReady(false);
4119
- }
4120
- }
4121
- },
4122
- ...radioItems
4123
- );
4124
- }
4125
- var DialogContext = createContext(null);
4126
- function useDialog() {
4127
- const ctx = useContext(DialogContext);
4128
- if (!ctx) {
4129
- throw new Error("useDialog must be used within a DialogHost");
4130
- }
4131
- return ctx;
4132
- }
4133
- function DialogHost({ children }) {
4134
- const [dialogs, setDialogs] = useState([]);
4135
- const idCounter = useRef(0);
4136
- const alert = useCallback((content, options) => {
4137
- return new Promise((resolve) => {
4138
- const id = ++idCounter.current;
4139
- setDialogs((prev) => [
4140
- ...prev,
4141
- {
4142
- id,
4143
- type: "alert",
4144
- content,
4145
- okText: options?.okText ?? "OK",
4146
- cancelText: "",
4147
- style: options?.style,
4148
- resolve: () => resolve()
4149
- }
4150
- ]);
4151
- });
4152
- }, []);
4153
- const confirm = useCallback((content, options) => {
4154
- return new Promise((resolve) => {
4155
- const id = ++idCounter.current;
4156
- setDialogs((prev) => [
4157
- ...prev,
4158
- {
4159
- id,
4160
- type: "confirm",
4161
- content,
4162
- okText: options?.okText ?? "OK",
4163
- cancelText: options?.cancelText ?? "Cancel",
4164
- style: options?.style,
4165
- resolve
4166
- }
4167
- ]);
4168
- });
4169
- }, []);
4170
- const dismissDialog = useCallback((id, result) => {
4171
- setDialogs((prev) => {
4172
- const dialog = prev.find((d) => d.id === id);
4173
- if (dialog) {
4174
- dialog.resolve(result);
4175
- }
4176
- return prev.filter((d) => d.id !== id);
4177
- });
4178
- }, []);
4179
- const contextValue = { alert, confirm };
4180
- const activeDialog = dialogs[dialogs.length - 1];
4181
- return React15.createElement(
4182
- DialogContext.Provider,
4183
- { value: contextValue },
4184
- children,
4185
- activeDialog && React15.createElement(DialogOverlay, {
4186
- key: activeDialog.id,
4187
- dialog: activeDialog,
4188
- onDismiss: dismissDialog
4189
- })
4190
- );
4191
- }
4192
- function DialogOverlay({ dialog, onDismiss }) {
4193
- const focusCtx = useContext(FocusContext);
4194
- const okButtonRef = useRef(null);
4195
- const cancelButtonRef = useRef(null);
4196
- const okFocusIdRef = useRef(null);
4197
- const cancelFocusIdRef = useRef(null);
4198
- const [focusedButton, setFocusedButton] = useState("ok");
4199
- const [refsReady, setRefsReady] = useState(0);
4200
- useEffect(() => {
4201
- if (!focusCtx || refsReady === 0) return;
4202
- const cleanups = [];
4203
- if (okButtonRef.current && okFocusIdRef.current) {
4204
- cleanups.push(focusCtx.register(okFocusIdRef.current, okButtonRef.current));
4205
- }
4206
- if (cancelButtonRef.current && cancelFocusIdRef.current) {
4207
- cleanups.push(focusCtx.register(cancelFocusIdRef.current, cancelButtonRef.current));
4208
- }
4209
- if (okFocusIdRef.current) {
4210
- focusCtx.requestFocus(okFocusIdRef.current);
4211
- }
4212
- return () => cleanups.forEach((fn) => fn());
4213
- }, [focusCtx, refsReady]);
4214
- useEffect(() => {
4215
- if (!focusCtx) return;
4216
- return focusCtx.onFocusChange((id) => {
4217
- if (id === okFocusIdRef.current) {
4218
- setFocusedButton("ok");
4219
- } else if (id === cancelFocusIdRef.current) {
4220
- setFocusedButton("cancel");
4221
- }
4222
- });
4223
- }, [focusCtx]);
4224
- useInput((key) => {
4225
- if (key.name === "return" || key.name === "space") {
4226
- if (dialog.type === "alert") {
4227
- onDismiss(dialog.id, true);
4228
- } else {
4229
- onDismiss(dialog.id, focusedButton === "ok");
4230
- }
4231
- return;
4232
- }
4233
- if (key.name === "escape") {
4234
- onDismiss(dialog.id, false);
4235
- return;
4236
- }
4237
- if (dialog.type === "confirm" && focusCtx) {
4238
- if (key.name === "left" || key.name === "right") {
4239
- if (focusedButton === "ok" && cancelFocusIdRef.current) {
4240
- focusCtx.requestFocus(cancelFocusIdRef.current);
4241
- } else if (okFocusIdRef.current) {
4242
- focusCtx.requestFocus(okFocusIdRef.current);
4243
- }
4244
- }
4245
- }
4246
- }, [dialog, focusedButton, focusCtx, onDismiss]);
4247
- const contentIsString = typeof dialog.content === "string";
4248
- const contentLength = contentIsString ? dialog.content.length : 0;
4249
- const minWidth = Math.max(20, contentIsString ? Math.min(contentLength + 6, 50) : 30);
4250
- const boxStyle = {
4251
- minWidth,
4252
- maxWidth: 50,
4253
- bg: "black",
4254
- border: "round",
4255
- borderColor: "white",
4256
- padding: 1,
4257
- flexDirection: "column",
4258
- gap: 1,
4259
- ...dialog.style
4260
- };
4261
- const getButtonStyle = (isSelected) => ({
4262
- paddingX: 2,
4263
- bg: isSelected ? "white" : "blackBright",
4264
- color: isSelected ? "black" : "white",
4265
- bold: isSelected
4266
- });
4267
- return React15.createElement(
4268
- FocusScope,
4269
- { trap: true },
4270
- // Backdrop
4271
- React15.createElement("box", {
4272
- style: {
4273
- position: "absolute",
4274
- top: 0,
4275
- left: 0,
4276
- right: 0,
4277
- bottom: 0,
4278
- zIndex: 999
4279
- }
4280
- }),
4281
- // Centering wrapper
4282
- React15.createElement(
4283
- "box",
4284
- {
4285
- style: {
4286
- position: "absolute",
4287
- top: 0,
4288
- left: 0,
4289
- right: 0,
4290
- bottom: 0,
4291
- justifyContent: "center",
4292
- alignItems: "center",
4293
- zIndex: 1e3
4294
- }
4295
- },
4296
- // Dialog box
4297
- React15.createElement(
4298
- "box",
4299
- { style: boxStyle },
4300
- // Content
4301
- React15.createElement(
4302
- "box",
4303
- { style: { flexDirection: "column" } },
4304
- typeof dialog.content === "string" ? React15.createElement("text", null, dialog.content) : dialog.content
4305
- ),
4306
- // Buttons row
4307
- React15.createElement(
4308
- "box",
4309
- {
4310
- style: {
4311
- flexDirection: "row",
4312
- justifyContent: "flex-end",
4313
- gap: 1
4314
- }
4315
- },
4316
- // Cancel button (confirm only)
4317
- dialog.type === "confirm" && React15.createElement(
4318
- "box",
4319
- {
4320
- style: getButtonStyle(focusedButton === "cancel"),
4321
- focusable: true,
4322
- ref: (node) => {
4323
- if (node && node.focusId && !cancelFocusIdRef.current) {
4324
- cancelButtonRef.current = node;
4325
- cancelFocusIdRef.current = node.focusId;
4326
- setRefsReady((r) => r + 1);
4327
- }
4328
- }
4329
- },
4330
- React15.createElement("text", null, dialog.cancelText)
4331
- ),
4332
- // OK button
4333
- React15.createElement(
4334
- "box",
4335
- {
4336
- style: getButtonStyle(focusedButton === "ok"),
4337
- focusable: true,
4338
- ref: (node) => {
4339
- if (node && node.focusId && !okFocusIdRef.current) {
4340
- okButtonRef.current = node;
4341
- okFocusIdRef.current = node.focusId;
4342
- setRefsReady((r) => r + 1);
4343
- }
4344
- }
4345
- },
4346
- React15.createElement("text", null, dialog.okText)
4347
- )
4348
- )
4349
- )
4350
- )
4351
- );
4352
- }
4353
- function generateHints(count, chars) {
4354
- const hints = [];
4355
- const charList = chars.split("");
4356
- if (count <= charList.length) {
4357
- for (let i = 0; i < count; i++) {
4358
- hints.push(charList[i]);
4359
- }
4360
- } else {
4361
- for (let i = 0; i < charList.length && hints.length < count; i++) {
4362
- for (let j = 0; j < charList.length && hints.length < count; j++) {
4363
- hints.push(charList[i] + charList[j]);
4364
- }
4365
- }
4366
- }
4367
- return hints;
4368
- }
4369
- function JumpNav({
4370
- children,
4371
- activationKey = "ctrl+o",
4372
- hintStyle,
4373
- hintBg = "yellow",
4374
- hintFg = "black",
4375
- hintChars = "asdfghjklqwertyuiopzxcvbnm",
4376
- enabled = true,
4377
- debug = false
4378
- }) {
4379
- const log = debug ? (...args) => console.error("[JumpNav]", ...args) : () => {
4380
- };
4381
- const [isActive, setIsActive] = useState(false);
4382
- const [inputBuffer, setInputBuffer] = useState("");
4383
- const [elements, setElements] = useState([]);
4384
- const inputCtx = useContext(InputContext);
4385
- const focusCtx = useContext(FocusContext);
4386
- const layoutCtx = useContext(LayoutContext);
4387
- useEffect(() => {
4388
- log("Mounted, inputCtx:", !!inputCtx, "focusCtx:", !!focusCtx, "enabled:", enabled);
4389
- }, []);
4390
- const parseKey = useCallback((keyStr) => {
4391
- const parts = keyStr.toLowerCase().split("+");
4392
- return {
4393
- ctrl: parts.includes("ctrl"),
4394
- alt: parts.includes("alt"),
4395
- shift: parts.includes("shift"),
4396
- meta: parts.includes("meta"),
4397
- name: parts[parts.length - 1] ?? ""
4398
- };
4399
- }, []);
4400
- const activationKeyParsed = parseKey(activationKey);
4401
- const refreshElements = useCallback(() => {
4402
- if (!focusCtx?.getActiveElements) {
4403
- log("refreshElements: no getActiveElements");
4404
- return;
4405
- }
4406
- const active = focusCtx.getActiveElements();
4407
- log("getActiveElements returned", active.length, "elements");
4408
- const mapped = active.map(({ id, node }) => ({
4409
- id,
4410
- node,
4411
- layout: layoutCtx?.getLayout(node) ?? node.layout
4412
- }));
4413
- mapped.sort((a, b) => {
4414
- if (a.layout.y !== b.layout.y) {
4415
- return a.layout.y - b.layout.y;
4416
- }
4417
- return a.layout.x - b.layout.x;
4418
- });
4419
- setElements(mapped);
4420
- }, [focusCtx, layoutCtx, log]);
4421
- const wasActiveRef = useRef(false);
4422
- useEffect(() => {
4423
- if (isActive && !wasActiveRef.current) {
4424
- log("Activated! Refreshing elements...");
4425
- refreshElements();
4426
- }
4427
- wasActiveRef.current = isActive;
4428
- }, [isActive, refreshElements, log]);
4429
- const visibleElements = elements.filter(
4430
- (el) => el.layout.width > 0 && el.layout.height > 0
4431
- );
4432
- const visibleHints = generateHints(visibleElements.length, hintChars);
4433
- const visibleHintMap = useMemo(() => {
4434
- const map = /* @__PURE__ */ new Map();
4435
- visibleElements.forEach((el, i) => {
4436
- if (visibleHints[i]) {
4437
- map.set(visibleHints[i], el.id);
4438
- }
4439
- });
4440
- return map;
4441
- }, [visibleElements, visibleHints]);
4442
- useEffect(() => {
4443
- if (!inputCtx || !enabled) {
4444
- log("Not subscribing - inputCtx:", !!inputCtx, "enabled:", enabled);
4445
- return;
4446
- }
4447
- log("Subscribing to priority input, activation key:", activationKey);
4448
- const handler = (key) => {
4449
- const nameMatch = key.name === activationKeyParsed.name;
4450
- const ctrlMatch = !!key.ctrl === activationKeyParsed.ctrl;
4451
- const altMatch = !!key.alt === activationKeyParsed.alt;
4452
- const shiftMatch = !!key.shift === activationKeyParsed.shift;
4453
- const metaMatch = !!key.meta === activationKeyParsed.meta;
4454
- if (!isActive && nameMatch && ctrlMatch && altMatch && shiftMatch && metaMatch) {
4455
- log("Activation key matched! Activating...");
4456
- setIsActive(true);
4457
- setInputBuffer("");
4458
- return true;
4459
- }
4460
- if (isActive) {
4461
- if (key.name === "escape") {
4462
- log("Escape pressed, deactivating");
4463
- setIsActive(false);
4464
- setInputBuffer("");
4465
- return true;
4466
- }
4467
- if (key.name === "backspace") {
4468
- setInputBuffer("");
4469
- return true;
4470
- }
4471
- if (key.sequence && key.sequence.length === 1 && /[a-z]/i.test(key.sequence)) {
4472
- const newBuffer = inputBuffer + key.sequence.toLowerCase();
4473
- log("Buffer:", newBuffer);
4474
- const targetId = visibleHintMap.get(newBuffer);
4475
- if (targetId) {
4476
- log("Jumping to", targetId);
4477
- focusCtx?.requestFocus(targetId);
4478
- setIsActive(false);
4479
- setInputBuffer("");
4480
- return true;
4481
- }
4482
- const hasPartialMatch = [...visibleHintMap.keys()].some((h) => h.startsWith(newBuffer));
4483
- if (hasPartialMatch) {
4484
- setInputBuffer(newBuffer);
4485
- return true;
4486
- }
4487
- setInputBuffer("");
4488
- return true;
4489
- }
4490
- return true;
4491
- }
4492
- return false;
4493
- };
4494
- return inputCtx.subscribePriority(handler);
4495
- }, [inputCtx, enabled, isActive, activationKeyParsed, inputBuffer, visibleHintMap, focusCtx, activationKey, log]);
4496
- const hintsOverlay = isActive ? React15.createElement(
4497
- "box",
4498
- {
4499
- // Portal-like wrapper - fullscreen absolute overlay
4500
- style: {
4501
- position: "absolute",
4502
- top: 0,
4503
- left: 0,
4504
- width: "100%",
4505
- height: "100%",
4506
- zIndex: 99998
4507
- }
4508
- },
4509
- ...visibleElements.map((el, i) => {
4510
- const hint = visibleHints[i];
4511
- if (!hint) return null;
4512
- const { x, y } = el.layout;
4513
- const isPartialMatch = hint.startsWith(inputBuffer) && inputBuffer.length > 0;
4514
- return React15.createElement(
4515
- "box",
4516
- {
4517
- key: el.id,
4518
- style: {
4519
- position: "absolute",
4520
- top: y,
4521
- left: Math.max(0, x - hint.length - 2),
4522
- bg: isPartialMatch ? "cyan" : hintBg,
4523
- color: hintFg,
4524
- paddingX: 1,
4525
- zIndex: 99999,
4526
- ...hintStyle
4527
- }
4528
- },
4529
- React15.createElement("text", {
4530
- style: { bold: true, color: hintFg }
4531
- }, hint)
4532
- );
4533
- }),
4534
- // Status bar at bottom
4535
- React15.createElement(
4536
- "box",
4537
- {
4538
- style: {
4539
- position: "absolute",
4540
- bottom: 0,
4541
- left: 0,
4542
- right: 0,
4543
- bg: "blackBright",
4544
- paddingX: 1,
4545
- zIndex: 99999
4546
- }
4547
- },
4548
- React15.createElement("text", {
4549
- style: { color: "white" }
4550
- }, inputBuffer ? `Jump: ${inputBuffer}_` : "Press a key to jump \u2022 ESC to cancel")
4551
- )
4552
- ) : null;
4553
- return React15.createElement(
4554
- React15.Fragment,
4555
- null,
4556
- children,
4557
- hintsOverlay
4558
- );
4559
- }
4560
- function useFocus(nodeRef) {
4561
- const focusCtx = useContext(FocusContext);
4562
- const [id] = useState(() => `focus-${Math.random().toString(36).slice(2, 9)}`);
4563
- const isFocused = focusCtx ? focusCtx.focusedId === id : false;
4564
- useEffect(() => {
4565
- if (!focusCtx || !nodeRef?.current) return;
4566
- nodeRef.current.focusId = id;
4567
- return focusCtx.register(id, nodeRef.current);
4568
- }, [focusCtx, id, nodeRef]);
4569
- const focus = useMemo(() => {
4570
- return () => {
4571
- focusCtx?.requestFocus(id);
4572
- };
4573
- }, [focusCtx, id]);
4574
- return { focused: isFocused, focus };
4575
- }
4576
- function useFocusable(options = {}) {
4577
- const { disabled, onFocus, onBlur, onKeyPress } = options;
4578
- const focusCtx = useContext(FocusContext);
4579
- const inputCtx = useContext(InputContext);
4580
- const nodeRef = useRef(null);
4581
- const focusIdRef = useRef(null);
4582
- const [isFocused, setIsFocused] = useState(false);
4583
- const onFocusRef = useRef(onFocus);
4584
- const onBlurRef = useRef(onBlur);
4585
- const onKeyPressRef = useRef(onKeyPress);
4586
- onFocusRef.current = onFocus;
4587
- onBlurRef.current = onBlur;
4588
- onKeyPressRef.current = onKeyPress;
4589
- const ref = useCallback((node) => {
4590
- nodeRef.current = node;
4591
- if (node) {
4592
- focusIdRef.current = node.focusId ?? null;
4593
- } else {
4594
- focusIdRef.current = null;
4595
- }
4596
- }, []);
4597
- useEffect(() => {
4598
- if (!focusCtx || !focusIdRef.current || !nodeRef.current) return;
4599
- return focusCtx.register(focusIdRef.current, nodeRef.current);
4600
- }, [focusCtx]);
4601
- useEffect(() => {
4602
- if (!focusCtx || !focusIdRef.current) return;
4603
- focusCtx.setSkippable(focusIdRef.current, !!disabled);
4604
- }, [focusCtx, disabled]);
4605
- useEffect(() => {
4606
- if (!focusCtx || !focusIdRef.current) return;
4607
- const fid = focusIdRef.current;
4608
- const initiallyFocused = focusCtx.focusedId === fid;
4609
- setIsFocused(initiallyFocused);
4610
- return focusCtx.onFocusChange((newId) => {
4611
- const nowFocused = newId === fid;
4612
- setIsFocused((wasFocused) => {
4613
- if (nowFocused && !wasFocused) {
4614
- onFocusRef.current?.();
4615
- } else if (!nowFocused && wasFocused) {
4616
- onBlurRef.current?.();
4617
- }
4618
- return nowFocused;
4619
- });
4620
- });
4621
- }, [focusCtx]);
4622
- useEffect(() => {
4623
- if (!inputCtx || !focusIdRef.current || disabled) return;
4624
- const fid = focusIdRef.current;
4625
- const handler = (key) => {
4626
- if (focusCtx?.focusedId !== fid) return false;
4627
- return onKeyPressRef.current?.(key) === true;
4628
- };
4629
- return inputCtx.registerInputHandler(fid, handler);
4630
- }, [inputCtx, focusCtx, disabled]);
4631
- const focus = useCallback(() => {
4632
- if (focusCtx && focusIdRef.current) {
4633
- focusCtx.requestFocus(focusIdRef.current);
4634
- }
4635
- }, [focusCtx]);
4636
- return {
4637
- ref,
4638
- isFocused,
4639
- focus,
4640
- focusId: focusIdRef.current
4641
- };
4642
- }
4643
- function useApp() {
4644
- const ctx = useContext(AppContext);
4645
- if (!ctx) {
4646
- throw new Error("useApp must be used within a Glyph render tree");
4647
- }
4648
- return {
4649
- exit: ctx.exit,
4650
- get columns() {
4651
- return ctx.columns;
4652
- },
4653
- get rows() {
4654
- return ctx.rows;
4655
- }
4656
- };
4657
- }
4658
- function useFocusRegistry() {
4659
- const focusCtx = useContext(FocusContext);
4660
- const layoutCtx = useContext(LayoutContext);
4661
- const [elements, setElements] = useState([]);
4662
- const updateRef = useRef(() => {
4663
- });
4664
- const updateElements = useCallback(() => {
4665
- if (!focusCtx) return;
4666
- const registered = focusCtx.getActiveElements?.() ?? focusCtx.getRegisteredElements?.() ?? [];
4667
- const mapped = registered.map(({ id, node }) => ({
4668
- id,
4669
- node,
4670
- layout: layoutCtx?.getLayout(node) ?? node.layout,
4671
- type: node.type
4672
- }));
4673
- mapped.sort((a, b) => {
4674
- if (a.layout.y !== b.layout.y) {
4675
- return a.layout.y - b.layout.y;
4676
- }
4677
- return a.layout.x - b.layout.x;
4678
- });
4679
- setElements(mapped);
4680
- }, [focusCtx, layoutCtx]);
4681
- updateRef.current = updateElements;
4682
- useEffect(() => {
4683
- if (!focusCtx) return;
4684
- updateElements();
4685
- const unsubscribe = focusCtx.onFocusChange(() => {
4686
- updateElements();
4687
- });
4688
- const timer = setTimeout(updateElements, 50);
4689
- return () => {
4690
- unsubscribe();
4691
- clearTimeout(timer);
4692
- };
4693
- }, [focusCtx, layoutCtx, updateElements]);
4694
- if (!focusCtx) return null;
4695
- return {
4696
- elements,
4697
- focusedId: focusCtx.focusedId,
4698
- requestFocus: focusCtx.requestFocus,
4699
- focusNext: focusCtx.focusNext,
4700
- focusPrev: focusCtx.focusPrev,
4701
- refresh: () => updateRef.current()
4702
- };
4703
- }
4704
-
4705
- // src/utils/mask.ts
4706
- function parseMask(mask) {
4707
- const result = [];
4708
- for (const char of mask) {
4709
- switch (char) {
4710
- case "9":
4711
- result.push({ type: "digit", char });
4712
- break;
4713
- case "a":
4714
- result.push({ type: "letter", char });
4715
- break;
4716
- case "*":
4717
- result.push({ type: "alphanumeric", char });
4718
- break;
4719
- default:
4720
- result.push({ type: "literal", char });
4721
- break;
4722
- }
4723
- }
4724
- return result;
4725
- }
4726
- function isValidChar(char, type) {
4727
- switch (type) {
4728
- case "digit":
4729
- return /\d/.test(char);
4730
- case "letter":
4731
- return /[a-zA-Z]/.test(char);
4732
- case "alphanumeric":
4733
- return /[a-zA-Z0-9]/.test(char);
4734
- case "literal":
4735
- return true;
4736
- }
4737
- }
4738
- function createMask(maskOrOptions) {
4739
- const options = typeof maskOrOptions === "string" ? { mask: maskOrOptions } : maskOrOptions;
4740
- const { mask, placeholder = "_", showPlaceholder = false } = options;
4741
- const maskChars = parseMask(mask);
4742
- return (newValue, _oldValue) => {
4743
- const inputChars = [];
4744
- for (const char of newValue) {
4745
- if (char !== placeholder && !/[\s\-\(\)\/\.\:]/.test(char) || /[a-zA-Z0-9]/.test(char)) {
4746
- if (/[a-zA-Z0-9]/.test(char)) {
4747
- inputChars.push(char);
4748
- }
4749
- }
4750
- }
4751
- let result = "";
4752
- let inputIndex = 0;
4753
- for (const maskChar of maskChars) {
4754
- if (maskChar.type === "literal") {
4755
- if (inputIndex < inputChars.length || showPlaceholder) {
4756
- result += maskChar.char;
4757
- }
4758
- } else {
4759
- if (inputIndex < inputChars.length) {
4760
- const char = inputChars[inputIndex];
4761
- if (isValidChar(char, maskChar.type)) {
4762
- result += char;
4763
- inputIndex++;
4764
- } else {
4765
- inputIndex++;
4766
- continue;
4767
- }
4768
- } else if (showPlaceholder) {
4769
- result += placeholder;
4770
- }
4771
- }
4772
- }
4773
- return result;
4774
- };
4775
- }
4776
- var masks = {
4777
- /** US Phone: (123) 456-7890 */
4778
- usPhone: createMask("(999) 999-9999"),
4779
- /** International Phone: +1 234 567 8900 */
4780
- intlPhone: createMask("+9 999 999 9999"),
4781
- /** Date MM/DD/YYYY */
4782
- dateUS: createMask("99/99/9999"),
4783
- /** Date DD/MM/YYYY */
4784
- dateEU: createMask("99/99/9999"),
4785
- /** Date YYYY-MM-DD */
4786
- dateISO: createMask("9999-99-99"),
4787
- /** Time HH:MM */
4788
- time: createMask("99:99"),
4789
- /** Time HH:MM:SS */
4790
- timeFull: createMask("99:99:99"),
4791
- /** Credit Card: 1234 5678 9012 3456 */
4792
- creditCard: createMask("9999 9999 9999 9999"),
4793
- /** SSN: 123-45-6789 */
4794
- ssn: createMask("999-99-9999"),
4795
- /** ZIP Code: 12345 */
4796
- zip: createMask("99999"),
4797
- /** ZIP+4: 12345-6789 */
4798
- zipPlus4: createMask("99999-9999"),
4799
- /** IPv4: 192.168.001.001 */
4800
- ipv4: createMask("999.999.999.999"),
4801
- /** MAC Address: AA:BB:CC:DD:EE:FF */
4802
- mac: createMask("**:**:**:**:**:**")
4803
- };
4804
-
4805
- export { Box, Button, Checkbox, DialogHost, FocusScope, Input, JumpNav, Keybind, List, Menu, Portal, Progress, Radio, ScrollView, Select, Spacer, Spinner, Text, ToastHost, createMask, masks, render, useApp, useDialog, useFocus, useFocusRegistry, useFocusable, useInput, useLayout, useToast };
4806
- //# sourceMappingURL=index.js.map
1
+ import ee,{createContext,forwardRef,useState,useContext,useRef,useEffect,useLayoutEffect,useMemo,useCallback}from'react';import An from'react-reconciler';import ue from'string-width';import nn,{FlexDirection,Justify,Align,Direction,Edge,Wrap,Gutter,PositionType,Overflow,MeasureMode}from'yoga-layout';var Hn=0;function Lt(){return `glyph-focus-${Hn++}`}function Bt(e,t){let n=t.style??{};return {type:e,props:t,style:n,children:[],rawTextChildren:[],parent:null,yogaNode:null,text:null,layout:{x:0,y:0,width:0,height:0,innerX:0,innerY:0,innerWidth:0,innerHeight:0},focusId:e==="input"||t.focusable?Lt():null,hidden:false}}function st(e,t){t.parent=e,e.children.push(t);}function Mt(e,t){let n=e.children.indexOf(t);n!==-1&&(e.children.splice(n,1),t.parent=null);}function Vt(e,t,n){t.parent=e;let r=e.children.indexOf(n);r!==-1?e.children.splice(r,0,t):e.children.push(t);}function qe(e){let t={},n=e;for(;n;){let r=n.style;t.color===void 0&&r.color!==void 0&&(t.color=r.color),t.bg===void 0&&r.bg!==void 0&&(t.bg=r.bg),t.bold===void 0&&r.bold!==void 0&&(t.bold=r.bold),t.dim===void 0&&r.dim!==void 0&&(t.dim=r.dim),t.italic===void 0&&r.italic!==void 0&&(t.italic=r.italic),t.underline===void 0&&r.underline!==void 0&&(t.underline=r.underline),n=n.parent;}return t}function it(e){if(e.text!=null)return e.text;let t="";for(let n of e.children)t+=it(n);return t}var lt=32,Ht={supportsMutation:true,supportsPersistence:false,supportsHydration:false,isPrimaryRenderer:true,scheduleTimeout:setTimeout,cancelTimeout:clearTimeout,noTimeout:-1,supportsMicrotasks:true,scheduleMicrotask:queueMicrotask,getCurrentUpdatePriority:()=>lt,setCurrentUpdatePriority:e=>{},resolveUpdatePriority:()=>lt,getCurrentEventPriority:()=>lt,resolveEventType:()=>null,resolveEventTimeStamp:()=>-1.1,shouldAttemptEagerTransition:()=>false,getInstanceFromNode:()=>null,beforeActiveInstanceBlur:()=>{},afterActiveInstanceBlur:()=>{},prepareScopeUpdate:()=>{},getInstanceFromScope:()=>null,detachDeletedInstance:()=>{},requestPostPaintCallback:e=>{},maySuspendCommit:(e,t)=>false,preloadInstance:(e,t)=>true,startSuspendingCommit:()=>{},suspendInstance:(e,t)=>{},waitForCommitToBeReady:()=>null,NotPendingTransition:null,HostTransitionContext:{$$typeof:Symbol.for("react.context"),_currentValue:null},resetFormInstance:e=>{},bindToConsole:(e,t,n)=>Function.prototype.bind.call(console[e],console,...t),supportsResources:false,isHostHoistableType:(e,t)=>false,supportsSingletons:false,isHostSingletonType:e=>false,supportsTestSelectors:false,createInstance(e,t,n,r,o){return Bt(e,t)},createTextInstance(e,t,n,r){return {type:"raw-text",text:e,parent:null}},appendInitialChild(e,t){if(t.type==="raw-text"){let n=t;n.parent=e,e.rawTextChildren.push(n),e.text=e.rawTextChildren.map(r=>r.text).join("");}else st(e,t);},finalizeInitialChildren(e,t,n,r,o){return false},shouldSetTextContent(e,t){return false},getRootHostContext(e){return {}},getChildHostContext(e,t,n){return e},getPublicInstance(e){return e},prepareForCommit(e){return null},resetAfterCommit(e){e.onCommit();},preparePortalMount(){},appendChild(e,t){if(t.type==="raw-text"){let n=t;n.parent=e,e.rawTextChildren.push(n),e.text=e.rawTextChildren.map(r=>r.text).join("");}else st(e,t);},appendChildToContainer(e,t){if(t.type==="raw-text")return;let n=t;n.parent=null,e.children.push(n);},insertBefore(e,t,n){t.type==="raw-text"||n.type==="raw-text"||Vt(e,t,n);},insertInContainerBefore(e,t,n){if(t.type==="raw-text"||n.type==="raw-text")return;let r=t,o=n,s=e.children.indexOf(o);s!==-1?e.children.splice(s,0,r):e.children.push(r);},removeChild(e,t){if(t.type==="raw-text"){let n=t;n.parent=null;let r=e.rawTextChildren.indexOf(n);r!==-1&&e.rawTextChildren.splice(r,1),e.text=e.rawTextChildren.map(o=>o.text).join("")||null;return}Mt(e,t);},removeChildFromContainer(e,t){if(t.type==="raw-text")return;let n=t,r=e.children.indexOf(n);r!==-1&&e.children.splice(r,1);},commitTextUpdate(e,t,n){e.text=n,e.parent&&(e.parent.text=e.parent.rawTextChildren.map(r=>r.text).join(""));},commitUpdate(e,t,n,r,o){e.props=r,e.style=r.style??{},r.focusable&&!e.focusId&&(e.focusId=`focus-${Math.random().toString(36).slice(2,9)}`);},hideInstance(e){e.hidden=true;},hideTextInstance(e){e.text="";},unhideInstance(e,t){e.hidden=false;},unhideTextInstance(e,t){e.text=t;},clearContainer(e){e.children.length=0;},resetTextContent(e){e.text=null;}};var Ie=An(Ht);Ie.injectIntoDevTools({bundleType:process.env.NODE_ENV==="production"?0:1,version:"0.1.0",rendererPackageName:"glyph"});var _e=class{stdout;stdin;wasRaw=false;cleanedUp=false;dataHandlers=new Set;stdinAttached=false;oscState="normal";oscAccum="";escFlushTimer=null;palette=new Map;paletteResolve=null;constructor(t=process.stdout,n=process.stdin){this.stdout=t,this.stdin=n;}get columns(){return this.stdout.columns||80}get rows(){return this.stdout.rows||24}enterRawMode(){this.stdin.isTTY&&(this.wasRaw=this.stdin.isRaw,this.stdin.setRawMode(true),this.stdin.resume(),this.stdin.setEncoding("utf-8"));}exitRawMode(){this.stdin.isTTY&&!this.wasRaw&&(this.stdin.setRawMode(false),this.stdin.pause());}write(t){this.stdout.write(t);}hideCursor(){this.write("\x1B[?25l");}showCursor(){this.write("\x1B[?25h");}moveCursor(t,n){this.write(`\x1B[${n+1};${t+1}H`);}setCursorColor(t){this.write(`\x1B]12;${t}\x07`);}resetCursorColor(){this.write("\x1B]112\x07");}enterAltScreen(){this.write("\x1B[?1049h");}exitAltScreen(){this.write("\x1B[?1049l");}clearScreen(){this.write("\x1B[2J\x1B[H");}resetStyles(){this.write("\x1B[0m");}enableKittyKeyboard(){this.write("\x1B[>1u");}disableKittyKeyboard(){this.write("\x1B[<u");}setup(){this.enterRawMode(),this.enterAltScreen(),this.enableKittyKeyboard(),this.hideCursor(),this.clearScreen(),this.attachStdinListener(),this.installCleanupHandlers();}cleanup(){this.cleanedUp||(this.cleanedUp=true,this.escFlushTimer!==null&&(clearTimeout(this.escFlushTimer),this.escFlushTimer=null),this.resetStyles(),this.resetCursorColor(),this.disableKittyKeyboard(),this.showCursor(),this.exitAltScreen(),this.exitRawMode());}suspend(){this.escFlushTimer!==null&&(clearTimeout(this.escFlushTimer),this.escFlushTimer=null),this.oscState="normal",this.oscAccum="",this.resetStyles(),this.resetCursorColor(),this.disableKittyKeyboard(),this.showCursor(),this.exitAltScreen(),this.exitRawMode();}resume(){this.enterRawMode(),this.enterAltScreen(),this.enableKittyKeyboard(),this.hideCursor(),this.clearScreen();}attachStdinListener(){this.stdinAttached||(this.stdinAttached=true,this.stdin.on("data",t=>{let n=typeof t=="string"?t:t.toString("utf-8");this.dispatchFiltered(n);}));}onData(t){return this.dataHandlers.add(t),()=>{this.dataHandlers.delete(t);}}dispatchFiltered(t){this.escFlushTimer!==null&&(clearTimeout(this.escFlushTimer),this.escFlushTimer=null);let n=this.filterOsc(t);if(n.length>0)for(let r of this.dataHandlers)r(n);this.oscState==="esc"&&(this.escFlushTimer=setTimeout(()=>{this.escFlushTimer=null,this.oscState="normal";for(let r of this.dataHandlers)r("\x1B");},50));}filterOsc(t){let n="";for(let r=0;r<t.length;r++){let o=t[r],s=t.charCodeAt(r);switch(this.oscState){case "normal":s===27?this.oscState="esc":n+=o;break;case "esc":o==="]"?(this.oscState="osc",this.oscAccum=""):(n+="\x1B"+o,this.oscState="normal");break;case "osc":s===7?(this.handleOscResponse(this.oscAccum),this.oscAccum="",this.oscState="normal"):s===27?this.oscState="osc_esc":this.oscAccum+=o;break;case "osc_esc":o==="\\"?(this.handleOscResponse(this.oscAccum),this.oscAccum="",this.oscState="normal"):(this.oscAccum+="\x1B"+o,this.oscState="osc");break}}return n}handleOscResponse(t){let n=t.match(/^4;(\d+);rgb:([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)/);if(n){let r=parseInt(n[1],10),o=parseInt(n[2].substring(0,2),16),s=parseInt(n[3].substring(0,2),16),i=parseInt(n[4].substring(0,2),16);this.palette.set(r,[o,s,i]),this.palette.size>=16&&this.paletteResolve&&(this.paletteResolve(),this.paletteResolve=null);}}queryPalette(){return new Promise(t=>{let n=()=>t(this.palette),r=setTimeout(n,200);this.paletteResolve=()=>{clearTimeout(r),n();};let o="";for(let s=0;s<16;s++)o+=`\x1B]4;${s};?\x07`;this.write(o);})}onResize(t){return this.stdout.on("resize",t),()=>{this.stdout.off("resize",t);}}installCleanupHandlers(){let t=()=>this.cleanup();process.on("exit",t);let n=r=>{t(),process.kill(process.pid,r);};process.once("SIGINT",()=>n("SIGINT")),process.once("SIGTERM",()=>n("SIGTERM")),process.on("uncaughtException",r=>{t(),console.error(r),process.exit(1);}),process.on("unhandledRejection",r=>{t(),console.error(r),process.exit(1);});}};function At(e){switch(e){case 9:return "tab";case 13:return "return";case 27:return "escape";case 32:return " ";case 127:return "backspace";case 57358:return "capslock";case 57359:return "scrolllock";case 57360:return "numlock";case 57361:return "printscreen";case 57362:return "pause";case 57363:return "menu";case 57364:return "f13";case 57365:return "f14";case 57366:return "f15";case 57367:return "f16";case 57368:return "f17";case 57369:return "f18";case 57370:return "f19";case 57371:return "f20";case 57372:return "f21";case 57373:return "f22";case 57374:return "f23";case 57375:return "f24";case 57376:return "f25";case 57399:return "kp0";case 57400:return "kp1";case 57401:return "kp2";case 57402:return "kp3";case 57403:return "kp4";case 57404:return "kp5";case 57405:return "kp6";case 57406:return "kp7";case 57407:return "kp8";case 57408:return "kp9";case 57409:return "kpdecimal";case 57410:return "kpdivide";case 57411:return "kpmultiply";case 57412:return "kpminus";case 57413:return "kpplus";case 57414:return "kpenter";case 57415:return "kpequal";case 57416:return "kpleft";case 57417:return "kpright";case 57418:return "kpup";case 57419:return "kpdown";case 57420:return "kppageup";case 57421:return "kppagedown";case 57422:return "kphome";case 57423:return "kpend";case 57424:return "kpinsert";case 57425:return "kpdelete";case 57428:return "mediaplaypause";case 57429:return "mediastop";case 57430:return "mediaprev";case 57431:return "medianext";case 57432:return "mediarewind";case 57433:return "mediafastforward";case 57434:return "mediamute";case 57435:return "volumedown";case 57436:return "volumeup";default:return e>=32&&e<=126?String.fromCharCode(e).toLowerCase():"unknown"}}function jn(e){switch(e.split(";")[0]){case "1":return "home";case "2":return "insert";case "3":return "delete";case "4":return "end";case "5":return "pageup";case "6":return "pagedown";case "7":return "home";case "8":return "end";case "11":return "f1";case "12":return "f2";case "13":return "f3";case "14":return "f4";case "15":return "f5";case "17":return "f6";case "18":return "f7";case "19":return "f8";case "20":return "f9";case "21":return "f10";case "23":return "f11";case "24":return "f12";case "25":return "f13";case "26":return "f14";case "28":return "f15";case "29":return "f16";case "31":return "f17";case "32":return "f18";case "33":return "f19";case "34":return "f20";default:return "unknown"}}function $e(e,t){let n=t-1;n&1&&(e.shift=true),n&2&&(e.alt=true),n&4&&(e.ctrl=true),n&8&&(e.meta=true);}function jt(e){let t=[],n=0;for(;n<e.length;){let r=e[n],o=e.charCodeAt(n);if(r==="\x1B"){if(e[n+1]==="["){let s=Dn(e,n);if(s){t.push(s.key),n=s.end;continue}}if(e[n+1]==="O"){let s=On(e,n);if(s){t.push(s.key),n=s.end;continue}}if(n+1<e.length&&e.charCodeAt(n+1)>=32){t.push({name:e[n+1].toLowerCase(),sequence:e.substring(n,n+2),alt:true}),n+=2;continue}t.push({name:"escape",sequence:"\x1B"}),n++;continue}if(o>=1&&o<=26){let s=String.fromCharCode(o+96);o===13?t.push({name:"return",sequence:"\r"}):o===9?t.push({name:"tab",sequence:" "}):o===8?t.push({name:"backspace",sequence:"\b"}):t.push({name:s,sequence:r,ctrl:true}),n++;continue}if(o===127){t.push({name:"backspace",sequence:r}),n++;continue}t.push({name:r,sequence:r}),n++;}return t}function On(e,t){if(t+2>=e.length)return null;let n=e[t+2],r=e.substring(t,t+3),o;switch(n){case "A":o={name:"up",sequence:r};break;case "B":o={name:"down",sequence:r};break;case "C":o={name:"right",sequence:r};break;case "D":o={name:"left",sequence:r};break;case "H":o={name:"home",sequence:r};break;case "F":o={name:"end",sequence:r};break;case "P":o={name:"f1",sequence:r};break;case "Q":o={name:"f2",sequence:r};break;case "R":o={name:"f3",sequence:r};break;case "S":o={name:"f4",sequence:r};break;case "j":o={name:"kpmultiply",sequence:r};break;case "k":o={name:"kpplus",sequence:r};break;case "l":o={name:"kpcomma",sequence:r};break;case "m":o={name:"kpminus",sequence:r};break;case "n":o={name:"kpdecimal",sequence:r};break;case "o":o={name:"kpdivide",sequence:r};break;case "p":o={name:"kp0",sequence:r};break;case "q":o={name:"kp1",sequence:r};break;case "r":o={name:"kp2",sequence:r};break;case "s":o={name:"kp3",sequence:r};break;case "t":o={name:"kp4",sequence:r};break;case "u":o={name:"kp5",sequence:r};break;case "v":o={name:"kp6",sequence:r};break;case "w":o={name:"kp7",sequence:r};break;case "x":o={name:"kp8",sequence:r};break;case "y":o={name:"kp9",sequence:r};break;case "M":o={name:"kpenter",sequence:r};break;default:return null}return {key:o,end:t+3}}function Dn(e,t){let n=t+2,r="";for(;n<e.length;){let c=e.charCodeAt(n);if(c>=48&&c<=63)r+=e[n],n++;else break}if(n>=e.length)return null;let o=e[n],s=e.substring(t,n+1);n++;let i;switch(o){case "A":i={name:"up",sequence:s};break;case "B":i={name:"down",sequence:s};break;case "C":i={name:"right",sequence:s};break;case "D":i={name:"left",sequence:s};break;case "H":i={name:"home",sequence:s};break;case "F":i={name:"end",sequence:s};break;case "Z":i={name:"tab",sequence:s,shift:true};break;case "P":i={name:"f1",sequence:s};break;case "Q":i={name:"f2",sequence:s};break;case "R":i={name:"f3",sequence:s};break;case "S":i={name:"f4",sequence:s};break;case "~":{if(r.startsWith("27;")){let c=r.split(";"),l=parseInt(c[1]??"1",10),a=parseInt(c[2]??"0",10);i={name:At(a),sequence:s},$e(i,l);break}if(i={name:jn(r),sequence:s},r.includes(";")){let c=r.split(";"),l=parseInt(c[1]??"1",10);$e(i,l);}break}case "u":{let c=r.split(";"),l=parseInt(c[0]??"0",10),a=parseInt(c[1]??"1",10);i={name:At(l),sequence:s},$e(i,a);break}case "I":i={name:"focus",sequence:s};break;case "O":i={name:"blur",sequence:s};break;default:i={name:"unknown",sequence:s};}if(r.includes(";")&&!["~","u"].includes(o)){let c=r.split(";"),l=parseInt(c[c.length-1]??"1",10);l>=1&&l<=16&&$e(i,l);}return {key:i,end:n}}var Kn={black:"\x1B[30m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",blackBright:"\x1B[90m",redBright:"\x1B[91m",greenBright:"\x1B[92m",yellowBright:"\x1B[93m",blueBright:"\x1B[94m",magentaBright:"\x1B[95m",cyanBright:"\x1B[96m",whiteBright:"\x1B[97m"},Wn={black:"\x1B[40m",red:"\x1B[41m",green:"\x1B[42m",yellow:"\x1B[43m",blue:"\x1B[44m",magenta:"\x1B[45m",cyan:"\x1B[46m",white:"\x1B[47m",blackBright:"\x1B[100m",redBright:"\x1B[101m",greenBright:"\x1B[102m",yellowBright:"\x1B[103m",blueBright:"\x1B[104m",magentaBright:"\x1B[105m",cyanBright:"\x1B[106m",whiteBright:"\x1B[107m"};function ct(e){let t=e.replace("#",""),n=parseInt(t.substring(0,2),16),r=parseInt(t.substring(2,4),16),o=parseInt(t.substring(4,6),16);return {r:n,g:r,b:o}}function Ot(e){if(typeof e=="string"){if(e.startsWith("#")){let{r:o,g:s,b:i}=ct(e);return `\x1B[38;2;${o};${s};${i}m`}return Kn[e]??"\x1B[39m"}if(typeof e=="number")return `\x1B[38;5;${e}m`;let{r:t,g:n,b:r}=e;return `\x1B[38;2;${t};${n};${r}m`}function Dt(e){if(typeof e=="string"){if(e.startsWith("#")){let{r:o,g:s,b:i}=ct(e);return `\x1B[48;2;${o};${s};${i}m`}return Wn[e]??"\x1B[49m"}if(typeof e=="number")return `\x1B[48;5;${e}m`;let{r:t,g:n,b:r}=e;return `\x1B[48;2;${t};${n};${r}m`}var Kt={black:[0,0,0],red:[170,0,0],green:[0,170,0],yellow:[170,170,0],blue:[0,0,170],magenta:[170,0,170],cyan:[0,170,170],white:[170,170,170],blackBright:[85,85,85],redBright:[255,85,85],greenBright:[85,255,85],yellowBright:[255,255,85],blueBright:[85,85,255],magentaBright:[255,85,255],cyanBright:[85,255,255],whiteBright:[255,255,255]},Wt=["black","red","green","yellow","blue","magenta","cyan","white","blackBright","redBright","greenBright","yellowBright","blueBright","magentaBright","cyanBright","whiteBright"],Te=null;function qt(e){e.size>0&&(Te=e);}function qn(e){if(Te){let t=Wt.indexOf(e);if(t!==-1){let n=Te.get(t);if(n)return n}}return Kt[e]??null}function _n(e){if(typeof e=="string"){if(e.startsWith("#")){let t=ct(e);return [t.r,t.g,t.b]}return qn(e)}if(typeof e=="number"){if(e<16){if(Te){let s=Te.get(e);if(s)return s}return Kt[Wt[e]]}if(e>=232){let s=(e-232)*10+8;return [s,s,s]}let t=e-16,n=t%6*51,r=Math.floor(t/6)%6*51;return [Math.floor(t/36)*51,r,n]}return [e.r,e.g,e.b]}function ze(e){let t=_n(e);if(!t)return false;let[n,r,o]=t.map(i=>{let c=i/255;return c<=.03928?c/12.92:Math.pow((c+.055)/1.055,2.4)});return .2126*n+.7152*r+.0722*o>.4}function ut(e,t){return e===t?true:e==null||t==null?false:typeof e=="object"&&typeof t=="object"?e.r===t.r&&e.g===t.g&&e.b===t.b:false}function _t(e){return e&&ze(e)?"black":"white"}var Ne=class e{width;height;cells;constructor(t,n){this.width=t,this.height=n,this.cells=new Array(t*n),this.clear();}clear(){for(let t=0;t<this.cells.length;t++)this.cells[t]={ch:" "};}resize(t,n){this.width=t,this.height=n,this.cells=new Array(t*n),this.clear();}get(t,n){if(!(t<0||t>=this.width||n<0||n>=this.height))return this.cells[n*this.width+t]}set(t,n,r){t<0||t>=this.width||n<0||n>=this.height||(this.cells[n*this.width+t]=r);}setChar(t,n,r,o,s,i,c,l,a){t<0||t>=this.width||n<0||n>=this.height||(this.cells[n*this.width+t]={ch:r,fg:o,bg:s,bold:i,dim:c,italic:l,underline:a});}fillRect(t,n,r,o,s,i,c){for(let l=n;l<n+o;l++)for(let a=t;a<t+r;a++)this.setChar(a,l,s,i,c);}clone(){let t=new e(this.width,this.height);for(let n=0;n<this.cells.length;n++){let r=this.cells[n];t.cells[n]={...r};}return t}cellsEqual(t,n){return t.ch===n.ch&&ut(t.fg,n.fg)&&ut(t.bg,n.bg)&&(t.bold??false)===(n.bold??false)&&(t.dim??false)===(n.dim??false)&&(t.italic??false)===(n.italic??false)&&(t.underline??false)===(n.underline??false)}};var $n={single:{topLeft:"\u250C",topRight:"\u2510",bottomLeft:"\u2514",bottomRight:"\u2518",horizontal:"\u2500",vertical:"\u2502"},double:{topLeft:"\u2554",topRight:"\u2557",bottomLeft:"\u255A",bottomRight:"\u255D",horizontal:"\u2550",vertical:"\u2551"},round:{topLeft:"\u256D",topRight:"\u256E",bottomLeft:"\u2570",bottomRight:"\u256F",horizontal:"\u2500",vertical:"\u2502"},ascii:{topLeft:"+",topRight:"+",bottomLeft:"+",bottomRight:"+",horizontal:"-",vertical:"|"}};function $t(e){return e==="none"?null:$n[e]}function zt(e,t,n,r){if(e.length===0)return {width:0,height:0};let o=e.split(`
2
+ `);if(n===MeasureMode.Undefined||r==="none"){let l=0;for(let a of o){let p=ue(a);p>l&&(l=p);}return {width:l,height:o.length}}let s=Math.max(1,Math.floor(t)),i=ce(o,s,r),c=0;for(let l of i){let a=ue(l);a>c&&(c=a);}return {width:c,height:i.length}}function ce(e,t,n){let r=[];for(let o of e){if(ue(o)<=t){r.push(o);continue}if(n==="truncate"){r.push(Xt(o,t));continue}if(n==="ellipsis"){r.push(Xn(o,t));continue}let i=Jn(o,t);r.push(...i);}return r}function Xt(e,t){let n="",r=0;for(let o of e){let s=ue(o);if(r+s>t)break;n+=o,r+=s;}return n}function Xn(e,t){if(t<=1)return t===1?"\u2026":"";let n=Xt(e,t-1);return ue(n)<ue(e)?n+"\u2026":e}function Jn(e,t){let n=[],r="",o=0,s="",i=0;for(let c=0;c<=e.length;c++){let l=e[c],a=c===e.length,p=l===" ";if(a||p){if(s.length>0){if(o+i<=t)r+=s,o+=i;else if(i<=t)r.length>0&&n.push(r),r=s,o=i;else for(let d of s){let S=ue(d);o+S>t&&r.length>0&&(n.push(r),r="",o=0),r+=d,o+=S;}s="",i=0;}p&&(o+1<=t?(r+=" ",o+=1):(r.length>0&&n.push(r),r=" ",o=1));}else l&&(s+=l,i+=ue(l));}return r.length>0&&n.push(r),n.length>0?n:[""]}function Jt(e,t,n={}){t.clear();let r={},o=[],s={x:0,y:0,width:t.width,height:t.height};for(let i of e)i.hidden||Yt(i,s,i.style.zIndex??0,o);o.sort((i,c)=>i.zIndex-c.zIndex);for(let i of o){let c=Un(i.node,t,i.clip,n);c?.cursorPosition&&(r.cursorPosition=c.cursorPosition);}return r}function Yt(e,t,n,r){if(e.hidden)return;let o=e.style.zIndex??n,s=e.style.clip?Yn(t,{x:e.layout.innerX,y:e.layout.innerY,width:e.layout.innerWidth,height:e.layout.innerHeight}):t;if(r.push({node:e,clip:t,zIndex:o}),e.type!=="text"&&e.type!=="input")for(let i of e.children)Yt(i,s,o,r);}function Yn(e,t){let n=Math.max(e.x,t.x),r=Math.max(e.y,t.y),o=Math.min(e.x+e.width,t.x+t.width),s=Math.min(e.y+e.height,t.y+t.height);return {x:n,y:r,width:Math.max(0,o-n),height:Math.max(0,s-r)}}function Xe(e,t,n){return e>=n.x&&e<n.x+n.width&&t>=n.y&&t<n.y+n.height}function Un(e,t,n,r={}){let{x:o,y:s,width:i,height:c,innerX:l,innerY:a,innerWidth:p,innerHeight:d}=e.layout,S=e.style;if(i<=0||c<=0)return;let I=qe(e).bg;if(S.bg)for(let u=s;u<s+c;u++)for(let R=o;R<o+i;R++)Xe(R,u,n)&&t.setChar(R,u," ",void 0,S.bg);let f=S.border?$t(S.border):null;if(f&&i>=2&&c>=2){let u=S.borderColor,R=I;ie(t,n,o,s,f.topLeft,u,R);for(let P=o+1;P<o+i-1;P++)ie(t,n,P,s,f.horizontal,u,R);ie(t,n,o+i-1,s,f.topRight,u,R),ie(t,n,o,s+c-1,f.bottomLeft,u,R);for(let P=o+1;P<o+i-1;P++)ie(t,n,P,s+c-1,f.horizontal,u,R);ie(t,n,o+i-1,s+c-1,f.bottomRight,u,R);for(let P=s+1;P<s+c-1;P++)ie(t,n,o,P,f.vertical,u,R),ie(t,n,o+i-1,P,f.vertical,u,R);}if(e.type==="text")Zn(e,t,n);else if(e.type==="input")return Qn(e,t,n,r)}function ie(e,t,n,r,o,s,i,c,l,a,p){Xe(n,r,t)&&e.setChar(n,r,o,s,i,c,l,a,p);}function Ut(e,t){if(e!==void 0)return e;if(t!==void 0)return ze(t)?"black":"white"}function Zn(e,t,n){let{innerX:r,innerY:o,innerWidth:s,innerHeight:i}=e.layout,c=qe(e),l=it(e);if(!l)return;let a=Ut(c.color,c.bg),p=e.style.wrap??"wrap",d=e.style.textAlign??"left",S=l.split(`
3
+ `),v=ce(S,s,p);for(let I=0;I<v.length&&I<i;I++){let f=v[I],u=ue(f),R=0;d==="center"?R=Math.max(0,Math.floor((s-u)/2)):d==="right"&&(R=Math.max(0,s-u));let P=0;for(let E of f){let A=ue(E);A>0&&ie(t,n,r+R+P,o+I,E,a,c.bg,c.bold,c.dim,c.italic,c.underline),P+=A;}}}function Qn(e,t,n,r={}){let{cursorInfo:o,useNativeCursor:s}=r,{innerX:i,innerY:c,innerWidth:l,innerHeight:a}=e.layout;if(l<=0||a<=0)return;let p=e.props.value??e.props.defaultValue??"",d=e.props.placeholder??"",S=p||d,v=!p&&!!d,I=e.props.multiline??false,f=qe(e),u=Ut(f.color,f.bg),R=f.bg?ze(f.bg)?"blackBright":"whiteBright":"blackBright",P=v?R:u??f.color??e.style.color,E=v?R:P,A=v?true:f.dim,W=o&&o.nodeId===e.focusId,k;if(I&&!v){let G=e.style.wrap??"wrap",T=S.split(`
4
+ `),B=ce(T,l,G),C=0,x=0;if(W){let b=o.position,M=0,_=b,q=0;for(let X=0;X<T.length;X++){let J=T[X].length;if(b<=q+J){M=X,_=b-q;break}q+=J+1;}let z=0;for(let X=0;X<M;X++)z+=ce([T[X]],l,G).length;let j=ce([T[M]],l,G),D=0,L=0;for(let X=0;X<j.length;X++){let J=j[X];if(_<=D+J.length){L=X;break}D+=J.length;}C=z+L,x=ue(T[M].slice(D,D+(_-D)));}let w=Math.max(0,C-a+1);for(let b=0;b<a;b++){let M=w+b;if(M>=B.length)break;let _=B[M],q=0;for(let z of _){if(q>=l)break;let j=ue(z);j>0&&ie(t,n,i+q,c+b,z,E,f.bg,f.bold,A,f.italic,f.underline),q+=j;}}if(W){let b=C-w;if(b>=0&&b<a){let M=Math.min(x,l-1),_=i+M,q=c+b;if(Xe(_,q,n)&&_<i+l)if(s)k={cursorPosition:{x:_,y:q,bg:f.bg}};else {let z=t.get(_,q),j=z?.ch&&z.ch!==" "?z.ch:"\u258C",D=f.bg??"black",L=f.color??"white";t.setChar(_,q,j,D,L,z?.bold,z?.dim,z?.italic,false);}}}}else {let G=0;for(let T of S){if(G>=l)break;let B=ue(T);B>0&&ie(t,n,i+G,c,T,E,f.bg,f.bold,A,f.italic,f.underline),G+=B;}if(W){let T=Math.min(o.position,l-1),B=i+T;if(Xe(B,c,n)&&B<i+l)if(s)k={cursorPosition:{x:B,y:c,bg:f.bg}};else {let C=t.get(B,c),x=C?.ch&&C.ch!==" "?C.ch:"\u258C",w=f.bg??"black",b=f.color??"white";t.setChar(B,c,x,w,b,C?.bold,C?.dim,C?.italic,false);}}}return k}var er="\x1B",de=`${er}[`;function tr(e,t){return `${de}${t+1};${e+1}H`}function nr(e){let t=`${de}0m`;return e.bold&&(t+=`${de}1m`),e.dim&&(t+=`${de}2m`),e.italic&&(t+=`${de}3m`),e.underline&&(t+=`${de}4m`),e.fg!=null&&(t+=Ot(e.fg)),e.bg!=null&&(t+=Dt(e.bg)),t}function Zt(e,t,n){let r="",o=-1,s=-1,i="";for(let c=0;c<t.height;c++)for(let l=0;l<t.width;l++){let a=t.get(l,c);if(!n){let d=e.get(l,c);if(d&&t.cellsEqual(a,d))continue}(s!==c||o!==l)&&(r+=tr(l,c));let p=nr(a);p!==i&&(r+=p,i=p),r+=a.ch,o=l+1,s=c;}return r.length>0&&(r+=`${de}0m`),r}var ir={row:FlexDirection.Row,column:FlexDirection.Column},lr={"flex-start":Justify.FlexStart,center:Justify.Center,"flex-end":Justify.FlexEnd,"space-between":Justify.SpaceBetween,"space-around":Justify.SpaceAround},cr={"flex-start":Align.FlexStart,center:Align.Center,"flex-end":Align.FlexEnd,stretch:Align.Stretch};function tn(e,t,n){n!==void 0&&(typeof n=="string"&&n.endsWith("%"),t(n));}function ae(e,t,n){n!==void 0&&(typeof n=="string"&&n.endsWith("%")?e.setPositionPercent(t,parseFloat(n)):e.setPosition(t,n));}function ur(e,t,n){tn(e,o=>e.setWidth(o),t.width),tn(e,o=>e.setHeight(o),t.height),t.minWidth!==void 0&&e.setMinWidth(t.minWidth),t.minHeight!==void 0&&e.setMinHeight(t.minHeight),t.maxWidth!==void 0&&e.setMaxWidth(t.maxWidth),t.maxHeight!==void 0&&e.setMaxHeight(t.maxHeight),t.padding!==void 0&&e.setPadding(Edge.All,t.padding),t.paddingX!==void 0&&e.setPadding(Edge.Horizontal,t.paddingX),t.paddingY!==void 0&&e.setPadding(Edge.Vertical,t.paddingY),t.paddingTop!==void 0&&e.setPadding(Edge.Top,t.paddingTop),t.paddingRight!==void 0&&e.setPadding(Edge.Right,t.paddingRight),t.paddingBottom!==void 0&&e.setPadding(Edge.Bottom,t.paddingBottom),t.paddingLeft!==void 0&&e.setPadding(Edge.Left,t.paddingLeft);let r=t.border!=null&&t.border!=="none";e.setBorder(Edge.All,r?1:0),t.flexDirection&&e.setFlexDirection(ir[t.flexDirection]??FlexDirection.Column),t.flexWrap&&e.setFlexWrap(t.flexWrap==="wrap"?Wrap.Wrap:Wrap.NoWrap),t.justifyContent&&e.setJustifyContent(lr[t.justifyContent]??Justify.FlexStart),t.alignItems&&e.setAlignItems(cr[t.alignItems]??Align.Stretch),t.flexGrow!==void 0&&e.setFlexGrow(t.flexGrow),t.flexShrink!==void 0&&e.setFlexShrink(t.flexShrink),t.gap!==void 0&&e.setGap(Gutter.All,t.gap),t.position==="absolute"?e.setPositionType(PositionType.Absolute):e.setPositionType(PositionType.Relative),t.inset!==void 0&&(ae(e,Edge.Top,t.inset),ae(e,Edge.Right,t.inset),ae(e,Edge.Bottom,t.inset),ae(e,Edge.Left,t.inset)),ae(e,Edge.Top,t.top),ae(e,Edge.Right,t.right),ae(e,Edge.Bottom,t.bottom),ae(e,Edge.Left,t.left),t.clip&&e.setOverflow(Overflow.Hidden);}function rn(e){let t=nn.Node.create();if(e.yogaNode=t,ur(t,e.style,e.type),e.type==="text"||e.type==="input")t.setMeasureFunc((n,r,o,s)=>{let i;return e.type==="input"?(i=e.props.value??e.props.defaultValue??e.props.placeholder??"",i.length===0&&(i=" ")):i=on(e),zt(i,n,r,e.style.wrap??"wrap")});else for(let n=0;n<e.children.length;n++){let r=e.children[n];r.hidden||(rn(r),t.insertChild(r.yogaNode,t.getChildCount()));}}function on(e){if(e.text!=null)return e.text;let t="";for(let n of e.children)t+=on(n);if(t===""&&e.props.children!=null){if(typeof e.props.children=="string")return e.props.children;if(typeof e.props.children=="number")return String(e.props.children)}return t}function sn(e,t,n){let r=e.yogaNode,o=r.getComputedLayout(),s=t+o.left,i=n+o.top,c=o.width,l=o.height,a=e.style.border&&e.style.border!=="none"?1:0,p=r.getComputedPadding(Edge.Top),d=r.getComputedPadding(Edge.Right),S=r.getComputedPadding(Edge.Bottom),v=r.getComputedPadding(Edge.Left),I=s+a+v,f=i+a+p,u=Math.max(0,c-a*2-v-d),R=Math.max(0,l-a*2-p-S);e.layout={x:s,y:i,width:c,height:l,innerX:I,innerY:f,innerWidth:u,innerHeight:R};for(let P of e.children)P.hidden||!P.yogaNode||sn(P,s,i);}function ln(e,t,n){let r=nn.Node.create();r.setWidth(t),r.setHeight(n),r.setFlexDirection(FlexDirection.Column);for(let o of e)o.hidden||(rn(o),r.insertChild(o.yogaNode,r.getChildCount()));r.calculateLayout(t,n,Direction.LTR);for(let o of e)o.hidden||!o.yogaNode||sn(o,0,0);r.freeRecursive(),cn(e);}function cn(e){for(let t of e)t.yogaNode=null,cn(t.children);}var Y=createContext(null),$=createContext(null),oe=createContext(null),be=createContext(null),Ye=createContext(null);function ar(e,t={}){let n=t.stdout??process.stdout,r=t.stdin??process.stdin,o=t.debug??false,s=t.useNativeCursor??true,i=new _e(n,r);i.setup();let c=false;i.queryPalette().then(m=>{qt(m),p=true,b();});let l=new Ne(i.columns,i.rows),a=new Ne(i.columns,i.rows),p=true,d=new Set,S=new Set,v=new Map,I={subscribe(m){return d.add(m),()=>d.delete(m)},subscribePriority(m){return S.add(m),()=>S.delete(m)},registerInputHandler(m,g){return v.set(m,g),()=>v.delete(m)}},f=null,u=new Map,R=[],P=new Set,E=[],A=new Set;function W(m){if(f!==m){f=m,b();for(let g of A)g(f);}}function k(){let m=[...R];if(E.length>0){let g=E[E.length-1];m=m.filter(y=>g.has(y));}return m=m.filter(g=>!P.has(g)),m.sort((g,y)=>{let O=u.get(g),K=u.get(y);if(!O||!K)return 0;let F=O.layout,h=K.layout;return F.y!==h.y?F.y-h.y:F.x-h.x}),m}let G={get focusedId(){return f},register(m,g){if(u.set(m,g),R.includes(m)||R.push(m),E.length>0&&E[E.length-1].add(m),f===null){let y=k();y.length>0&&W(y[0]);}return ()=>{u.delete(m);let y=R.indexOf(m);if(y!==-1&&R.splice(y,1),f===m){let O=k();W(O[0]??null);}}},requestFocus(m){W(m);},focusNext(){let m=k();if(m.length===0)return;let y=((f?m.indexOf(f):-1)+1)%m.length;W(m[y]);},focusPrev(){let m=k();if(m.length===0)return;let y=((f?m.indexOf(f):0)-1+m.length)%m.length;W(m[y]);},setSkippable(m,g){if(g){if(P.add(m),f===m){let y=k();y.length>0&&W(y[0]);}}else P.delete(m);},trapIds:null,pushTrap(m){return E.push(m),()=>{let g=E.indexOf(m);g!==-1&&E.splice(g,1);}},onFocusChange(m){return A.add(m),()=>{A.delete(m);}},getRegisteredElements(){let m=[];for(let g of R){if(P.has(g))continue;let y=u.get(g);y&&m.push({id:g,node:y});}return m},getActiveElements(){let m=k(),g=[];for(let y of m){if(P.has(y))continue;let O=u.get(y);O&&g.push({id:y,node:O});}return g}},T=new Map,B={getLayout(m){return m.layout},subscribe(m,g){return T.has(m)||T.set(m,new Set),T.get(m).add(g),()=>{let y=T.get(m);y&&(y.delete(g),y.size===0&&T.delete(m));}}},C={registerNode(){},unregisterNode(){},scheduleRender:b,exit(m){le.exit(m);},get columns(){return i.columns},get rows(){return i.rows}},x={type:"root",children:[],onCommit(){b();}},w=false;function b(){w||(w=true,queueMicrotask(()=>{w=false,M();}));}function M(){let m=i.columns,g=i.rows;(a.width!==m||a.height!==g)&&(a.resize(m,g),l.resize(m,g),p=true),ln(x.children,m,g),_(x.children);let y;if(f){let F=u.get(f);F?.type==="input"&&(y={nodeId:f,position:F.props.cursorPosition??F.props.value?.length??0});}let O=Jt(x.children,a,{cursorInfo:y,useNativeCursor:s}),K=Zt(l,a,p);if(K.length>0&&i.write(K),s)if(O.cursorPosition){let F=_t(O.cursorPosition.bg);i.setCursorColor(F),i.moveCursor(O.cursorPosition.x,O.cursorPosition.y),c||(i.showCursor(),c=true);}else c&&(i.hideCursor(),c=false);for(let F=0;F<a.cells.length;F++)l.cells[F]={...a.cells[F]};p=false;}function _(m){for(let g of m){let y=T.get(g);if(y)for(let O of y)O(g.layout);_(g.children);}}let q=i.onData(m=>{let g=jt(m);for(let y of g){if(y.ctrl&&y.name==="c"){le.exit();return}if(y.ctrl&&y.name==="z"){i.suspend(),process.kill(0,"SIGSTOP");return}if(y.name==="tab"&&!y.ctrl&&!y.alt){y.shift?G.focusPrev():G.focusNext();continue}let O=false;for(let K of S)if(K(y)){O=true;break}if(!O&&f){let K=v.get(f);K&&(O=K(y));}if(!O)for(let K of d)K(y);}}),z=i.onResize(()=>{p=true,b();}),j=()=>{i.resume(),p=true,b();};process.on("SIGCONT",j);let D=ee.createElement(be.Provider,{value:C},ee.createElement(Y.Provider,{value:I},ee.createElement($.Provider,{value:G},ee.createElement(oe.Provider,{value:B},e)))),L=m=>{o&&console.error("Uncaught error:",m);},X=m=>{o&&console.error("Error caught by boundary:",m);},J=m=>{o&&console.error("Recoverable error:",m);},se=Ie.createContainer(x,0,null,false,null,"",L,X,J,null);Ie.updateContainer(D,se,null,null);let le={unmount(){Ie.updateContainer(null,se,null,null),q(),z(),process.off("SIGCONT",j),i.cleanup();},exit(m){le.unmount(),process.exit(m??0);}};return le}var dr=forwardRef(function({children:t,style:n,focusable:r},o){return ee.createElement("box",{style:n,focusable:r,ref:o},t)});var gr=forwardRef(function({children:t,style:n,wrap:r},o){let s=r?{...n,wrap:r}:n;return ee.createElement("text",{style:s,ref:o},t)});function un(e,t,n){if(n<=0)return {visualLine:0,visualCol:t,totalVisualLines:1,lineStartOffset:0,lineLength:e.length};let r=e.split(`
5
+ `),o=[],s=0;for(let l of r){let a=ce([l],n,"wrap"),p=0;for(let d of a)o.push({text:d,logicalOffset:s+p}),p+=d.length;s+=l.length+1;}let i=0;for(let l=0;l<o.length;l++){let a=o[l],p=a.text.length,d=l+1<o.length&&o[l+1].logicalOffset!==a.logicalOffset+p,S=p+(d?1:0);if(t<i+p||l===o.length-1)return {visualLine:l,visualCol:Math.min(t-i,p),totalVisualLines:o.length,lineStartOffset:i,lineLength:p};i+=S;}let c=o.length-1;return {visualLine:c,visualCol:o[c].text.length,totalVisualLines:o.length,lineStartOffset:i-o[c].text.length,lineLength:o[c].text.length}}function an(e,t,n,r){if(r<=0)return Math.min(n,e.length);let o=e.split(`
6
+ `),s=[],i=0;for(let p of o){let d=ce([p],r,"wrap"),S=0;for(let v of d)s.push({text:v,startOffset:i+S}),S+=v.length;i+=p.length+1;}let c=Math.max(0,Math.min(t,s.length-1)),l=s[c],a=Math.min(n,l.text.length);return l.startOffset+a}function Ge(e,t){let n=e.split(`
7
+ `),r=t;for(let s=0;s<n.length;s++){if(r<=n[s].length)return {line:s,col:r,lines:n};r-=n[s].length+1;}let o=n.length-1;return {line:o,col:n[o].length,lines:n}}function Le(e,t,n){let r=0;for(let o=0;o<t&&o<e.length;o++)r+=e[o].length+1;return r+Math.min(n,e[t]?.length??0)}function xr(e){let{value:t,defaultValue:n="",onChange:r,onKeyPress:o,onBeforeChange:s,placeholder:i,style:c,focusedStyle:l,multiline:a,autoFocus:p,type:d="text"}=e,[S,v]=useState(n),[I,f]=useState(n.length),[u,R]=useState(0),[P,E]=useState(false),[A,W]=useState(false),k=useContext(Y),G=useContext($),T=useContext(oe),B=useRef(null),C=useRef(null),x=t!==void 0,w=x?t:S;useEffect(()=>{if(!T||!B.current)return;let j=T.getLayout(B.current);return R(j.innerWidth),T.subscribe(B.current,D=>{R(D.innerWidth);})},[T]);let b=useRef(w),M=useRef(I);useEffect(()=>{b.current=w,M.current>w.length&&(M.current=w.length,f(w.length));},[w]),useEffect(()=>{M.current=I;},[I]);let _=useRef({isControlled:x,onChange:r,onKeyPress:o,onBeforeChange:s,multiline:a??false,innerWidth:u,type:d});_.current={isControlled:x,onChange:r,onKeyPress:o,onBeforeChange:s,multiline:a??false,innerWidth:u,type:d},useEffect(()=>{if(!(!G||!C.current||!B.current))return G.register(C.current,B.current)},[G,A]);let q=useRef(false);useEffect(()=>{if(p&&!q.current&&G&&C.current){q.current=true;let j=C.current;queueMicrotask(()=>{G.requestFocus(j);});}},[p,G,A]),useEffect(()=>{if(!G||!C.current)return;let j=C.current;return E(G.focusedId===j),G.onFocusChange(D=>{E(D===j);})},[G,A]),useEffect(()=>{if(!k||!C.current)return;let j=C.current,D=L=>{let{isControlled:X,onChange:J,onKeyPress:se,onBeforeChange:le,multiline:m}=_.current;if(se?.(L)===true)return true;let g=b.current,y=M.current;if(L.name==="escape")return false;let O=(h,N)=>{let V=h,Q=N;if(le){let H=le(h,g);if(H===false)return;typeof H=="string"&&(V=H,Q=H.length);}b.current=V,M.current=Q,X||v(V),J?.(V),f(Q);},K=h=>{M.current=h,f(h);};if(L.name==="return"){if(m){let h=g.slice(0,y)+`
8
+ `+g.slice(y);return O(h,y+1),true}return false}if(L.ctrl){if(L.name==="w"){if(y>0){let h=y;for(;h>0&&g[h-1]===" ";)h--;for(;h>0&&g[h-1]!==" "&&(!m||g[h-1]!==`
9
+ `);)h--;let N=g.slice(0,h)+g.slice(y);O(N,h);}return true}if(L.name==="a"){if(m){let{line:h,lines:N}=Ge(g,y);K(Le(N,h,0));}else K(0);return true}if(L.name==="e"){if(m){let{line:h,lines:N}=Ge(g,y);K(Le(N,h,N[h].length));}else K(g.length);return true}if(L.name==="k"){if(m){let{line:h,lines:N}=Ge(g,y),V=Le(N,h,N[h].length);if(y<V){let Q=g.slice(0,y)+g.slice(V);O(Q,y);}}else if(y<g.length){let h=g.slice(0,y);O(h,y);}return true}return false}if(L.alt){if(L.name==="left"||L.name==="b"){let h=y;for(;h>0&&g[h-1]===" ";)h--;for(;h>0&&g[h-1]!==" "&&g[h-1]!==`
10
+ `;)h--;return K(h),true}if(L.name==="right"||L.name==="f"){let h=y;for(;h<g.length&&g[h]!==" "&&g[h]!==`
11
+ `;)h++;for(;h<g.length&&g[h]===" ";)h++;return K(h),true}if(L.name==="backspace"||L.name==="d")if(L.name==="backspace"){if(y>0){let h=y;for(;h>0&&g[h-1]===" ";)h--;for(;h>0&&g[h-1]!==" "&&g[h-1]!==`
12
+ `;)h--;let N=g.slice(0,h)+g.slice(y);O(N,h);}return true}else {if(y<g.length){let h=y;for(;h<g.length&&g[h]!==" "&&g[h]!==`
13
+ `;)h++;for(;h<g.length&&g[h]===" ";)h++;let N=g.slice(0,y)+g.slice(h);O(N,y);}return true}return false}if(L.name==="left")return K(Math.max(0,y-1)),true;if(L.name==="right")return K(Math.min(g.length,y+1)),true;if(L.name==="up"){let{innerWidth:h}=_.current,N=un(g,y,h);return N.visualLine>0&&K(an(g,N.visualLine-1,N.visualCol,h)),true}if(L.name==="down"){let{innerWidth:h}=_.current,N=un(g,y,h);return N.visualLine<N.totalVisualLines-1&&K(an(g,N.visualLine+1,N.visualCol,h)),true}if(L.name==="home"){if(m){let{line:h,lines:N}=Ge(g,y);K(Le(N,h,0));}else K(0);return true}if(L.name==="end"){if(m){let{line:h,lines:N}=Ge(g,y);K(Le(N,h,N[h].length));}else K(g.length);return true}if(L.name==="backspace"){if(y>0){let h=g.slice(0,y-1)+g.slice(y);O(h,y-1);}return true}if(L.name==="delete"){if(y<g.length){let h=g.slice(0,y)+g.slice(y+1);O(h,y);}return true}if(L.name.length>1)return false;let F=L.sequence;if(F.length===1&&F.charCodeAt(0)>=32){let{type:h}=_.current;if(h==="number"){let V=/[0-9]/.test(F),Q=F==="."&&!g.includes("."),H=F==="-"&&y===0&&!g.includes("-");if(!V&&!Q&&!H)return true}let N=g.slice(0,y)+F+g.slice(y);return O(N,y+1),true}return false};return k.registerInputHandler(j,D)},[k,A]);let z={...c,...P&&l?l:{}};return ee.createElement("input",{style:z,value:w,defaultValue:n,placeholder:i,onChange:r,cursorPosition:I,multiline:a??false,focused:P,ref:j=>{j?(B.current=j,C.current=j.focusId,W(true)):(B.current=null,C.current=null,W(false));}})}function ft({trap:e=false,children:t}){let n=useContext($),r=useRef(null),o=useRef(new Set);return useLayoutEffect(()=>{if(!e||!n)return;r.current=n.focusedId;let s=n.pushTrap(o.current);return ()=>{s(),r.current&&n.requestFocus(r.current);}},[e,n]),useEffect(()=>{if(!(!e||!n)&&o.current.size>0){let s=o.current.values().next().value;s&&n.requestFocus(s);}},[e,n]),ee.createElement(ee.Fragment,null,t)}function Rr({size:e}){return ee.createElement("box",{style:{flexGrow:e??1}})}function hn(e){let t=e.toLowerCase().split("+");return {name:t[t.length-1],ctrl:t.includes("ctrl"),alt:t.includes("alt"),shift:t.includes("shift"),meta:t.includes("meta")||t.includes("cmd")||t.includes("super")||t.includes("win")}}function gn(e,t){return !(t.name!==e.name||e.ctrl!==!!t.ctrl||e.alt!==!!t.alt||e.shift!==!!t.shift||e.meta!==!!t.meta)}function Ir({keypress:e,onPress:t,whenFocused:n,priority:r,disabled:o}){let s=useContext(Y),i=useContext($),c=useRef(t);c.current=t;let l=useRef(hn(e));return l.current=hn(e),useEffect(()=>{if(!(!s||o))if(r){let a=p=>!gn(l.current,p)||n&&i?.focusedId!==n?false:(c.current(),true);return s.subscribePriority(a)}else {let a=p=>{gn(l.current,p)&&(n&&i?.focusedId!==n||c.current());};return s.subscribe(a)}},[s,i,n,r,o]),null}function Nr({children:e,zIndex:t=1e3}){return ee.createElement("box",{style:{position:"absolute",top:0,left:0,width:"100%",height:"100%",zIndex:t}},e)}function Er({onPress:e,style:t,focusedStyle:n,children:r,disabled:o}){let s=useContext($),i=useContext(Y),c=useRef(null),l=useRef(null),a=useRef(e);a.current=e;let[p,d]=useState(false),[S,v]=useState(false);useEffect(()=>{if(!(!s||!l.current||!c.current||o))return s.register(l.current,c.current)},[s,o,p]),useEffect(()=>{if(!s||!l.current)return;let f=l.current;return v(s.focusedId===f),s.onFocusChange(u=>{v(u===f);})},[s,p]),useEffect(()=>{if(!i||!l.current||o)return;let f=l.current,u=R=>s?.focusedId!==f?false:R.name==="return"||R.name===" "||R.sequence===" "?(a.current?.(),true):false;return i.registerInputHandler(f,u)},[i,s,o,p]);let I={...t,...S&&n?n:{}};return ee.createElement("box",{style:I,focusable:!o,ref:f=>{f?(c.current=f,l.current=f.focusId,d(true)):(c.current=null,l.current=null,d(false));}},r)}var Lr={x:0,y:0,width:0,height:0,innerX:0,innerY:0,innerWidth:0,innerHeight:0};function fe(e){let t=useContext(oe),[n,r]=useState(Lr);return useEffect(()=>{if(!(!t||!e?.current))return r(t.getLayout(e.current)),t.subscribe(e.current,r)},[t,e]),n}function Be(e,t=[]){let n=useContext(Y);useEffect(()=>{if(n)return n.subscribe(e)},[n,...t]);}function Ar({children:e,style:t,scrollOffset:n,onScroll:r,defaultScrollOffset:o=0,scrollStep:s=1,disableKeyboard:i,scrollToFocus:c=true,showScrollbar:l=true,focusable:a=true,focusedStyle:p}){let d=n!==void 0,[S,v]=useState(o),I=d?n:S,f=useRef(null),u=useRef(null),R=fe(f),P=fe(u),E=useContext($),A=useContext(oe),W=useRef(null);a&&!W.current&&(W.current=`scrollview-${Math.random().toString(36).slice(2,9)}`);let k=a?W.current:null;useEffect(()=>{if(!(!a||!k||!E||!f.current))return E.register(k,f.current)},[a,k,E]);let G=a&&k&&E?.focusedId===k,T=R.innerHeight,B=P.height,C=Math.max(0,B-T),x=Math.max(0,Math.min(I,C)),w=useMemo(()=>({getBounds:()=>{let H=R.y;return {visibleTop:H,visibleBottom:H+T,viewportHeight:T,scrollOffset:x}}}),[R.y,T,x]),b=useCallback(H=>{let re=Math.max(0,Math.min(H,C));d?r?.(re):v(re);},[d,r,C]);useEffect(()=>{I>C&&C>=0&&b(C);},[I,C,b]),useEffect(()=>!c||!E||!A||!u.current?void 0:E.onFocusChange(re=>{if(!re||!u.current)return;let Z=Ke=>{if(Ke.focusId===re)return Ke;for(let We of Ke.children){let ye=Z(We);if(ye)return ye}return null},pe=Z(u.current);if(!pe)return;let ge=A.getLayout(pe),Re=u.current.layout?.y??0,Oe=ge.y-Re,De=Oe+ge.height,ve=I,ot=I+T;Oe<ve?b(Oe):De>ot&&b(De-T);}),[c,E,A,I,T,b]);let M=useCallback(()=>{if(!E)return false;let H=E.focusedId;if(!H)return false;if(a&&k&&H===k)return true;if(!u.current)return false;let re=Z=>{if(Z.focusId===H)return true;for(let pe of Z.children)if(re(pe))return true;return false};return re(u.current)},[E,a,k]);Be(H=>{if(i||!M())return;let re=Math.max(1,Math.floor(T/2)),Z=Math.max(1,T);switch(H.name){case "pageup":b(I-Z);break;case "pagedown":b(I+Z);break;default:H.ctrl&&(H.name==="d"?b(I+re):H.name==="u"?b(I-re):H.name==="f"?b(I+Z):H.name==="b"&&b(I-Z));break}},[I,s,T,C,i,b,M]);let{padding:_,paddingX:q,paddingY:z,paddingTop:j,paddingRight:D,paddingBottom:L,paddingLeft:X,...J}=t??{},le=J.border!=null&&J.border!=="none"?2:0,m=B>0?B+le:void 0,g={...J,...G?p:{},clip:true,...J.height===void 0&&m!==void 0?{height:m,flexShrink:J.flexShrink??1,minHeight:J.minHeight??0}:{}},y={position:"absolute",top:-x,left:0,right:0,flexDirection:"column",..._!==void 0&&{padding:_},...q!==void 0&&{paddingX:q},...z!==void 0&&{paddingY:z},...j!==void 0&&{paddingTop:j},...D!==void 0&&{paddingRight:D},...L!==void 0&&{paddingBottom:L},...X!==void 0&&{paddingLeft:X}},O=B>T&&T>0,K=l&&O,F=Math.max(1,Math.floor(T/B*T)),h=B-T,N=h>0?Math.floor(x/h*(T-F)):0,V=[];if(K)for(let H=0;H<T;H++)H>=N&&H<N+F?V.push("\u2588"):V.push("\u2591");let Q={position:"absolute",top:0,right:0,width:1,height:T,flexDirection:"column"};return ee.createElement(Ye.Provider,{value:w},ee.createElement("box",{style:g,ref:H=>{f.current=H??null;},...a?{focusable:true,focusId:k}:{}},ee.createElement("box",{style:{...y,paddingRight:K?(y.paddingRight??0)+1:y.paddingRight},ref:H=>{u.current=H??null;}},e),K&&ee.createElement("box",{style:Q},ee.createElement("text",{style:{color:"blackBright"}},V.join(`
14
+ `)))))}function Ct({count:e,renderItem:t,selectedIndex:n,onSelectionChange:r,onSelect:o,defaultSelectedIndex:s=0,disabledIndices:i,style:c,focusable:l=true}){let a=n!==void 0,[p,d]=useState(s),S=a?n:p,v=useContext($),I=useContext(Y),f=useRef(null),u=useRef(null),R=useRef(o);R.current=o;let[P,E]=useState(false),[A,W]=useState(false),k=useRef(null),G=useCallback(x=>{let w=Math.max(0,Math.min(x,e-1));a?r?.(w):d(w);},[a,r,e]),T=useCallback((x,w)=>{if(!i||i.size===0)return Math.max(0,Math.min(x+w,e-1));let b=x+w;for(;b>=0&&b<e&&i.has(b);)b+=w;return b<0||b>=e?x:b},[i,e]);useEffect(()=>{if(!(!v||!u.current||!f.current||!l))return v.register(u.current,f.current)},[v,l,P]),useEffect(()=>{if(!v||!u.current)return;let x=u.current;return W(v.focusedId===x),v.onFocusChange(w=>{W(w===x);})},[v,P]);let B=useCallback(x=>{let w=x?e-1:0,b=x?-1:1,M=w;for(;M>=0&&M<e&&i?.has(M);)M+=b;return M>=0&&M<e?M:x?e-1:0},[i,e]);useEffect(()=>{if(!I||!u.current||!l)return;let x=u.current,w=b=>v?.focusedId!==x?false:b.name==="g"&&!b.ctrl&&!b.alt?k.current==="g"?(G(B(false)),k.current=null,true):(k.current="g",true):b.name==="G"||b.name==="g"&&b.shift?(k.current=null,G(B(true)),true):(k.current=null,b.name==="up"||b.name==="k"?(G(T(S,-1)),true):b.name==="down"||b.name==="j"?(G(T(S,1)),true):b.name==="return"?(i?.has(S)||R.current?.(S),true):false);return I.registerInputHandler(x,w)},[I,v,l,S,G,T,B,i,P]);let C=[];for(let x=0;x<e;x++)C.push(ee.createElement(ee.Fragment,{key:x},t({index:x,selected:x===S,focused:A})));return ee.createElement("box",{style:{flexDirection:"column",...c},focusable:l,ref:x=>{x?(f.current=x,u.current=x.focusId,E(true)):(f.current=null,u.current=null,E(false));}},...C)}function jr({items:e,selectedIndex:t,onSelectionChange:n,onSelect:r,defaultSelectedIndex:o=0,style:s,highlightColor:i="cyan",focusable:c=true}){let l=new Set;for(let p=0;p<e.length;p++)e[p].disabled&&l.add(p);let a=p=>{let d=e[p];d&&!d.disabled&&r?.(d.value,p);};return ee.createElement(Ct,{count:e.length,selectedIndex:t,onSelectionChange:n,onSelect:a,defaultSelectedIndex:o,disabledIndices:l.size>0?l:void 0,style:s,focusable:c,renderItem:({index:p,selected:d,focused:S})=>{let v=e[p],I=v.disabled,f=d&&S,u=d?">":" ";return ee.createElement("box",{style:{flexDirection:"row",...f?{bg:i}:{}}},ee.createElement("text",{style:f?{bold:true,color:"black"}:I?{dim:true}:{}},`${u} ${v.label}`))}})}function Wr({value:e,indeterminate:t=false,width:n="100%",label:r,showPercent:o=false,style:s,filled:i="\u2588",empty:c="\u2591"}){let l=useRef(null),p=fe(l).innerWidth,[d,S]=useState(0);useEffect(()=>{if(!t)return;let R=setInterval(()=>{S(P=>(P+1)%Math.max(1,p+6));},100);return ()=>clearInterval(R)},[t,p]);let v=Math.max(0,Math.min(1,e??0)),I=o?` ${Math.round(v*100)}%`:"",f="";if(p>0)if(t&&e===void 0){let R=Math.max(1,Math.min(3,Math.floor(p/4))),P=[];for(let E=0;E<p;E++)E>=d-R&&E<d?P.push(i):P.push(c);f=P.join("");}else {let R=Math.round(v*p);f=i.repeat(R)+c.repeat(p-R);}let u=[];return r&&u.push(ee.createElement("text",{key:"label",style:{bold:true}},r+" ")),u.push(ee.createElement("box",{key:"track",style:{flexGrow:1,flexShrink:1},ref:R=>{l.current=R??null;}},ee.createElement("text",{key:"bar",style:{}},f))),o&&u.push(ee.createElement("text",{key:"pct",style:{bold:true}},I)),ee.createElement("box",{style:{flexDirection:"row",width:n,...s}},...u)}var $r=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function zr({frames:e=$r,intervalMs:t=80,label:n,style:r}){let[o,s]=useState(0);useEffect(()=>{let c=setInterval(()=>{s(l=>(l+1)%e.length);},t);return ()=>clearInterval(c)},[e.length,t]);let i=[ee.createElement("text",{key:"frame",style:r},e[o])];return n&&i.push(ee.createElement("text",{key:"label",style:{}}," "+n)),ee.createElement("box",{style:{flexDirection:"row"}},...i)}var Rn=createContext(null),Qr=0;function eo(){let e=useContext(Rn);if(!e)throw new Error("useToast must be used within a <ToastHost>");return e.push}var to={info:{bg:"blackBright",title:"cyanBright",text:"white"},success:{bg:"blackBright",title:"greenBright",text:"white"},warning:{bg:"blackBright",title:"yellowBright",text:"white"},error:{bg:"blackBright",title:"redBright",text:"white"}};function no({position:e="bottom-right",maxVisible:t=5,children:n}){let[r,o]=useState([]),s=useRef(new Map),i=useCallback(v=>{let I=`toast-${Qr++}`,f={id:I,durationMs:3e3,variant:"info",...v};if(o(u=>[...u,f]),f.durationMs&&f.durationMs>0){let u=setTimeout(()=>{s.current.delete(I),o(R=>R.filter(P=>P.id!==I));},f.durationMs);s.current.set(I,u);}},[]);useEffect(()=>()=>{for(let v of s.current.values())clearTimeout(v);s.current.clear();},[]);let c=useRef({push:i});c.current.push=i;let l=e.startsWith("top"),a=e.endsWith("right"),p={position:"absolute",top:0,left:0,width:"100%",height:"100%",zIndex:900,flexDirection:"column",justifyContent:l?"flex-start":"flex-end",alignItems:a?"flex-end":"flex-start",padding:1},S=r.slice(-t).map(v=>{let I=v.variant??"info",f=to[I],u=[];return v.title&&u.push(ee.createElement("text",{key:"title",style:{bold:true,color:f.title}},v.title)),u.push(ee.createElement("text",{key:"msg",style:{color:f.text}},v.message)),ee.createElement("box",{key:v.id,style:{bg:f.bg,paddingX:1,flexDirection:"column",minWidth:20,maxWidth:50}},...u)});return ee.createElement(Rn.Provider,{value:c.current},n,S.length>0?ee.createElement("box",{style:p},...S):null)}function oo({items:e,value:t,onChange:n,placeholder:r="Select...",style:o,focusedStyle:s,dropdownStyle:i,highlightColor:c="cyan",maxVisible:l=8,searchable:a=true,disabled:p}){let d=useContext($),S=useContext(Y),v=useContext(be),I=useContext(Ye),f=useRef(null),u=useRef(null),R=useRef(n);R.current=n;let[P,E]=useState(false),[A,W]=useState(false),[k,G]=useState(false),[T,B]=useState(0),[C,x]=useState(""),[w,b]=useState(0),M=fe(f),_=v?.rows??24,q=I?.getBounds(),j=e.find(F=>F.value===t)?.label??"",D=useMemo(()=>{if(!C)return e;let F=C.toLowerCase();return e.filter(h=>h.label.toLowerCase().includes(F))},[e,C]),L=Math.min(l,D.length),X=D.slice(w,w+L);useEffect(()=>{B(0),b(0);},[C]),useEffect(()=>{!A&&k&&(G(false),x(""));},[A,k]),useEffect(()=>{if(!(!d||!u.current||!f.current))return d.register(u.current,f.current)},[d,P]),useEffect(()=>{!d||!u.current||d.setSkippable(u.current,!!p);},[d,p,P]),useEffect(()=>{if(!d||!u.current)return;let F=u.current;return W(d.focusedId===F),d.onFocusChange(h=>{W(h===F);})},[d,P]);let J=useCallback((F,h)=>{let N=F+h;for(;N>=0&&N<D.length;){if(!D[N].disabled)return N;N+=h;}return F},[D]),se=useCallback(F=>{F<w?b(F):F>=w+L&&b(F-L+1);},[w,L]);useEffect(()=>{if(!S||!u.current||p)return;let F=u.current,h=N=>{if(d?.focusedId!==F)return false;if(!k){if(N.name==="return"||N.name===" "||N.sequence===" "||N.name==="down"){G(true),x("");let V=D.findIndex(H=>H.value===t),Q=V>=0?V:0;return B(Q),b(Math.max(0,Q-Math.floor(l/2))),true}return false}if(N.name==="tab")return G(false),x(""),false;if(N.name==="escape")return G(false),x(""),true;if(N.name==="return"){let V=D[T];return V&&!V.disabled&&(R.current?.(V.value),G(false),x("")),true}if(N.name==="up"){let V=J(T,-1);return B(V),se(V),true}if(N.name==="down"){let V=J(T,1);return B(V),se(V),true}if(N.name==="backspace")return a&&C.length>0&&x(V=>V.slice(0,-1)),true;if(N.name==="home"){let V=J(-1,1);return B(V),se(V),true}if(N.name==="end"){let V=J(D.length,-1);return B(V),se(V),true}if(a&&N.sequence&&N.sequence.length===1&&!N.ctrl&&!N.alt){let V=N.sequence;if(V>=" "&&V<="~")return x(Q=>Q+V),true}return true};return S.registerInputHandler(F,h)},[S,d,p,k,T,D,t,l,a,C,J,se,P]);let m={flexDirection:"row",width:"100%",...!o?.bg&&o?.border===void 0?{border:"single"}:{},...o,...A&&s?s:{}},g=j?o?.color??void 0:"blackBright",y=[ee.createElement("text",{key:"label",style:{flexGrow:1,flexShrink:1,color:g,wrap:"ellipsis",...j?{}:{dim:true}}},j||r),ee.createElement("text",{key:"arrow",style:{flexShrink:0,color:A?c:"blackBright"}},k?" \u25B2":" \u25BC")],O=null;if(k){let F=[];a&&C&&F.push(ee.createElement("box",{key:"search",style:{paddingX:1}},ee.createElement("text",{style:{color:"blackBright",dim:true}},`/${C}`))),D.length===0&&F.push(ee.createElement("box",{key:"empty",style:{paddingX:1}},ee.createElement("text",{style:{dim:true,color:"blackBright"}},"No matches"))),w>0&&F.push(ee.createElement("box",{key:"scroll-up",style:{justifyContent:"center",alignItems:"center"}},ee.createElement("text",{style:{dim:true,color:"blackBright"}},"\u25B2"))),X.forEach((ve,ot)=>{let We=w+ot===T,ye=ve.disabled,Mn={paddingX:1,...We&&!ye?{bg:c}:{}},Vn={...We&&!ye?{color:"black",bold:true}:{},...ye?{dim:true,color:"blackBright"}:{}};F.push(ee.createElement("box",{key:`item-${ve.value}`,style:Mn},ee.createElement("text",{style:Vn},ve.label)));}),w+L<D.length&&F.push(ee.createElement("box",{key:"scroll-down",style:{justifyContent:"center",alignItems:"center"}},ee.createElement("text",{style:{dim:true,color:"blackBright"}},"\u25BC")));let h=w>0,N=w+L<D.length,V=a&&C,Q=D.length===0,H=!i?.bg&&i?.border===void 0,Z=L+(H?2:0);h&&(Z+=1),N&&(Z+=1),V&&(Z+=1),Q&&(Z+=1);let pe=M.y+M.height,ge,Re;q?(ge=q.visibleBottom-pe,Re=M.y-q.visibleTop):(ge=_-pe,Re=M.y);let De=ge<Z&&Re>=Z?-Z:M.height||1;O=ee.createElement("box",{style:{position:"absolute",top:De,left:0,right:0,zIndex:9999,...H?{border:"single"}:{},bg:"black",flexDirection:"column",...i}},...F);}let K={flexDirection:"column",width:m.width??"100%",minWidth:m.minWidth,maxWidth:m.maxWidth,flexGrow:m.flexGrow,flexShrink:m.flexShrink??1};return ee.createElement("box",{style:K},ee.createElement("box",{style:m,focusable:true,ref:F=>{F?(f.current=F,u.current=F.focusId,E(true)):(f.current=null,u.current=null,E(false));}},...y),O)}function so({checked:e,onChange:t,label:n,style:r,focusedStyle:o,disabled:s,checkedChar:i="\u2713",uncheckedChar:c=" "}){let l=useContext($),a=useContext(Y),p=useRef(null),d=useRef(null),S=useRef(t);S.current=t;let v=useRef(e);v.current=e;let[I,f]=useState(false),[u,R]=useState(false);useEffect(()=>{if(!(!l||!d.current||!p.current||s))return l.register(d.current,p.current)},[l,s,I]),useEffect(()=>{if(!l||!d.current)return;let k=d.current;return R(l.focusedId===k),l.onFocusChange(G=>{R(G===k);})},[l,I]),useEffect(()=>{if(!a||!d.current||s)return;let k=d.current,G=T=>l?.focusedId!==k?false:T.name==="return"||T.name===" "||T.sequence===" "?(S.current(!v.current),true):false;return a.registerInputHandler(k,G)},[a,l,s,I]);let P={flexDirection:"row",gap:1,...r,...u&&o?o:{}},E=e?i:c,A={color:s?"blackBright":u?"white":r?.color},W={color:s?"blackBright":r?.color};return ee.createElement("box",{style:P,focusable:!s,ref:k=>{k?(p.current=k,d.current=k.focusId,f(true)):(p.current=null,d.current=null,f(false));}},ee.createElement("text",{key:"box",style:A},`[${E}]`),n?ee.createElement("text",{key:"label",style:W},n):null)}function lo({items:e,value:t,onChange:n,style:r,itemStyle:o,focusedItemStyle:s,selectedItemStyle:i,disabled:c,direction:l="column",gap:a=0,selectedChar:p="\u25CF",unselectedChar:d="\u25CB"}){let S=useContext($),v=useContext(Y),I=useRef(null),f=useRef(null),u=useRef(n);u.current=n;let[R,P]=useState(false),[E,A]=useState(false),[W,k]=useState(()=>{let C=e.findIndex(x=>x.value===t);return C>=0?C:e.findIndex(x=>!x.disabled)}),G=useCallback((C,x)=>{let w=C;for(let b=0;b<e.length;b++)if(w=(w+x+e.length)%e.length,!e[w]?.disabled)return w;return C},[e]);useEffect(()=>{if(!(!S||!f.current||!I.current||c))return S.register(f.current,I.current)},[S,c,R]),useEffect(()=>{if(!S||!f.current)return;let C=f.current;return A(S.focusedId===C),S.onFocusChange(x=>{A(x===C);})},[S,R]),useEffect(()=>{if(!v||!f.current||c)return;let C=f.current,x=w=>{if(S?.focusedId!==C)return false;if(w.name==="up"||w.name==="left"||w.name==="k"||w.name==="tab"&&w.shift)return k(b=>G(b,-1)),true;if(w.name==="down"||w.name==="right"||w.name==="j"||w.name==="tab"&&!w.shift)return k(b=>G(b,1)),true;if(w.name==="return"||w.name===" "||w.sequence===" "){let b=e[W];return b&&!b.disabled&&u.current(b.value),true}return false};return v.registerInputHandler(C,x)},[v,S,c,e,W,G,R]),useEffect(()=>{let C=e.findIndex(x=>x.value===t);C>=0&&k(C);},[t,e]);let T={flexDirection:l,gap:a,...r},B=e.map((C,x)=>{let w=C.value===t,b=x===W,M=c||C.disabled,_=w?p:d,q={flexDirection:"row",gap:1,...o};w&&i&&(q={...q,...i}),E&&b&&s&&(q={...q,...s});let z=M?"blackBright":E&&b?s?.color??"white":w?i?.color??o?.color:o?.color;return ee.createElement("box",{key:x,style:q},ee.createElement("text",{key:"radio",style:{color:z}},`(${_})`),ee.createElement("text",{key:"label",style:{color:z}},C.label))});return ee.createElement("box",{style:T,focusable:!c,ref:C=>{C?(I.current=C,f.current=C.focusId,P(true)):(I.current=null,f.current=null,P(false));}},...B)}var kn=createContext(null);function uo(){let e=useContext(kn);if(!e)throw new Error("useDialog must be used within a DialogHost");return e}function ao({children:e}){let[t,n]=useState([]),r=useRef(0),o=useCallback((a,p)=>new Promise(d=>{let S=++r.current;n(v=>[...v,{id:S,type:"alert",content:a,okText:p?.okText??"OK",cancelText:"",style:p?.style,resolve:()=>d()}]);}),[]),s=useCallback((a,p)=>new Promise(d=>{let S=++r.current;n(v=>[...v,{id:S,type:"confirm",content:a,okText:p?.okText??"OK",cancelText:p?.cancelText??"Cancel",style:p?.style,resolve:d}]);}),[]),i=useCallback((a,p)=>{n(d=>{let S=d.find(v=>v.id===a);return S&&S.resolve(p),d.filter(v=>v.id!==a)});},[]),c={alert:o,confirm:s},l=t[t.length-1];return ee.createElement(kn.Provider,{value:c},e,l&&ee.createElement(fo,{key:l.id,dialog:l,onDismiss:i}))}function fo({dialog:e,onDismiss:t}){let n=useContext($),r=useRef(null),o=useRef(null),s=useRef(null),i=useRef(null),[c,l]=useState("ok"),[a,p]=useState(0);useEffect(()=>{if(!n||a===0)return;let u=[];return r.current&&s.current&&u.push(n.register(s.current,r.current)),o.current&&i.current&&u.push(n.register(i.current,o.current)),s.current&&n.requestFocus(s.current),()=>u.forEach(R=>R())},[n,a]),useEffect(()=>{if(n)return n.onFocusChange(u=>{u===s.current?l("ok"):u===i.current&&l("cancel");})},[n]),Be(u=>{if(u.name==="return"||u.name==="space"){e.type==="alert"?t(e.id,true):t(e.id,c==="ok");return}if(u.name==="escape"){t(e.id,false);return}e.type==="confirm"&&n&&(u.name==="left"||u.name==="right")&&(c==="ok"&&i.current?n.requestFocus(i.current):s.current&&n.requestFocus(s.current));},[e,c,n,t]);let d=typeof e.content=="string",S=d?e.content.length:0,I={minWidth:Math.max(20,d?Math.min(S+6,50):30),maxWidth:50,bg:"black",border:"round",borderColor:"white",padding:1,flexDirection:"column",gap:1,...e.style},f=u=>({paddingX:2,bg:u?"white":"blackBright",color:u?"black":"white",bold:u});return ee.createElement(ft,{trap:true},ee.createElement("box",{style:{position:"absolute",top:0,left:0,right:0,bottom:0,zIndex:999}}),ee.createElement("box",{style:{position:"absolute",top:0,left:0,right:0,bottom:0,justifyContent:"center",alignItems:"center",zIndex:1e3}},ee.createElement("box",{style:I},ee.createElement("box",{style:{flexDirection:"column"}},typeof e.content=="string"?ee.createElement("text",null,e.content):e.content),ee.createElement("box",{style:{flexDirection:"row",justifyContent:"flex-end",gap:1}},e.type==="confirm"&&ee.createElement("box",{style:f(c==="cancel"),focusable:true,ref:u=>{u&&u.focusId&&!i.current&&(o.current=u,i.current=u.focusId,p(R=>R+1));}},ee.createElement("text",null,e.cancelText)),ee.createElement("box",{style:f(c==="ok"),focusable:true,ref:u=>{u&&u.focusId&&!s.current&&(r.current=u,s.current=u.focusId,p(R=>R+1));}},ee.createElement("text",null,e.okText))))))}function ho(e,t){let n=[],r=t.split("");if(e<=r.length)for(let o=0;o<e;o++)n.push(r[o]);else for(let o=0;o<r.length&&n.length<e;o++)for(let s=0;s<r.length&&n.length<e;s++)n.push(r[o]+r[s]);return n}function go({children:e,activationKey:t="ctrl+o",hintStyle:n,hintBg:r="yellow",hintFg:o="black",hintChars:s="asdfghjklqwertyuiopzxcvbnm",enabled:i=true,debug:c=false}){let l=c?(...C)=>console.error("[JumpNav]",...C):()=>{},[a,p]=useState(false),[d,S]=useState(""),[v,I]=useState([]),f=useContext(Y),u=useContext($),R=useContext(oe);useEffect(()=>{l("Mounted, inputCtx:",!!f,"focusCtx:",!!u,"enabled:",i);},[]);let E=useCallback(C=>{let x=C.toLowerCase().split("+");return {ctrl:x.includes("ctrl"),alt:x.includes("alt"),shift:x.includes("shift"),meta:x.includes("meta"),name:x[x.length-1]??""}},[])(t),A=useCallback(()=>{if(!u?.getActiveElements){l("refreshElements: no getActiveElements");return}let C=u.getActiveElements();l("getActiveElements returned",C.length,"elements");let x=C.map(({id:w,node:b})=>({id:w,node:b,layout:R?.getLayout(b)??b.layout}));x.sort((w,b)=>w.layout.y!==b.layout.y?w.layout.y-b.layout.y:w.layout.x-b.layout.x),I(x);},[u,R,l]),W=useRef(false);useEffect(()=>{a&&!W.current&&(l("Activated! Refreshing elements..."),A()),W.current=a;},[a,A,l]);let k=v.filter(C=>C.layout.width>0&&C.layout.height>0),G=ho(k.length,s),T=useMemo(()=>{let C=new Map;return k.forEach((x,w)=>{G[w]&&C.set(G[w],x.id);}),C},[k,G]);useEffect(()=>{if(!f||!i){l("Not subscribing - inputCtx:",!!f,"enabled:",i);return}l("Subscribing to priority input, activation key:",t);let C=x=>{let w=x.name===E.name,b=!!x.ctrl===E.ctrl,M=!!x.alt===E.alt,_=!!x.shift===E.shift,q=!!x.meta===E.meta;if(!a&&w&&b&&M&&_&&q)return l("Activation key matched! Activating..."),p(true),S(""),true;if(a){if(x.name==="escape")return l("Escape pressed, deactivating"),p(false),S(""),true;if(x.name==="backspace")return S(""),true;if(x.sequence&&x.sequence.length===1&&/[a-z]/i.test(x.sequence)){let z=d+x.sequence.toLowerCase();l("Buffer:",z);let j=T.get(z);return j?(l("Jumping to",j),u?.requestFocus(j),p(false),S(""),true):[...T.keys()].some(L=>L.startsWith(z))?(S(z),true):(S(""),true)}return true}return false};return f.subscribePriority(C)},[f,i,a,E,d,T,u,t,l]);let B=a?ee.createElement("box",{style:{position:"absolute",top:0,left:0,width:"100%",height:"100%",zIndex:99998}},...k.map((C,x)=>{let w=G[x];if(!w)return null;let{x:b,y:M}=C.layout,_=w.startsWith(d)&&d.length>0;return ee.createElement("box",{key:C.id,style:{position:"absolute",top:M,left:Math.max(0,b-w.length-2),bg:_?"cyan":r,color:o,paddingX:1,zIndex:99999,...n}},ee.createElement("text",{style:{bold:true,color:o}},w))}),ee.createElement("box",{style:{position:"absolute",bottom:0,left:0,right:0,bg:"blackBright",paddingX:1,zIndex:99999}},ee.createElement("text",{style:{color:"white"}},d?`Jump: ${d}_`:"Press a key to jump \u2022 ESC to cancel"))):null;return ee.createElement(ee.Fragment,null,e,B)}function So(e){let t=useContext($),[n]=useState(()=>`focus-${Math.random().toString(36).slice(2,9)}`),r=t?t.focusedId===n:false;useEffect(()=>{if(!(!t||!e?.current))return e.current.focusId=n,t.register(n,e.current)},[t,n,e]);let o=useMemo(()=>()=>{t?.requestFocus(n);},[t,n]);return {focused:r,focus:o}}function Ro(e={}){let{disabled:t,onFocus:n,onBlur:r,onKeyPress:o}=e,s=useContext($),i=useContext(Y),c=useRef(null),l=useRef(null),[a,p]=useState(false),d=useRef(n),S=useRef(r),v=useRef(o);d.current=n,S.current=r,v.current=o;let I=useCallback(u=>{c.current=u,u?l.current=u.focusId??null:l.current=null;},[]);useEffect(()=>{if(!(!s||!l.current||!c.current))return s.register(l.current,c.current)},[s]),useEffect(()=>{!s||!l.current||s.setSkippable(l.current,!!t);},[s,t]),useEffect(()=>{if(!s||!l.current)return;let u=l.current,R=s.focusedId===u;return p(R),s.onFocusChange(P=>{let E=P===u;p(A=>(E&&!A?d.current?.():!E&&A&&S.current?.(),E));})},[s]),useEffect(()=>{if(!i||!l.current||t)return;let u=l.current,R=P=>s?.focusedId!==u?false:v.current?.(P)===true;return i.registerInputHandler(u,R)},[i,s,t]);let f=useCallback(()=>{s&&l.current&&s.requestFocus(l.current);},[s]);return {ref:I,isFocused:a,focus:f,focusId:l.current}}function Io(){let e=useContext(be);if(!e)throw new Error("useApp must be used within a Glyph render tree");return {exit:e.exit,get columns(){return e.columns},get rows(){return e.rows}}}function ko(){let e=useContext($),t=useContext(oe),[n,r]=useState([]),o=useRef(()=>{}),s=useCallback(()=>{if(!e)return;let c=(e.getActiveElements?.()??e.getRegisteredElements?.()??[]).map(({id:l,node:a})=>({id:l,node:a,layout:t?.getLayout(a)??a.layout,type:a.type}));c.sort((l,a)=>l.layout.y!==a.layout.y?l.layout.y-a.layout.y:l.layout.x-a.layout.x),r(c);},[e,t]);return o.current=s,useEffect(()=>{if(!e)return;s();let i=e.onFocusChange(()=>{s();}),c=setTimeout(s,50);return ()=>{i(),clearTimeout(c);}},[e,t,s]),e?{elements:n,focusedId:e.focusedId,requestFocus:e.requestFocus,focusNext:e.focusNext,focusPrev:e.focusPrev,refresh:()=>o.current()}:null}function Fo(e){let t=[];for(let n of e)switch(n){case "9":t.push({type:"digit",char:n});break;case "a":t.push({type:"letter",char:n});break;case "*":t.push({type:"alphanumeric",char:n});break;default:t.push({type:"literal",char:n});break}return t}function Go(e,t){switch(t){case "digit":return /\d/.test(e);case "letter":return /[a-zA-Z]/.test(e);case "alphanumeric":return /[a-zA-Z0-9]/.test(e);case "literal":return true}}function te(e){let t=typeof e=="string"?{mask:e}:e,{mask:n,placeholder:r="_",showPlaceholder:o=false}=t,s=Fo(n);return (i,c)=>{let l=[];for(let d of i)(d!==r&&!/[\s\-\(\)\/\.\:]/.test(d)||/[a-zA-Z0-9]/.test(d))&&/[a-zA-Z0-9]/.test(d)&&l.push(d);let a="",p=0;for(let d of s)if(d.type==="literal")(p<l.length||o)&&(a+=d.char);else if(p<l.length){let S=l[p];if(Go(S,d.type))a+=S,p++;else {p++;continue}}else o&&(a+=r);return a}}var Lo={usPhone:te("(999) 999-9999"),intlPhone:te("+9 999 999 9999"),dateUS:te("99/99/9999"),dateEU:te("99/99/9999"),dateISO:te("9999-99-99"),time:te("99:99"),timeFull:te("99:99:99"),creditCard:te("9999 9999 9999 9999"),ssn:te("999-99-9999"),zip:te("99999"),zipPlus4:te("99999-9999"),ipv4:te("999.999.999.999"),mac:te("**:**:**:**:**:**")};export{dr as Box,Er as Button,so as Checkbox,ao as DialogHost,ft as FocusScope,xr as Input,go as JumpNav,Ir as Keybind,Ct as List,jr as Menu,Nr as Portal,Wr as Progress,lo as Radio,Ar as ScrollView,oo as Select,Rr as Spacer,zr as Spinner,gr as Text,no as ToastHost,te as createMask,Lo as masks,ar as render,Io as useApp,uo as useDialog,So as useFocus,ko as useFocusRegistry,Ro as useFocusable,Be as useInput,fe as useLayout,eo as useToast};//# sourceMappingURL=index.js.map
4807
15
  //# sourceMappingURL=index.js.map