@gridland/demo 0.2.12 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/bin/cli.mjs +21 -3
  2. package/dist/run.js +44 -32
  3. package/package.json +1 -1
package/bin/cli.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from "node:fs";
3
+ import { execSync, spawnSync } from "node:child_process";
3
4
  import { fileURLToPath } from "node:url";
4
5
  import { dirname, join } from "node:path";
5
6
 
@@ -17,7 +18,7 @@ if (!name || name === "--help" || name === "-h") {
17
18
  console.log(` ${d}`);
18
19
  }
19
20
  console.log("\nExamples:");
20
- console.log(" npx @gridland/demo ascii");
21
+ console.log(" bunx @gridland/demo ascii");
21
22
  console.log(" bunx @gridland/demo gradient");
22
23
  process.exit(name ? 0 : 1);
23
24
  }
@@ -28,5 +29,22 @@ if (!AVAILABLE_DEMOS.includes(name)) {
28
29
  process.exit(1);
29
30
  }
30
31
 
31
- const { runDemo } = await import("../dist/run.js");
32
- await runDemo(name);
32
+ // @opentui/core uses bun:ffi for the native terminal renderer, so we must run under bun
33
+ let hasBun = false;
34
+ try {
35
+ execSync("bun --version", { stdio: "ignore" });
36
+ hasBun = true;
37
+ } catch {}
38
+
39
+ if (!hasBun) {
40
+ console.error("Error: bun is required to run gridland demos.");
41
+ console.error("Install it with: curl -fsSL https://bun.sh/install | bash");
42
+ console.error("\nThen run: bunx @gridland/demo " + name);
43
+ process.exit(1);
44
+ }
45
+
46
+ const runPath = join(__dirname, "../dist/run.js");
47
+ const { status } = spawnSync("bun", ["-e", `import("${runPath}").then(m => m.runDemo("${name}"))`], {
48
+ stdio: "inherit",
49
+ });
50
+ process.exit(status ?? 1);
package/dist/run.js CHANGED
@@ -299,11 +299,10 @@ function SelectInput({
299
299
  list.push(item);
300
300
  grouped.set(group, list);
301
301
  }
302
- let groupIndex = 0;
302
+ let first = true;
303
303
  for (const [group, groupItems] of grouped) {
304
- if (groupIndex > 0) {
305
- rows.push({ type: "separator" });
306
- }
304
+ if (!first) rows.push({ type: "separator" });
305
+ first = false;
307
306
  if (group) {
308
307
  rows.push({ type: "group", label: group });
309
308
  }
@@ -312,7 +311,6 @@ function SelectInput({
312
311
  selectable.push({ item, index });
313
312
  index++;
314
313
  }
315
- groupIndex++;
316
314
  }
317
315
  return { flatRows: rows, selectableItems: selectable };
318
316
  }, [items]);
@@ -480,6 +478,7 @@ function MultiSelect({
480
478
  enableClear = true,
481
479
  highlightColor,
482
480
  checkboxColor,
481
+ allowEmpty = false,
483
482
  onSubmit,
484
483
  useKeyboard: useKeyboard3
485
484
  }) {
@@ -496,10 +495,8 @@ function MultiSelect({
496
495
  selected: new Set(isControlled ? controlledSelected : defaultSelected),
497
496
  submitted: false
498
497
  });
499
- const currentSelected = useMemo2(
500
- () => isControlled ? new Set(controlledSelected) : state.selected,
501
- [isControlled, controlledSelected, state.selected]
502
- );
498
+ const cursorRef = useRef2(0);
499
+ const currentSelected = isControlled ? new Set(controlledSelected) : state.selected;
503
500
  const { flatRows, selectableItems } = useMemo2(() => {
504
501
  const rows = [];
505
502
  const selectable = [];
@@ -511,11 +508,10 @@ function MultiSelect({
511
508
  list.push(item);
512
509
  grouped.set(group, list);
513
510
  }
514
- let groupIndex = 0;
511
+ let first = true;
515
512
  for (const [group, groupItems] of grouped) {
516
- if (groupIndex > 0) {
517
- rows.push({ type: "separator" });
518
- }
513
+ if (!first) rows.push({ type: "separator" });
514
+ first = false;
519
515
  if (group) {
520
516
  rows.push({ type: "group", label: group });
521
517
  }
@@ -524,10 +520,12 @@ function MultiSelect({
524
520
  selectable.push({ item, index });
525
521
  index++;
526
522
  }
527
- groupIndex++;
528
523
  }
529
524
  return { flatRows: rows, selectableItems: selectable };
530
525
  }, [items]);
526
+ const hasSubmitRow = allowEmpty || currentSelected.size > 0;
527
+ const totalPositions = selectableItems.length + (hasSubmitRow ? 1 : 0);
528
+ const isOnSubmit = hasSubmitRow && state.cursor === selectableItems.length;
531
529
  const visibleCount = limit ?? VISIBLE2;
532
530
  const cursorRowIndex = flatRows.findIndex((r) => r.type === "item" && r.index === state.cursor);
533
531
  const scrollOffset = Math.max(0, Math.min(cursorRowIndex - Math.floor(visibleCount / 2), flatRows.length - visibleCount));
@@ -542,28 +540,38 @@ function MultiSelect({
542
540
  const diamondColor = invalid ? theme.error : disabled ? theme.muted : theme.accent;
543
541
  useKeyboard3?.((event) => {
544
542
  if (state.submitted || disabled) return;
543
+ const move = (direction) => {
544
+ let next = cursorRef.current + direction;
545
+ if (next < 0) next = totalPositions - 1;
546
+ if (next >= totalPositions) next = 0;
547
+ cursorRef.current = next;
548
+ dispatch({ type: "MOVE", direction, max: totalPositions });
549
+ };
545
550
  if (event.name === "up" || event.name === "k") {
546
- dispatch({ type: "MOVE", direction: -1, max: selectableItems.length });
551
+ move(-1);
547
552
  } else if (event.name === "down" || event.name === "j") {
548
- dispatch({ type: "MOVE", direction: 1, max: selectableItems.length });
553
+ move(1);
549
554
  } else if (event.name === "return") {
550
- const current = selectableItems[state.cursor];
551
- if (current && !current.item.disabled) {
552
- const isDeselecting = currentSelected.has(current.item.value);
553
- if (!isDeselecting && maxCount !== void 0 && currentSelected.size >= maxCount) return;
554
- const next = new Set(currentSelected);
555
- if (isDeselecting) next.delete(current.item.value);
556
- else next.add(current.item.value);
557
- setSelected(Array.from(next));
555
+ const onSubmitRow = hasSubmitRow && cursorRef.current === selectableItems.length;
556
+ if (onSubmitRow) {
557
+ dispatch({ type: "SUBMIT" });
558
+ onSubmit?.(Array.from(currentSelected));
559
+ } else {
560
+ const current = selectableItems[cursorRef.current];
561
+ if (current && !current.item.disabled) {
562
+ const isDeselecting = currentSelected.has(current.item.value);
563
+ if (!isDeselecting && maxCount !== void 0 && currentSelected.size >= maxCount) return;
564
+ const next = new Set(currentSelected);
565
+ if (isDeselecting) next.delete(current.item.value);
566
+ else next.add(current.item.value);
567
+ setSelected(Array.from(next));
568
+ }
558
569
  }
559
570
  } else if (event.name === "a" && enableSelectAll) {
560
571
  const enabledValues = items.filter((i) => !i.disabled).map((i) => i.value);
561
572
  setSelected(maxCount !== void 0 ? enabledValues.slice(0, maxCount) : enabledValues);
562
573
  } else if (event.name === "x" && enableClear) {
563
574
  setSelected([]);
564
- } else if (event.name === "space" && currentSelected.size > 0) {
565
- dispatch({ type: "SUBMIT" });
566
- onSubmit?.(Array.from(currentSelected));
567
575
  }
568
576
  });
569
577
  if (state.submitted) {
@@ -661,12 +669,17 @@ function MultiSelect({
661
669
  /* @__PURE__ */ jsx10("span", { style: textStyle({ fg: itemColor, dim: isItemDisabled }), children: item.label })
662
670
  ] }, item.key ?? String(item.value));
663
671
  }),
664
- currentSelected.size > 0 && /* @__PURE__ */ jsxs6("text", { children: [
672
+ hasSubmitRow && /* @__PURE__ */ jsxs6("text", { children: [
665
673
  /* @__PURE__ */ jsxs6("span", { style: textStyle({ fg: theme.muted }), children: [
666
674
  BAR2,
667
675
  " "
668
676
  ] }),
669
- /* @__PURE__ */ jsx10("span", { style: textStyle({ dim: true }), children: " space to submit" })
677
+ /* @__PURE__ */ jsxs6("span", { style: textStyle({ fg: isOnSubmit ? resolvedHighlight : void 0 }), children: [
678
+ isOnSubmit ? CURSOR2 : " ",
679
+ " "
680
+ ] }),
681
+ /* @__PURE__ */ jsx10("span", { style: textStyle({ fg: isOnSubmit ? resolvedHighlight : theme.muted }), children: "\u21B3 " }),
682
+ /* @__PURE__ */ jsx10("span", { style: textStyle({ fg: isOnSubmit ? resolvedHighlight : theme.text }), children: "Submit" })
670
683
  ] })
671
684
  ] });
672
685
  }
@@ -1262,7 +1275,7 @@ function InstallBox() {
1262
1275
  flexShrink: 0,
1263
1276
  children: /* @__PURE__ */ jsxs14("text", { children: [
1264
1277
  /* @__PURE__ */ jsx20("span", { style: textStyle({ dim: true }), children: "$ " }),
1265
- /* @__PURE__ */ jsx20("span", { style: textStyle({ bold: true }), children: "npm create " }),
1278
+ /* @__PURE__ */ jsx20("span", { style: textStyle({ bold: true }), children: "bun create " }),
1266
1279
  /* @__PURE__ */ jsx20("span", { style: textStyle({ fg: theme.accent }), children: "gridland" })
1267
1280
  ] })
1268
1281
  }
@@ -1533,7 +1546,7 @@ function LandingApp({ useKeyboard: useKeyboard3 }) {
1533
1546
  flexShrink: 0,
1534
1547
  children: /* @__PURE__ */ jsxs17("text", { children: [
1535
1548
  /* @__PURE__ */ jsx24("span", { style: textStyle({ dim: true }), children: "$ " }),
1536
- /* @__PURE__ */ jsx24("span", { style: textStyle({ bold: true }), children: "npx " }),
1549
+ /* @__PURE__ */ jsx24("span", { style: textStyle({ bold: true }), children: "bunx " }),
1537
1550
  /* @__PURE__ */ jsx24("span", { style: textStyle({ fg: theme.accent }), children: "@gridland/demo landing" })
1538
1551
  ] })
1539
1552
  }
@@ -1684,7 +1697,6 @@ function MultiSelectApp() {
1684
1697
  { key: "enter", label: "select" },
1685
1698
  { key: "a", label: "all" },
1686
1699
  { key: "x", label: "clear" },
1687
- { key: "space", label: "submit" },
1688
1700
  { key: "q", label: "quit" }
1689
1701
  ] })
1690
1702
  ] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gridland/demo",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "type": "module",
5
5
  "description": "Run gridland component demos from anywhere",
6
6
  "bin": {