semajsx 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{client-BrupjhG0.mjs → client-CEJQ4fit.mjs} +3 -3
- package/dist/{client-BrupjhG0.mjs.map → client-CEJQ4fit.mjs.map} +1 -1
- package/dist/{document-DsiJO2jG.mjs → document-Cbz4084O.mjs} +2 -2
- package/dist/{document-XKyAs62C.mjs → document-Cfdhi7vG.mjs} +2 -2
- package/dist/{document-XKyAs62C.mjs.map → document-Cfdhi7vG.mjs.map} +1 -1
- package/dist/dom/index.mjs +2 -2
- package/dist/dom/jsx-dev-runtime.mjs +1 -1
- package/dist/dom/jsx-runtime.mjs +1 -1
- package/dist/index.d.mts +11 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{jsx-runtime-Dc77fsnM.d.mts → jsx-runtime-tdaY-P9K.d.mts} +2 -2
- package/dist/{jsx-runtime-Dc77fsnM.d.mts.map → jsx-runtime-tdaY-P9K.d.mts.map} +1 -1
- package/dist/{lucide-Ddt_N9dJ.mjs → lucide-DWk3itzO.mjs} +3 -3
- package/dist/{lucide-Ddt_N9dJ.mjs.map → lucide-DWk3itzO.mjs.map} +1 -1
- package/dist/{resource-pm7qP-jV.mjs → resource-BU0Po0ez.mjs} +2 -2
- package/dist/{resource-pm7qP-jV.mjs.map → resource-BU0Po0ez.mjs.map} +1 -1
- package/dist/{src-Cv4rRVzv.mjs → src--YS4EvMz.mjs} +9 -6
- package/dist/src--YS4EvMz.mjs.map +1 -0
- package/dist/{src-CXY-7FC3.mjs → src-77V1Plyd.mjs} +665 -129
- package/dist/src-77V1Plyd.mjs.map +1 -0
- package/dist/{src-SqJ6k7Xv.mjs → src-BTG08Qnh.mjs} +4 -4
- package/dist/{src-SqJ6k7Xv.mjs.map → src-BTG08Qnh.mjs.map} +1 -1
- package/dist/{src-C_aFsFJ3.mjs → src-Cm12Y2XV.mjs} +2 -2
- package/dist/{src-C_aFsFJ3.mjs.map → src-Cm12Y2XV.mjs.map} +1 -1
- package/dist/{src-CAyv9Uf9.mjs → src-Mucdq4zw.mjs} +6 -6
- package/dist/{src-CAyv9Uf9.mjs.map → src-Mucdq4zw.mjs.map} +1 -1
- package/dist/ssg/index.mjs +6 -6
- package/dist/ssg/plugins/docs-theme.mjs +9 -9
- package/dist/ssg/plugins/lucide.mjs +3 -3
- package/dist/ssr/client.mjs +4 -4
- package/dist/ssr/index.mjs +5 -5
- package/dist/terminal/index.d.mts +248 -4
- package/dist/terminal/index.d.mts.map +1 -1
- package/dist/terminal/index.mjs +3 -3
- package/dist/terminal/jsx-dev-runtime.d.mts +2 -2
- package/dist/terminal/jsx-dev-runtime.mjs +1 -1
- package/dist/terminal/jsx-runtime.d.mts +2 -2
- package/dist/terminal/jsx-runtime.mjs +1 -1
- package/dist/{types-Bj5q5x2Q.d.mts → types-Bm8rZGKW.d.mts} +2 -2
- package/dist/{types-Bj5q5x2Q.d.mts.map → types-Bm8rZGKW.d.mts.map} +1 -1
- package/package.json +1 -1
- package/dist/src-CXY-7FC3.mjs.map +0 -1
- package/dist/src-Cv4rRVzv.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as signal } from "./signal-4PgGfydw.mjs";
|
|
2
2
|
import { t as computed } from "./computed-BpjqvQu1.mjs";
|
|
3
|
-
import { c as when, i as jsx, t as createRenderer, v as Fragment } from "./src
|
|
3
|
+
import { a as jsxs, c as when, i as jsx, t as createRenderer, v as Fragment } from "./src--YS4EvMz.mjs";
|
|
4
4
|
import Yoga from "yoga-layout-prebuilt";
|
|
5
5
|
import ansiEscapes from "ansi-escapes";
|
|
6
6
|
import stringWidth from "string-width";
|
|
@@ -11,54 +11,62 @@ import wrapAnsi from "wrap-ansi";
|
|
|
11
11
|
|
|
12
12
|
//#region ../terminal/src/utils/colors.ts
|
|
13
13
|
/**
|
|
14
|
+
* Color name to chalk function mapping (hoisted to module level for performance)
|
|
15
|
+
*/
|
|
16
|
+
const colors = {
|
|
17
|
+
black: chalk.black,
|
|
18
|
+
red: chalk.red,
|
|
19
|
+
green: chalk.green,
|
|
20
|
+
yellow: chalk.yellow,
|
|
21
|
+
blue: chalk.blue,
|
|
22
|
+
magenta: chalk.magenta,
|
|
23
|
+
cyan: chalk.cyan,
|
|
24
|
+
white: chalk.white,
|
|
25
|
+
gray: chalk.gray,
|
|
26
|
+
grey: chalk.grey,
|
|
27
|
+
blackBright: chalk.blackBright,
|
|
28
|
+
redBright: chalk.redBright,
|
|
29
|
+
greenBright: chalk.greenBright,
|
|
30
|
+
yellowBright: chalk.yellowBright,
|
|
31
|
+
blueBright: chalk.blueBright,
|
|
32
|
+
magentaBright: chalk.magentaBright,
|
|
33
|
+
cyanBright: chalk.cyanBright,
|
|
34
|
+
whiteBright: chalk.whiteBright
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Background color name to chalk function mapping (hoisted to module level for performance)
|
|
38
|
+
*/
|
|
39
|
+
const bgColors = {
|
|
40
|
+
black: chalk.bgBlack,
|
|
41
|
+
red: chalk.bgRed,
|
|
42
|
+
green: chalk.bgGreen,
|
|
43
|
+
yellow: chalk.bgYellow,
|
|
44
|
+
blue: chalk.bgBlue,
|
|
45
|
+
magenta: chalk.bgMagenta,
|
|
46
|
+
cyan: chalk.bgCyan,
|
|
47
|
+
white: chalk.bgWhite,
|
|
48
|
+
gray: chalk.bgGray,
|
|
49
|
+
grey: chalk.bgGrey,
|
|
50
|
+
blackBright: chalk.bgBlackBright,
|
|
51
|
+
redBright: chalk.bgRedBright,
|
|
52
|
+
greenBright: chalk.bgGreenBright,
|
|
53
|
+
yellowBright: chalk.bgYellowBright,
|
|
54
|
+
blueBright: chalk.bgBlueBright,
|
|
55
|
+
magentaBright: chalk.bgMagentaBright,
|
|
56
|
+
cyanBright: chalk.bgCyanBright,
|
|
57
|
+
whiteBright: chalk.bgWhiteBright
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
14
60
|
* Get a chalk color function by name
|
|
15
61
|
*/
|
|
16
62
|
function getChalkColor(colorName) {
|
|
17
|
-
return
|
|
18
|
-
black: chalk.black,
|
|
19
|
-
red: chalk.red,
|
|
20
|
-
green: chalk.green,
|
|
21
|
-
yellow: chalk.yellow,
|
|
22
|
-
blue: chalk.blue,
|
|
23
|
-
magenta: chalk.magenta,
|
|
24
|
-
cyan: chalk.cyan,
|
|
25
|
-
white: chalk.white,
|
|
26
|
-
gray: chalk.gray,
|
|
27
|
-
grey: chalk.grey,
|
|
28
|
-
blackBright: chalk.blackBright,
|
|
29
|
-
redBright: chalk.redBright,
|
|
30
|
-
greenBright: chalk.greenBright,
|
|
31
|
-
yellowBright: chalk.yellowBright,
|
|
32
|
-
blueBright: chalk.blueBright,
|
|
33
|
-
magentaBright: chalk.magentaBright,
|
|
34
|
-
cyanBright: chalk.cyanBright,
|
|
35
|
-
whiteBright: chalk.whiteBright
|
|
36
|
-
}[colorName] || chalk;
|
|
63
|
+
return colors[colorName] || chalk;
|
|
37
64
|
}
|
|
38
65
|
/**
|
|
39
66
|
* Get a chalk background color function by name
|
|
40
67
|
*/
|
|
41
68
|
function getChalkBgColor(colorName) {
|
|
42
|
-
return
|
|
43
|
-
black: chalk.bgBlack,
|
|
44
|
-
red: chalk.bgRed,
|
|
45
|
-
green: chalk.bgGreen,
|
|
46
|
-
yellow: chalk.bgYellow,
|
|
47
|
-
blue: chalk.bgBlue,
|
|
48
|
-
magenta: chalk.bgMagenta,
|
|
49
|
-
cyan: chalk.bgCyan,
|
|
50
|
-
white: chalk.bgWhite,
|
|
51
|
-
gray: chalk.bgGray,
|
|
52
|
-
grey: chalk.bgGrey,
|
|
53
|
-
blackBright: chalk.bgBlackBright,
|
|
54
|
-
redBright: chalk.bgRedBright,
|
|
55
|
-
greenBright: chalk.bgGreenBright,
|
|
56
|
-
yellowBright: chalk.bgYellowBright,
|
|
57
|
-
blueBright: chalk.bgBlueBright,
|
|
58
|
-
magentaBright: chalk.bgMagentaBright,
|
|
59
|
-
cyanBright: chalk.bgCyanBright,
|
|
60
|
-
whiteBright: chalk.bgWhiteBright
|
|
61
|
-
}[colorName] || chalk;
|
|
69
|
+
return bgColors[colorName] || chalk;
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
//#endregion
|
|
@@ -378,7 +386,8 @@ var TerminalRenderer = class {
|
|
|
378
386
|
this.buffer = [];
|
|
379
387
|
this.previousOutput = "";
|
|
380
388
|
this.lastOutputHeight = 0;
|
|
381
|
-
this.
|
|
389
|
+
this.resizeHandler = null;
|
|
390
|
+
this.resizeCallback = null;
|
|
382
391
|
this.root = {
|
|
383
392
|
type: "root",
|
|
384
393
|
stream,
|
|
@@ -393,11 +402,22 @@ var TerminalRenderer = class {
|
|
|
393
402
|
this.root.yogaNode.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
|
|
394
403
|
this.root.yogaNode.setAlignItems(Yoga.ALIGN_FLEX_START);
|
|
395
404
|
}
|
|
396
|
-
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
397
|
-
this.wasRawMode = process.stdin.isRaw || false;
|
|
398
|
-
if (!this.wasRawMode) process.stdin.setRawMode(true);
|
|
399
|
-
}
|
|
400
405
|
this.root.stream.write(ansiEscapes.cursorHide);
|
|
406
|
+
this.resizeHandler = () => {
|
|
407
|
+
const { columns, rows } = this.root.stream;
|
|
408
|
+
if (this.root.yogaNode) {
|
|
409
|
+
this.root.yogaNode.setWidth(columns || 80);
|
|
410
|
+
this.root.yogaNode.setHeight(rows || 24);
|
|
411
|
+
}
|
|
412
|
+
if (this.resizeCallback) this.resizeCallback();
|
|
413
|
+
};
|
|
414
|
+
this.root.stream.on("resize", this.resizeHandler);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Set a callback to be called on terminal resize (triggers re-render).
|
|
418
|
+
*/
|
|
419
|
+
setResizeCallback(callback) {
|
|
420
|
+
this.resizeCallback = callback;
|
|
401
421
|
}
|
|
402
422
|
/**
|
|
403
423
|
* Get the root node
|
|
@@ -515,7 +535,10 @@ var TerminalRenderer = class {
|
|
|
515
535
|
this.render();
|
|
516
536
|
this.root.stream.write("\n");
|
|
517
537
|
this.root.stream.write(ansiEscapes.cursorShow);
|
|
518
|
-
if (
|
|
538
|
+
if (this.resizeHandler) {
|
|
539
|
+
this.root.stream.removeListener("resize", this.resizeHandler);
|
|
540
|
+
this.resizeHandler = null;
|
|
541
|
+
}
|
|
519
542
|
if (this.root.yogaNode) this.root.yogaNode.freeRecursive();
|
|
520
543
|
}
|
|
521
544
|
};
|
|
@@ -543,99 +566,392 @@ function setSignalProperty(node, key, signal) {
|
|
|
543
566
|
});
|
|
544
567
|
}
|
|
545
568
|
/**
|
|
569
|
+
* Set of all recognized style property names (hoisted to module level for performance)
|
|
570
|
+
*/
|
|
571
|
+
const styleProps = new Set([
|
|
572
|
+
"flexDirection",
|
|
573
|
+
"justifyContent",
|
|
574
|
+
"alignItems",
|
|
575
|
+
"flexGrow",
|
|
576
|
+
"flexShrink",
|
|
577
|
+
"flexBasis",
|
|
578
|
+
"width",
|
|
579
|
+
"height",
|
|
580
|
+
"minWidth",
|
|
581
|
+
"minHeight",
|
|
582
|
+
"maxWidth",
|
|
583
|
+
"maxHeight",
|
|
584
|
+
"margin",
|
|
585
|
+
"marginLeft",
|
|
586
|
+
"marginRight",
|
|
587
|
+
"marginTop",
|
|
588
|
+
"marginBottom",
|
|
589
|
+
"marginInline",
|
|
590
|
+
"marginBlock",
|
|
591
|
+
"padding",
|
|
592
|
+
"paddingLeft",
|
|
593
|
+
"paddingRight",
|
|
594
|
+
"paddingTop",
|
|
595
|
+
"paddingBottom",
|
|
596
|
+
"paddingInline",
|
|
597
|
+
"paddingBlock",
|
|
598
|
+
"border",
|
|
599
|
+
"borderColor",
|
|
600
|
+
"color",
|
|
601
|
+
"backgroundColor",
|
|
602
|
+
"bold",
|
|
603
|
+
"italic",
|
|
604
|
+
"underline",
|
|
605
|
+
"strikethrough",
|
|
606
|
+
"dim"
|
|
607
|
+
]);
|
|
608
|
+
/**
|
|
546
609
|
* Check if a property is a style property
|
|
547
610
|
*/
|
|
548
611
|
function isStyleProperty(key) {
|
|
549
|
-
return
|
|
550
|
-
"flexDirection",
|
|
551
|
-
"justifyContent",
|
|
552
|
-
"alignItems",
|
|
553
|
-
"flexGrow",
|
|
554
|
-
"flexShrink",
|
|
555
|
-
"flexBasis",
|
|
556
|
-
"width",
|
|
557
|
-
"height",
|
|
558
|
-
"minWidth",
|
|
559
|
-
"minHeight",
|
|
560
|
-
"maxWidth",
|
|
561
|
-
"maxHeight",
|
|
562
|
-
"margin",
|
|
563
|
-
"marginLeft",
|
|
564
|
-
"marginRight",
|
|
565
|
-
"marginTop",
|
|
566
|
-
"marginBottom",
|
|
567
|
-
"marginInline",
|
|
568
|
-
"marginBlock",
|
|
569
|
-
"padding",
|
|
570
|
-
"paddingLeft",
|
|
571
|
-
"paddingRight",
|
|
572
|
-
"paddingTop",
|
|
573
|
-
"paddingBottom",
|
|
574
|
-
"paddingInline",
|
|
575
|
-
"paddingBlock",
|
|
576
|
-
"border",
|
|
577
|
-
"borderColor",
|
|
578
|
-
"color",
|
|
579
|
-
"backgroundColor",
|
|
580
|
-
"bold",
|
|
581
|
-
"italic",
|
|
582
|
-
"underline",
|
|
583
|
-
"strikethrough",
|
|
584
|
-
"dim"
|
|
585
|
-
]).has(key);
|
|
612
|
+
return styleProps.has(key);
|
|
586
613
|
}
|
|
587
614
|
|
|
588
615
|
//#endregion
|
|
589
|
-
//#region ../terminal/src/
|
|
590
|
-
/** @jsxImportSource @semajsx/terminal */
|
|
616
|
+
//#region ../terminal/src/context.ts
|
|
591
617
|
/**
|
|
592
|
-
*
|
|
593
|
-
* Set to true during unmount to hide exit hints in final render
|
|
618
|
+
* The active terminal session. Set by render(), cleared on unmount.
|
|
594
619
|
*/
|
|
595
|
-
|
|
620
|
+
let activeSession = null;
|
|
596
621
|
/**
|
|
597
|
-
*
|
|
598
|
-
* Used internally by render() to coordinate with ExitHint component
|
|
622
|
+
* Create a fresh terminal session
|
|
599
623
|
*/
|
|
600
|
-
function
|
|
601
|
-
return
|
|
624
|
+
function createTerminalSession() {
|
|
625
|
+
return {
|
|
626
|
+
keyboardListeners: [],
|
|
627
|
+
lastKeySignal: signal(null),
|
|
628
|
+
keyboardInstalled: false,
|
|
629
|
+
stdinHandler: null,
|
|
630
|
+
exitCallback: null,
|
|
631
|
+
cleanupCallbacks: [],
|
|
632
|
+
exitingSignal: signal(false)
|
|
633
|
+
};
|
|
602
634
|
}
|
|
603
635
|
/**
|
|
604
|
-
*
|
|
636
|
+
* Set the active terminal session
|
|
605
637
|
*/
|
|
606
|
-
function
|
|
607
|
-
|
|
638
|
+
function setActiveSession(session) {
|
|
639
|
+
activeSession = session;
|
|
608
640
|
}
|
|
609
641
|
/**
|
|
610
|
-
*
|
|
642
|
+
* Get the active terminal session, or null if no render is active
|
|
643
|
+
*/
|
|
644
|
+
function getActiveSession() {
|
|
645
|
+
return activeSession;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
//#endregion
|
|
649
|
+
//#region ../terminal/src/keyboard.ts
|
|
650
|
+
/**
|
|
651
|
+
* Parse raw stdin data into a KeyEvent
|
|
652
|
+
*/
|
|
653
|
+
function parseKeyEvent(data) {
|
|
654
|
+
const raw = data.toString();
|
|
655
|
+
const event = {
|
|
656
|
+
key: "",
|
|
657
|
+
ctrl: false,
|
|
658
|
+
shift: false,
|
|
659
|
+
meta: false,
|
|
660
|
+
raw
|
|
661
|
+
};
|
|
662
|
+
if (raw === "\x1B[A") {
|
|
663
|
+
event.key = "up";
|
|
664
|
+
return event;
|
|
665
|
+
}
|
|
666
|
+
if (raw === "\x1B[B") {
|
|
667
|
+
event.key = "down";
|
|
668
|
+
return event;
|
|
669
|
+
}
|
|
670
|
+
if (raw === "\x1B[C") {
|
|
671
|
+
event.key = "right";
|
|
672
|
+
return event;
|
|
673
|
+
}
|
|
674
|
+
if (raw === "\x1B[D") {
|
|
675
|
+
event.key = "left";
|
|
676
|
+
return event;
|
|
677
|
+
}
|
|
678
|
+
if (raw === "\x1B[H" || raw === "\x1B[1~") {
|
|
679
|
+
event.key = "home";
|
|
680
|
+
return event;
|
|
681
|
+
}
|
|
682
|
+
if (raw === "\x1B[F" || raw === "\x1B[4~") {
|
|
683
|
+
event.key = "end";
|
|
684
|
+
return event;
|
|
685
|
+
}
|
|
686
|
+
if (raw === "\x1B[5~") {
|
|
687
|
+
event.key = "pageup";
|
|
688
|
+
return event;
|
|
689
|
+
}
|
|
690
|
+
if (raw === "\x1B[6~") {
|
|
691
|
+
event.key = "pagedown";
|
|
692
|
+
return event;
|
|
693
|
+
}
|
|
694
|
+
if (raw === "\x1B[3~") {
|
|
695
|
+
event.key = "delete";
|
|
696
|
+
return event;
|
|
697
|
+
}
|
|
698
|
+
if (raw === "\x1B[2~") {
|
|
699
|
+
event.key = "insert";
|
|
700
|
+
return event;
|
|
701
|
+
}
|
|
702
|
+
if (raw.length === 2 && raw[0] === "\x1B") {
|
|
703
|
+
event.meta = true;
|
|
704
|
+
event.key = raw[1];
|
|
705
|
+
return event;
|
|
706
|
+
}
|
|
707
|
+
if (raw === "\x1B") {
|
|
708
|
+
event.key = "escape";
|
|
709
|
+
return event;
|
|
710
|
+
}
|
|
711
|
+
if (raw === "") {
|
|
712
|
+
event.key = "c";
|
|
713
|
+
event.ctrl = true;
|
|
714
|
+
return event;
|
|
715
|
+
}
|
|
716
|
+
if (raw === "\r" || raw === "\n") {
|
|
717
|
+
event.key = "return";
|
|
718
|
+
return event;
|
|
719
|
+
}
|
|
720
|
+
if (raw === " ") {
|
|
721
|
+
event.key = "tab";
|
|
722
|
+
return event;
|
|
723
|
+
}
|
|
724
|
+
if (raw === "" || raw === "\b") {
|
|
725
|
+
event.key = "backspace";
|
|
726
|
+
return event;
|
|
727
|
+
}
|
|
728
|
+
if (raw === " ") {
|
|
729
|
+
event.key = "space";
|
|
730
|
+
return event;
|
|
731
|
+
}
|
|
732
|
+
if (raw.length === 1) {
|
|
733
|
+
const code = raw.charCodeAt(0);
|
|
734
|
+
if (code >= 1 && code <= 26) {
|
|
735
|
+
event.ctrl = true;
|
|
736
|
+
event.key = String.fromCharCode(code + 96);
|
|
737
|
+
return event;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (raw.length === 1) {
|
|
741
|
+
event.key = raw;
|
|
742
|
+
if (raw >= "A" && raw <= "Z") event.shift = true;
|
|
743
|
+
return event;
|
|
744
|
+
}
|
|
745
|
+
event.key = raw;
|
|
746
|
+
return event;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Install the keyboard handler on stdin for the active render context.
|
|
750
|
+
* Called automatically by render() when setting up keyboard input.
|
|
751
|
+
*/
|
|
752
|
+
function installKeyboardHandler() {
|
|
753
|
+
const ctx = getActiveSession();
|
|
754
|
+
if (!ctx || ctx.keyboardInstalled) return;
|
|
755
|
+
ctx.keyboardInstalled = true;
|
|
756
|
+
ctx.stdinHandler = (data) => {
|
|
757
|
+
const event = parseKeyEvent(data);
|
|
758
|
+
ctx.lastKeySignal.value = event;
|
|
759
|
+
const listeners = [...ctx.keyboardListeners];
|
|
760
|
+
for (const listener of listeners) listener(event);
|
|
761
|
+
};
|
|
762
|
+
if (process.stdin.isTTY) process.stdin.on("data", ctx.stdinHandler);
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Uninstall the keyboard handler for the active render context.
|
|
766
|
+
* Called during cleanup/unmount.
|
|
767
|
+
*/
|
|
768
|
+
function uninstallKeyboardHandler() {
|
|
769
|
+
const ctx = getActiveSession();
|
|
770
|
+
if (!ctx || !ctx.keyboardInstalled) return;
|
|
771
|
+
ctx.keyboardInstalled = false;
|
|
772
|
+
if (ctx.stdinHandler) {
|
|
773
|
+
process.stdin.removeListener("data", ctx.stdinHandler);
|
|
774
|
+
ctx.stdinHandler = null;
|
|
775
|
+
}
|
|
776
|
+
ctx.keyboardListeners = [];
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Subscribe to keyboard events with a callback.
|
|
780
|
+
* Returns an unsubscribe function.
|
|
611
781
|
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
782
|
+
* @example
|
|
783
|
+
* ```tsx
|
|
784
|
+
* const unsub = onKeypress((event) => {
|
|
785
|
+
* if (event.key === "up") moveUp();
|
|
786
|
+
* if (event.key === "down") moveDown();
|
|
787
|
+
* if (event.key === "return") confirm();
|
|
788
|
+
* });
|
|
614
789
|
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
790
|
+
* // Later: unsub();
|
|
791
|
+
* ```
|
|
792
|
+
*/
|
|
793
|
+
function onKeypress(handler) {
|
|
794
|
+
const ctx = getActiveSession();
|
|
795
|
+
if (!ctx) return () => {};
|
|
796
|
+
ctx.keyboardListeners.push(handler);
|
|
797
|
+
installKeyboardHandler();
|
|
798
|
+
return () => {
|
|
799
|
+
const idx = ctx.keyboardListeners.indexOf(handler);
|
|
800
|
+
if (idx !== -1) ctx.keyboardListeners.splice(idx, 1);
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Get a readonly signal of the last keypress event.
|
|
805
|
+
* Useful for reactive UIs that need to respond to any key.
|
|
617
806
|
*
|
|
618
807
|
* @example
|
|
619
808
|
* ```tsx
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
809
|
+
* const lastKey = useKeypress();
|
|
810
|
+
* // lastKey.value is null initially, then updated on each keypress
|
|
811
|
+
* ```
|
|
812
|
+
*/
|
|
813
|
+
function useKeypress() {
|
|
814
|
+
const ctx = getActiveSession();
|
|
815
|
+
if (!ctx) return signal(null);
|
|
816
|
+
installKeyboardHandler();
|
|
817
|
+
return ctx.lastKeySignal;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
//#endregion
|
|
821
|
+
//#region ../terminal/src/hooks.ts
|
|
822
|
+
/**
|
|
823
|
+
* Terminal hooks for interactive CLI applications
|
|
824
|
+
*/
|
|
825
|
+
/**
|
|
826
|
+
* Set the exit callback on the active render context.
|
|
827
|
+
* Called internally by render().
|
|
828
|
+
*/
|
|
829
|
+
function setExitCallback(callback) {
|
|
830
|
+
const ctx = getActiveSession();
|
|
831
|
+
if (ctx) ctx.exitCallback = callback;
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Programmatic exit hook - allows components to trigger unmount.
|
|
623
835
|
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
* );
|
|
836
|
+
* Returns a function that, when called, unmounts the terminal app
|
|
837
|
+
* (equivalent to pressing Ctrl+C).
|
|
838
|
+
*
|
|
839
|
+
* @example
|
|
840
|
+
* ```tsx
|
|
841
|
+
* function App() {
|
|
842
|
+
* const exit = useExit();
|
|
843
|
+
*
|
|
844
|
+
* onKeypress((event) => {
|
|
845
|
+
* if (event.key === "q") exit();
|
|
846
|
+
* });
|
|
847
|
+
*
|
|
848
|
+
* return <text>Press q to quit</text>;
|
|
849
|
+
* }
|
|
631
850
|
* ```
|
|
851
|
+
*/
|
|
852
|
+
function useExit() {
|
|
853
|
+
return () => {
|
|
854
|
+
const ctx = getActiveSession();
|
|
855
|
+
if (ctx?.exitCallback) ctx.exitCallback();
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Check whether stdin raw mode is supported and/or active.
|
|
632
860
|
*
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
*
|
|
861
|
+
* Useful for graceful degradation in non-TTY environments (CI, pipes).
|
|
862
|
+
*
|
|
863
|
+
* @example
|
|
864
|
+
* ```tsx
|
|
865
|
+
* const { supported, active } = isRawModeSupported();
|
|
866
|
+
* if (!supported) {
|
|
867
|
+
* print(<text>Interactive mode not available</text>);
|
|
868
|
+
* }
|
|
869
|
+
* ```
|
|
636
870
|
*/
|
|
637
|
-
function
|
|
638
|
-
|
|
871
|
+
function isRawModeSupported() {
|
|
872
|
+
const supported = Boolean(process.stdin.isTTY && process.stdin.setRawMode);
|
|
873
|
+
return {
|
|
874
|
+
supported,
|
|
875
|
+
active: supported && Boolean(process.stdin.isRaw)
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
//#endregion
|
|
880
|
+
//#region ../terminal/src/lifecycle.ts
|
|
881
|
+
/**
|
|
882
|
+
* Component lifecycle management for terminal rendering.
|
|
883
|
+
*
|
|
884
|
+
* Provides an onCleanup registry that components can use to register
|
|
885
|
+
* cleanup callbacks (timers, listeners, etc.) that run during unmount.
|
|
886
|
+
*
|
|
887
|
+
* Supports two modes:
|
|
888
|
+
* - Per-component scope (via onBeforeComponent/onAfterComponent hooks in core)
|
|
889
|
+
* Cleanups attached to RenderedNode.subscriptions, run when component unmounts
|
|
890
|
+
* - Global fallback (when called outside component render)
|
|
891
|
+
* Cleanups stored in TerminalSession.cleanupCallbacks, run on full unmount
|
|
892
|
+
*/
|
|
893
|
+
/**
|
|
894
|
+
* Stack of per-component cleanup scopes.
|
|
895
|
+
* Pushed by onBeforeComponent(), popped by onAfterComponent().
|
|
896
|
+
*/
|
|
897
|
+
const cleanupScopes = [];
|
|
898
|
+
/**
|
|
899
|
+
* Push a new per-component cleanup scope.
|
|
900
|
+
* Called by core's renderComponent via onBeforeComponent strategy hook.
|
|
901
|
+
*/
|
|
902
|
+
function pushCleanupScope() {
|
|
903
|
+
cleanupScopes.push([]);
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Pop the current cleanup scope and return collected callbacks.
|
|
907
|
+
* Called by core's renderComponent via onAfterComponent strategy hook.
|
|
908
|
+
*/
|
|
909
|
+
function popCleanupScope() {
|
|
910
|
+
return cleanupScopes.pop() ?? [];
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Register a cleanup callback that will run when the component unmounts.
|
|
914
|
+
*
|
|
915
|
+
* When called during component rendering, the callback is attached to
|
|
916
|
+
* the component's RenderedNode and runs when that component is unmounted
|
|
917
|
+
* (including via conditional rendering with when()/signal).
|
|
918
|
+
*
|
|
919
|
+
* When called outside component rendering (e.g., in render setup code),
|
|
920
|
+
* falls back to the global session cleanup list.
|
|
921
|
+
*
|
|
922
|
+
* @example
|
|
923
|
+
* ```tsx
|
|
924
|
+
* function Timer() {
|
|
925
|
+
* const elapsed = signal(0);
|
|
926
|
+
* const timer = setInterval(() => { elapsed.value++ }, 1000);
|
|
927
|
+
* onCleanup(() => clearInterval(timer));
|
|
928
|
+
* return <text>Elapsed: {elapsed}s</text>;
|
|
929
|
+
* }
|
|
930
|
+
* ```
|
|
931
|
+
*/
|
|
932
|
+
function onCleanup(fn) {
|
|
933
|
+
if (cleanupScopes.length > 0) {
|
|
934
|
+
cleanupScopes[cleanupScopes.length - 1].push(fn);
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
const ctx = getActiveSession();
|
|
938
|
+
if (ctx) ctx.cleanupCallbacks.push(fn);
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Run all registered global cleanup callbacks and clear the registry.
|
|
942
|
+
* Called internally by render() during unmount.
|
|
943
|
+
*
|
|
944
|
+
* Note: Per-component cleanups are handled by core's unmount/cleanupSubscriptions.
|
|
945
|
+
* This only flushes the global fallback list.
|
|
946
|
+
*/
|
|
947
|
+
function flushCleanups() {
|
|
948
|
+
const ctx = getActiveSession();
|
|
949
|
+
if (!ctx) return;
|
|
950
|
+
const callbacks = [...ctx.cleanupCallbacks];
|
|
951
|
+
ctx.cleanupCallbacks = [];
|
|
952
|
+
for (const fn of callbacks) try {
|
|
953
|
+
fn();
|
|
954
|
+
} catch {}
|
|
639
955
|
}
|
|
640
956
|
|
|
641
957
|
//#endregion
|
|
@@ -651,7 +967,9 @@ const { renderNode, cleanupSubscriptions } = createRenderer({
|
|
|
651
967
|
removeChild,
|
|
652
968
|
replaceNode,
|
|
653
969
|
setProperty,
|
|
654
|
-
setSignalProperty
|
|
970
|
+
setSignalProperty,
|
|
971
|
+
onBeforeComponent: pushCleanupScope,
|
|
972
|
+
onAfterComponent: popCleanupScope
|
|
655
973
|
});
|
|
656
974
|
/**
|
|
657
975
|
* Render a VNode tree to the terminal
|
|
@@ -681,7 +999,8 @@ const { renderNode, cleanupSubscriptions } = createRenderer({
|
|
|
681
999
|
*/
|
|
682
1000
|
function render(element, options = {}) {
|
|
683
1001
|
const { renderer, autoRender = true, fps = 60, stream: outputStream = process.stdout } = options;
|
|
684
|
-
|
|
1002
|
+
const session = createTerminalSession();
|
|
1003
|
+
setActiveSession(session);
|
|
685
1004
|
const autoCreated = !renderer;
|
|
686
1005
|
const actualRenderer = renderer || new TerminalRenderer(outputStream);
|
|
687
1006
|
const root = actualRenderer.getRoot();
|
|
@@ -692,15 +1011,24 @@ function render(element, options = {}) {
|
|
|
692
1011
|
}
|
|
693
1012
|
actualRenderer.render();
|
|
694
1013
|
let isRendering = false;
|
|
1014
|
+
let renderPending = false;
|
|
695
1015
|
const safeRender = () => {
|
|
696
|
-
if (isRendering)
|
|
1016
|
+
if (isRendering) {
|
|
1017
|
+
renderPending = true;
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
697
1020
|
isRendering = true;
|
|
698
1021
|
try {
|
|
699
1022
|
actualRenderer.render();
|
|
1023
|
+
while (renderPending) {
|
|
1024
|
+
renderPending = false;
|
|
1025
|
+
actualRenderer.render();
|
|
1026
|
+
}
|
|
700
1027
|
} finally {
|
|
701
1028
|
isRendering = false;
|
|
702
1029
|
}
|
|
703
1030
|
};
|
|
1031
|
+
actualRenderer.setResizeCallback(safeRender);
|
|
704
1032
|
let renderInterval = null;
|
|
705
1033
|
if (autoRender) {
|
|
706
1034
|
const interval = Math.floor(1e3 / fps);
|
|
@@ -714,8 +1042,11 @@ function render(element, options = {}) {
|
|
|
714
1042
|
});
|
|
715
1043
|
const originalRawMode = process.stdin.isTTY && process.stdin.isRaw;
|
|
716
1044
|
let handleExit = null;
|
|
717
|
-
let
|
|
1045
|
+
let exitKeyUnsub = null;
|
|
1046
|
+
let cleaned = false;
|
|
718
1047
|
const cleanup = () => {
|
|
1048
|
+
if (cleaned) return;
|
|
1049
|
+
cleaned = true;
|
|
719
1050
|
if (renderInterval) {
|
|
720
1051
|
clearInterval(renderInterval);
|
|
721
1052
|
renderInterval = null;
|
|
@@ -724,19 +1055,28 @@ function render(element, options = {}) {
|
|
|
724
1055
|
process.removeListener("SIGINT", handleExit);
|
|
725
1056
|
process.removeListener("SIGTERM", handleExit);
|
|
726
1057
|
}
|
|
727
|
-
|
|
1058
|
+
flushCleanups();
|
|
1059
|
+
setExitCallback(null);
|
|
1060
|
+
if (exitKeyUnsub) {
|
|
1061
|
+
exitKeyUnsub();
|
|
1062
|
+
exitKeyUnsub = null;
|
|
1063
|
+
}
|
|
1064
|
+
uninstallKeyboardHandler();
|
|
728
1065
|
if (process.stdin.isTTY && process.stdin.setRawMode) try {
|
|
729
1066
|
process.stdin.setRawMode(originalRawMode || false);
|
|
730
1067
|
} catch {}
|
|
731
1068
|
cleanupSubscriptions(rendered);
|
|
1069
|
+
actualRenderer.setResizeCallback(null);
|
|
732
1070
|
actualRenderer.destroy();
|
|
1071
|
+
setActiveSession(null);
|
|
733
1072
|
if (exitResolver) exitResolver();
|
|
734
1073
|
};
|
|
735
1074
|
const unmount = () => {
|
|
736
|
-
|
|
1075
|
+
session.exitingSignal.value = true;
|
|
737
1076
|
actualRenderer.render();
|
|
738
1077
|
cleanup();
|
|
739
1078
|
};
|
|
1079
|
+
setExitCallback(unmount);
|
|
740
1080
|
if (autoCreated) {
|
|
741
1081
|
handleExit = () => {
|
|
742
1082
|
unmount();
|
|
@@ -748,13 +1088,12 @@ function render(element, options = {}) {
|
|
|
748
1088
|
if (process.stdin.isTTY) {
|
|
749
1089
|
process.stdin.setRawMode(true);
|
|
750
1090
|
process.stdin.resume();
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
if (key === "
|
|
1091
|
+
installKeyboardHandler();
|
|
1092
|
+
exitKeyUnsub = onKeypress((event) => {
|
|
1093
|
+
if (event.key === "c" && event.ctrl || event.key === "escape") {
|
|
754
1094
|
if (handleExit) handleExit();
|
|
755
1095
|
}
|
|
756
|
-
};
|
|
757
|
-
process.stdin.on("data", handleKeypress);
|
|
1096
|
+
});
|
|
758
1097
|
}
|
|
759
1098
|
} catch (err) {
|
|
760
1099
|
cleanup();
|
|
@@ -819,6 +1158,41 @@ function print(element, options = {}) {
|
|
|
819
1158
|
if (process.stdin.isTTY && process.stdin.setRawMode && wasRawMode) process.stdin.setRawMode(true);
|
|
820
1159
|
}
|
|
821
1160
|
|
|
1161
|
+
//#endregion
|
|
1162
|
+
//#region ../terminal/src/components/ExitHint.tsx
|
|
1163
|
+
/** @jsxImportSource @semajsx/terminal */
|
|
1164
|
+
/**
|
|
1165
|
+
* ExitHint component - hides its children during the final render before exit
|
|
1166
|
+
*
|
|
1167
|
+
* This is useful for hiding "Press Ctrl+C to exit" messages in the final
|
|
1168
|
+
* terminal output, keeping only the actual content visible.
|
|
1169
|
+
*
|
|
1170
|
+
* The component automatically detects when unmount() is called and reactively
|
|
1171
|
+
* hides its children during the final render using signal-based reactivity.
|
|
1172
|
+
*
|
|
1173
|
+
* @example
|
|
1174
|
+
* ```tsx
|
|
1175
|
+
* render(
|
|
1176
|
+
* <box flexDirection="column" padding={1}>
|
|
1177
|
+
* <text bold>Counter: {count}</text>
|
|
1178
|
+
*
|
|
1179
|
+
* <ExitHint>
|
|
1180
|
+
* <text dim marginTop={1} color="yellow">
|
|
1181
|
+
* Press Ctrl+C or ESC to exit
|
|
1182
|
+
* </text>
|
|
1183
|
+
* </ExitHint>
|
|
1184
|
+
* </box>
|
|
1185
|
+
* );
|
|
1186
|
+
* ```
|
|
1187
|
+
*
|
|
1188
|
+
* Result after exit:
|
|
1189
|
+
* - The counter remains visible
|
|
1190
|
+
* - The exit hint is hidden from final output
|
|
1191
|
+
*/
|
|
1192
|
+
function ExitHint({ children }) {
|
|
1193
|
+
return when(computed(getActiveSession()?.exitingSignal ?? signal(false), (isExiting) => !isExiting), children);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
822
1196
|
//#endregion
|
|
823
1197
|
//#region ../terminal/src/components/BlankLine.tsx
|
|
824
1198
|
/**
|
|
@@ -842,5 +1216,167 @@ function BlankLine({ count = 1 }) {
|
|
|
842
1216
|
}
|
|
843
1217
|
|
|
844
1218
|
//#endregion
|
|
845
|
-
|
|
846
|
-
|
|
1219
|
+
//#region ../terminal/src/components/Spinner.tsx
|
|
1220
|
+
/** @jsxImportSource @semajsx/terminal */
|
|
1221
|
+
/**
|
|
1222
|
+
* Built-in spinner frame sets
|
|
1223
|
+
*/
|
|
1224
|
+
const spinnerFrames = {
|
|
1225
|
+
dots: {
|
|
1226
|
+
frames: [
|
|
1227
|
+
"⠋",
|
|
1228
|
+
"⠙",
|
|
1229
|
+
"⠹",
|
|
1230
|
+
"⠸",
|
|
1231
|
+
"⠼",
|
|
1232
|
+
"⠴",
|
|
1233
|
+
"⠦",
|
|
1234
|
+
"⠧",
|
|
1235
|
+
"⠇",
|
|
1236
|
+
"⠏"
|
|
1237
|
+
],
|
|
1238
|
+
interval: 80
|
|
1239
|
+
},
|
|
1240
|
+
line: {
|
|
1241
|
+
frames: [
|
|
1242
|
+
"-",
|
|
1243
|
+
"\\",
|
|
1244
|
+
"|",
|
|
1245
|
+
"/"
|
|
1246
|
+
],
|
|
1247
|
+
interval: 130
|
|
1248
|
+
},
|
|
1249
|
+
arc: {
|
|
1250
|
+
frames: [
|
|
1251
|
+
"◜",
|
|
1252
|
+
"◠",
|
|
1253
|
+
"◝",
|
|
1254
|
+
"◞",
|
|
1255
|
+
"◡",
|
|
1256
|
+
"◟"
|
|
1257
|
+
],
|
|
1258
|
+
interval: 100
|
|
1259
|
+
},
|
|
1260
|
+
bouncingBar: {
|
|
1261
|
+
frames: [
|
|
1262
|
+
"[ ]",
|
|
1263
|
+
"[= ]",
|
|
1264
|
+
"[== ]",
|
|
1265
|
+
"[=== ]",
|
|
1266
|
+
"[ ===]",
|
|
1267
|
+
"[ ==]",
|
|
1268
|
+
"[ =]",
|
|
1269
|
+
"[ ]"
|
|
1270
|
+
],
|
|
1271
|
+
interval: 80
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
/**
|
|
1275
|
+
* Spinner component - animated loading indicator for terminal UIs.
|
|
1276
|
+
*
|
|
1277
|
+
* @example
|
|
1278
|
+
* ```tsx
|
|
1279
|
+
* <Spinner />
|
|
1280
|
+
* <Spinner type="line" label="Loading..." />
|
|
1281
|
+
* <Spinner frames={["🌑", "🌒", "🌓", "🌔", "🌕"]} interval={150} />
|
|
1282
|
+
* ```
|
|
1283
|
+
*/
|
|
1284
|
+
function Spinner({ type = "dots", frames: customFrames, interval: customInterval, label, color = "cyan" }) {
|
|
1285
|
+
const config = spinnerFrames[type];
|
|
1286
|
+
const frames = customFrames ?? config.frames;
|
|
1287
|
+
const interval = customInterval ?? config.interval;
|
|
1288
|
+
const frameSignal = signal(frames[0]);
|
|
1289
|
+
let index = 0;
|
|
1290
|
+
const timer = setInterval(() => {
|
|
1291
|
+
index = (index + 1) % frames.length;
|
|
1292
|
+
frameSignal.value = frames[index];
|
|
1293
|
+
}, interval);
|
|
1294
|
+
onCleanup(() => clearInterval(timer));
|
|
1295
|
+
if (label) return /* @__PURE__ */ jsxs("box", {
|
|
1296
|
+
flexDirection: "row",
|
|
1297
|
+
children: [/* @__PURE__ */ jsx("text", {
|
|
1298
|
+
color,
|
|
1299
|
+
children: frameSignal
|
|
1300
|
+
}), /* @__PURE__ */ jsxs("text", { children: [" ", label] })]
|
|
1301
|
+
});
|
|
1302
|
+
return /* @__PURE__ */ jsx("text", {
|
|
1303
|
+
color,
|
|
1304
|
+
children: frameSignal
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
//#endregion
|
|
1309
|
+
//#region ../terminal/src/components/MultiSelect.tsx
|
|
1310
|
+
/** @jsxImportSource @semajsx/terminal */
|
|
1311
|
+
/**
|
|
1312
|
+
* MultiSelect component - interactive multi-selection menu.
|
|
1313
|
+
*
|
|
1314
|
+
* Navigate with arrow keys, toggle with Space, confirm with Enter, cancel with Escape.
|
|
1315
|
+
*
|
|
1316
|
+
* @example
|
|
1317
|
+
* ```tsx
|
|
1318
|
+
* <MultiSelect
|
|
1319
|
+
* title="Select frameworks:"
|
|
1320
|
+
* options={[
|
|
1321
|
+
* { label: "React", value: "react" },
|
|
1322
|
+
* { label: "Vue", value: "vue" },
|
|
1323
|
+
* { label: "Svelte", value: "svelte" },
|
|
1324
|
+
* ]}
|
|
1325
|
+
* onConfirm={(selected) => console.log("Selected:", selected)}
|
|
1326
|
+
* />
|
|
1327
|
+
* ```
|
|
1328
|
+
*/
|
|
1329
|
+
function MultiSelect({ options, onConfirm, onCancel, title, indicator = "❯", selectedIndicator = "◉", unselectedIndicator = "◯", focusColor = "cyan", selectedColor = "green" }) {
|
|
1330
|
+
const focusIndex = signal(0);
|
|
1331
|
+
const selectedSet = signal(/* @__PURE__ */ new Set());
|
|
1332
|
+
const unsub = onKeypress((event) => {
|
|
1333
|
+
if (event.key === "up") focusIndex.value = Math.max(0, focusIndex.value - 1);
|
|
1334
|
+
else if (event.key === "down") focusIndex.value = Math.min(options.length - 1, focusIndex.value + 1);
|
|
1335
|
+
else if (event.key === "space") {
|
|
1336
|
+
const current = new Set(selectedSet.value);
|
|
1337
|
+
const value = options[focusIndex.value].value;
|
|
1338
|
+
if (current.has(value)) current.delete(value);
|
|
1339
|
+
else current.add(value);
|
|
1340
|
+
selectedSet.value = current;
|
|
1341
|
+
} else if (event.key === "return") {
|
|
1342
|
+
unsub();
|
|
1343
|
+
onConfirm(Array.from(selectedSet.value));
|
|
1344
|
+
} else if (event.key === "escape" && onCancel) {
|
|
1345
|
+
unsub();
|
|
1346
|
+
onCancel();
|
|
1347
|
+
}
|
|
1348
|
+
});
|
|
1349
|
+
onCleanup(unsub);
|
|
1350
|
+
const items = options.map((option, i) => {
|
|
1351
|
+
const isFocused = computed(focusIndex, (idx) => idx === i);
|
|
1352
|
+
const isSelected = computed(selectedSet, (set) => set.has(option.value));
|
|
1353
|
+
const prefix = computed(isSelected, (sel) => sel ? selectedIndicator : unselectedIndicator);
|
|
1354
|
+
const line = computed(isFocused, (focused) => focused ? `${indicator} ${prefix.value} ${option.label}` : ` ${prefix.value} ${option.label}`);
|
|
1355
|
+
return /* @__PURE__ */ jsx("text", {
|
|
1356
|
+
color: computed(isFocused, (focused) => {
|
|
1357
|
+
if (focused) return focusColor;
|
|
1358
|
+
if (isSelected.value) return selectedColor;
|
|
1359
|
+
}),
|
|
1360
|
+
children: line
|
|
1361
|
+
}, option.value);
|
|
1362
|
+
});
|
|
1363
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
1364
|
+
flexDirection: "column",
|
|
1365
|
+
children: [
|
|
1366
|
+
title ? /* @__PURE__ */ jsx("text", {
|
|
1367
|
+
bold: true,
|
|
1368
|
+
children: title
|
|
1369
|
+
}) : null,
|
|
1370
|
+
items,
|
|
1371
|
+
/* @__PURE__ */ jsx("text", {
|
|
1372
|
+
dim: true,
|
|
1373
|
+
marginTop: 1,
|
|
1374
|
+
children: "↑/↓ navigate · space toggle · enter confirm"
|
|
1375
|
+
})
|
|
1376
|
+
]
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
//#endregion
|
|
1381
|
+
export { replaceNode as A, createElement as C, insertBefore as D, getParent as E, getChalkColor as F, renderBackground as M, renderBorder as N, markNodeAsDirty as O, getChalkBgColor as P, createComment as S, getNextSibling as T, renderTextElement as _, ExitHint as a, applyStyle as b, onCleanup as c, onKeypress as d, parseKeyEvent as f, TerminalRenderer as g, setSignalProperty as h, BlankLine as i, setText as j, removeChild as k, isRawModeSupported as l, setProperty as m, Spinner as n, print as o, useKeypress as p, spinnerFrames as r, render as s, MultiSelect as t, useExit as u, renderTextNode as v, createTextNode as w, collectText as x, appendChild as y };
|
|
1382
|
+
//# sourceMappingURL=src-77V1Plyd.mjs.map
|