@silvery/examples 0.17.3 → 0.17.4

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 (112) hide show
  1. package/dist/UPNG-Cy7ViL8f.mjs +5074 -0
  2. package/dist/__vite-browser-external-2447137e-BML7CYau.mjs +4 -0
  3. package/dist/_banner-DLPxCqVy.mjs +44 -0
  4. package/dist/ansi-CCE2pVS0.mjs +16397 -0
  5. package/dist/apng-HhhBjRGt.mjs +68 -0
  6. package/dist/apng-mwUQbTTF.mjs +3 -0
  7. package/dist/apps/aichat/index.mjs +1299 -0
  8. package/dist/apps/app-todo.mjs +139 -0
  9. package/dist/apps/async-data.mjs +204 -0
  10. package/dist/apps/cli-wizard.mjs +339 -0
  11. package/dist/apps/clipboard.mjs +198 -0
  12. package/dist/apps/components.mjs +864 -0
  13. package/dist/apps/data-explorer.mjs +483 -0
  14. package/dist/apps/dev-tools.mjs +397 -0
  15. package/dist/apps/explorer.mjs +698 -0
  16. package/dist/apps/gallery.mjs +766 -0
  17. package/dist/apps/inline-bench.mjs +115 -0
  18. package/dist/apps/kanban.mjs +280 -0
  19. package/dist/apps/layout-ref.mjs +187 -0
  20. package/dist/apps/outline.mjs +203 -0
  21. package/dist/apps/paste-demo.mjs +189 -0
  22. package/dist/apps/scroll.mjs +86 -0
  23. package/dist/apps/search-filter.mjs +287 -0
  24. package/dist/apps/selection.mjs +355 -0
  25. package/dist/apps/spatial-focus-demo.mjs +388 -0
  26. package/dist/apps/task-list.mjs +258 -0
  27. package/dist/apps/terminal-caps-demo.mjs +315 -0
  28. package/dist/apps/terminal.mjs +872 -0
  29. package/dist/apps/text-selection-demo.mjs +254 -0
  30. package/dist/apps/textarea.mjs +178 -0
  31. package/dist/apps/theme.mjs +661 -0
  32. package/dist/apps/transform.mjs +215 -0
  33. package/dist/apps/virtual-10k.mjs +422 -0
  34. package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
  35. package/dist/backends-Bahh9mKN.mjs +1179 -0
  36. package/dist/backends-CCtCDQ94.mjs +3 -0
  37. package/dist/{cli.mjs → bin/cli.mjs} +15 -19
  38. package/dist/chunk-BSw8zbkd.mjs +37 -0
  39. package/dist/components/counter.mjs +48 -0
  40. package/dist/components/hello.mjs +31 -0
  41. package/dist/components/progress-bar.mjs +59 -0
  42. package/dist/components/select-list.mjs +85 -0
  43. package/dist/components/spinner.mjs +57 -0
  44. package/dist/components/text-input.mjs +62 -0
  45. package/dist/components/virtual-list.mjs +51 -0
  46. package/dist/flexily-zero-adapter-UB-ra8fR.mjs +3374 -0
  47. package/dist/gif-BZaqPPVX.mjs +3 -0
  48. package/dist/gif-BtnXuxLF.mjs +71 -0
  49. package/dist/gifenc-CLRW41dk.mjs +728 -0
  50. package/dist/jsx-runtime-dMs_8fNu.mjs +241 -0
  51. package/dist/key-mapping-5oYQdAQE.mjs +3 -0
  52. package/dist/key-mapping-D4LR1go6.mjs +130 -0
  53. package/dist/layout/dashboard.mjs +1204 -0
  54. package/dist/layout/live-resize.mjs +303 -0
  55. package/dist/layout/overflow.mjs +70 -0
  56. package/dist/layout/text-layout.mjs +335 -0
  57. package/dist/node-NuJ94BWl.mjs +1083 -0
  58. package/dist/plugins-D1KtkT4a.mjs +3057 -0
  59. package/dist/resvg-js-C_8Wps1F.mjs +201 -0
  60. package/dist/src-BTEVGpd9.mjs +23538 -0
  61. package/dist/src-CUUOuRH6.mjs +5322 -0
  62. package/dist/src-CzfRafCQ.mjs +814 -0
  63. package/dist/usingCtx-CsEf0xO3.mjs +57 -0
  64. package/dist/yoga-adapter-BVtQ5OJR.mjs +237 -0
  65. package/package.json +18 -13
  66. package/_banner.tsx +0 -60
  67. package/apps/aichat/components.tsx +0 -469
  68. package/apps/aichat/index.tsx +0 -220
  69. package/apps/aichat/script.ts +0 -460
  70. package/apps/aichat/state.ts +0 -325
  71. package/apps/aichat/types.ts +0 -19
  72. package/apps/app-todo.tsx +0 -201
  73. package/apps/async-data.tsx +0 -196
  74. package/apps/cli-wizard.tsx +0 -332
  75. package/apps/clipboard.tsx +0 -183
  76. package/apps/components.tsx +0 -658
  77. package/apps/data-explorer.tsx +0 -490
  78. package/apps/dev-tools.tsx +0 -395
  79. package/apps/explorer.tsx +0 -731
  80. package/apps/gallery.tsx +0 -653
  81. package/apps/inline-bench.tsx +0 -138
  82. package/apps/kanban.tsx +0 -265
  83. package/apps/layout-ref.tsx +0 -173
  84. package/apps/outline.tsx +0 -160
  85. package/apps/panes/index.tsx +0 -203
  86. package/apps/paste-demo.tsx +0 -185
  87. package/apps/scroll.tsx +0 -80
  88. package/apps/search-filter.tsx +0 -240
  89. package/apps/selection.tsx +0 -346
  90. package/apps/spatial-focus-demo.tsx +0 -372
  91. package/apps/task-list.tsx +0 -271
  92. package/apps/terminal-caps-demo.tsx +0 -317
  93. package/apps/terminal.tsx +0 -784
  94. package/apps/text-selection-demo.tsx +0 -193
  95. package/apps/textarea.tsx +0 -155
  96. package/apps/theme.tsx +0 -515
  97. package/apps/transform.tsx +0 -229
  98. package/apps/virtual-10k.tsx +0 -405
  99. package/apps/vterm-demo/index.tsx +0 -216
  100. package/components/counter.tsx +0 -49
  101. package/components/hello.tsx +0 -38
  102. package/components/progress-bar.tsx +0 -52
  103. package/components/select-list.tsx +0 -54
  104. package/components/spinner.tsx +0 -44
  105. package/components/text-input.tsx +0 -61
  106. package/components/virtual-list.tsx +0 -56
  107. package/dist/cli.d.mts +0 -1
  108. package/dist/cli.mjs.map +0 -1
  109. package/layout/dashboard.tsx +0 -953
  110. package/layout/live-resize.tsx +0 -282
  111. package/layout/overflow.tsx +0 -51
  112. package/layout/text-layout.tsx +0 -283
@@ -0,0 +1,287 @@
1
+ import { t as _usingCtx } from "../usingCtx-CsEf0xO3.mjs";
2
+ import { t as require_jsx_runtime } from "../jsx-runtime-dMs_8fNu.mjs";
3
+ import { t as ExampleBanner } from "../_banner-DLPxCqVy.mjs";
4
+ import { useDeferredValue, useState, useTransition } from "react";
5
+ import { Box, Kbd, Lead, Muted, Strong, Text, createTerm, render, useApp, useInput } from "silvery";
6
+ //#region apps/search-filter.tsx
7
+ /**
8
+ * Search Filter Example
9
+ *
10
+ * Demonstrates React concurrent features for responsive typing:
11
+ * - useTransition for low-priority state updates
12
+ * - useDeferredValue for deferred filtering
13
+ * - Typing remains responsive even with heavy filtering
14
+ */
15
+ var import_jsx_runtime = require_jsx_runtime();
16
+ const meta = {
17
+ name: "Search Filter",
18
+ description: "useTransition + useDeferredValue for responsive concurrent search",
19
+ features: [
20
+ "useDeferredValue",
21
+ "useTransition",
22
+ "useInput"
23
+ ]
24
+ };
25
+ const items = [
26
+ {
27
+ id: 1,
28
+ name: "React Hooks Guide",
29
+ category: "docs",
30
+ tags: [
31
+ "react",
32
+ "hooks",
33
+ "tutorial"
34
+ ]
35
+ },
36
+ {
37
+ id: 2,
38
+ name: "TypeScript Patterns",
39
+ category: "docs",
40
+ tags: ["typescript", "patterns"]
41
+ },
42
+ {
43
+ id: 3,
44
+ name: "Build Configuration",
45
+ category: "config",
46
+ tags: [
47
+ "webpack",
48
+ "vite",
49
+ "build"
50
+ ]
51
+ },
52
+ {
53
+ id: 4,
54
+ name: "Testing Best Practices",
55
+ category: "docs",
56
+ tags: [
57
+ "testing",
58
+ "jest",
59
+ "vitest"
60
+ ]
61
+ },
62
+ {
63
+ id: 5,
64
+ name: "API Documentation",
65
+ category: "docs",
66
+ tags: [
67
+ "api",
68
+ "rest",
69
+ "graphql"
70
+ ]
71
+ },
72
+ {
73
+ id: 6,
74
+ name: "Database Schema",
75
+ category: "config",
76
+ tags: [
77
+ "database",
78
+ "sql",
79
+ "migration"
80
+ ]
81
+ },
82
+ {
83
+ id: 7,
84
+ name: "Authentication Flow",
85
+ category: "docs",
86
+ tags: [
87
+ "auth",
88
+ "security",
89
+ "jwt"
90
+ ]
91
+ },
92
+ {
93
+ id: 8,
94
+ name: "CI/CD Pipeline",
95
+ category: "config",
96
+ tags: [
97
+ "ci",
98
+ "deployment",
99
+ "github"
100
+ ]
101
+ },
102
+ {
103
+ id: 9,
104
+ name: "Performance Tuning",
105
+ category: "docs",
106
+ tags: ["performance", "optimization"]
107
+ },
108
+ {
109
+ id: 10,
110
+ name: "Error Handling",
111
+ category: "docs",
112
+ tags: [
113
+ "errors",
114
+ "debugging",
115
+ "logging"
116
+ ]
117
+ },
118
+ {
119
+ id: 11,
120
+ name: "State Management",
121
+ category: "docs",
122
+ tags: [
123
+ "state",
124
+ "redux",
125
+ "zustand"
126
+ ]
127
+ },
128
+ {
129
+ id: 12,
130
+ name: "CSS Architecture",
131
+ category: "docs",
132
+ tags: [
133
+ "css",
134
+ "tailwind",
135
+ "styled"
136
+ ]
137
+ },
138
+ {
139
+ id: 13,
140
+ name: "Security Guidelines",
141
+ category: "docs",
142
+ tags: [
143
+ "security",
144
+ "owasp",
145
+ "audit"
146
+ ]
147
+ },
148
+ {
149
+ id: 14,
150
+ name: "Deployment Scripts",
151
+ category: "config",
152
+ tags: [
153
+ "deploy",
154
+ "docker",
155
+ "k8s"
156
+ ]
157
+ },
158
+ {
159
+ id: 15,
160
+ name: "Monitoring Setup",
161
+ category: "config",
162
+ tags: [
163
+ "monitoring",
164
+ "metrics",
165
+ "logs"
166
+ ]
167
+ }
168
+ ];
169
+ function SearchInput({ value, onChange }) {
170
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, { children: [
171
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Strong, {
172
+ color: "$primary",
173
+ children: "Search: "
174
+ }),
175
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: value }),
176
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
177
+ dim: true,
178
+ children: "|"
179
+ })
180
+ ] });
181
+ }
182
+ function FilteredList({ query, isPending }) {
183
+ const filtered = items.filter((item) => {
184
+ const searchLower = query.toLowerCase();
185
+ return item.name.toLowerCase().includes(searchLower) || item.category.toLowerCase().includes(searchLower) || item.tags.some((tag) => tag.toLowerCase().includes(searchLower));
186
+ });
187
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
188
+ flexDirection: "column",
189
+ marginTop: 1,
190
+ children: [
191
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
192
+ marginBottom: 1,
193
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Muted, { children: [
194
+ filtered.length,
195
+ " results",
196
+ isPending && " (filtering...)"
197
+ ] })
198
+ }),
199
+ filtered.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
200
+ marginBottom: 1,
201
+ children: [
202
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
203
+ bold: true,
204
+ children: item.name
205
+ }),
206
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
207
+ dim: true,
208
+ children: [
209
+ " [",
210
+ item.category,
211
+ "]"
212
+ ]
213
+ }),
214
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
215
+ color: "$muted",
216
+ children: [" ", item.tags.join(", ")]
217
+ })
218
+ ]
219
+ }, item.id)),
220
+ filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Lead, { children: "No matches found" })
221
+ ]
222
+ });
223
+ }
224
+ function SearchApp() {
225
+ const { exit } = useApp();
226
+ const [query, setQuery] = useState("");
227
+ const deferredQuery = useDeferredValue(query);
228
+ const [isPending, startTransition] = useTransition();
229
+ useInput((input, key) => {
230
+ if (key.escape) {
231
+ exit();
232
+ return;
233
+ }
234
+ if (key.backspace || key.delete) {
235
+ startTransition(() => {
236
+ setQuery((prev) => prev.slice(0, -1));
237
+ });
238
+ return;
239
+ }
240
+ if (input && !key.ctrl && !key.meta) startTransition(() => {
241
+ setQuery((prev) => prev + input);
242
+ });
243
+ });
244
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
245
+ flexDirection: "column",
246
+ padding: 1,
247
+ children: [
248
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchInput, {
249
+ value: query,
250
+ onChange: setQuery
251
+ }),
252
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
253
+ flexGrow: 1,
254
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilteredList, {
255
+ query: deferredQuery,
256
+ isPending
257
+ })
258
+ }),
259
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Muted, { children: [
260
+ " ",
261
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kbd, { children: "type" }),
262
+ " to search ",
263
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kbd, { children: "Esc/q" }),
264
+ " quit"
265
+ ] })
266
+ ]
267
+ });
268
+ }
269
+ async function main() {
270
+ try {
271
+ var _usingCtx$1 = _usingCtx();
272
+ const term = _usingCtx$1.u(createTerm());
273
+ const { waitUntilExit } = await render(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ExampleBanner, {
274
+ meta,
275
+ controls: "type to search Esc quit",
276
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchApp, {})
277
+ }), term);
278
+ await waitUntilExit();
279
+ } catch (_) {
280
+ _usingCtx$1.e = _;
281
+ } finally {
282
+ _usingCtx$1.d();
283
+ }
284
+ }
285
+ if (import.meta.main) await main();
286
+ //#endregion
287
+ export { SearchApp, main, meta };
@@ -0,0 +1,355 @@
1
+ import { t as _usingCtx } from "../usingCtx-CsEf0xO3.mjs";
2
+ import { t as require_jsx_runtime } from "../jsx-runtime-dMs_8fNu.mjs";
3
+ import { useCallback, useState } from "react";
4
+ import { Box, Text } from "silvery";
5
+ import { run, useInput as useInput$1 } from "silvery/runtime";
6
+ //#region apps/selection.tsx
7
+ /**
8
+ * Selection Model Demo
9
+ *
10
+ * Demonstrates the silvery selection model from docs/design/selection-model.md:
11
+ * - Node selection (click, j/k)
12
+ * - Multi-select (Cmd+click toggle, Shift+j/k extend)
13
+ * - Text editing (Enter → edit, Escape → node mode)
14
+ * - Mode ladder: text ──Esc──► node ──Esc──► board
15
+ * - Live status bar showing Selection state
16
+ *
17
+ * Run: bun examples/apps/selection.tsx
18
+ */
19
+ var import_jsx_runtime = require_jsx_runtime();
20
+ const S = {
21
+ cursor: (sel) => sel.nodes[0],
22
+ anchor: (sel) => sel.nodes.at(-1),
23
+ ids: (sel) => new Set(sel.nodes),
24
+ includes: (sel, id) => sel.nodes.includes(id),
25
+ isEditing: (sel) => !!sel.text,
26
+ inputMode: (sel) => !sel ? "board" : sel.text ? "text" : "node",
27
+ select: (id) => ({ nodes: [id] }),
28
+ toggle(sel, id) {
29
+ if (sel.nodes.includes(id)) {
30
+ const rest = sel.nodes.filter((n) => n !== id);
31
+ return rest.length > 0 ? { nodes: rest } : void 0;
32
+ }
33
+ return { nodes: [id, ...sel.nodes] };
34
+ },
35
+ extend(sel, id, allIds) {
36
+ const anchorIdx = allIds.indexOf(S.anchor(sel));
37
+ const targetIdx = allIds.indexOf(id);
38
+ if (anchorIdx < 0 || targetIdx < 0) return sel;
39
+ const lo = Math.min(anchorIdx, targetIdx);
40
+ const hi = Math.max(anchorIdx, targetIdx);
41
+ const range = allIds.slice(lo, hi + 1);
42
+ return { nodes: targetIdx <= anchorIdx ? range : [...range].reverse() };
43
+ },
44
+ areaSelect(_sel, hitIds, mode) {
45
+ if (mode === "replace") return hitIds.length > 0 ? { nodes: hitIds } : void 0;
46
+ let result = _sel;
47
+ for (const id of hitIds) result = result ? S.toggle(result, id) : S.select(id);
48
+ return result;
49
+ },
50
+ clear: () => void 0,
51
+ collapseToCursor(sel) {
52
+ return { nodes: [sel.nodes[0]] };
53
+ },
54
+ edit(sel, offset) {
55
+ return {
56
+ ...sel,
57
+ text: [{
58
+ nodeId: sel.nodes[0],
59
+ offset
60
+ }]
61
+ };
62
+ },
63
+ stopEditing(sel) {
64
+ const { text: _, ...rest } = sel;
65
+ return rest;
66
+ }
67
+ };
68
+ const ITEMS = [
69
+ {
70
+ id: "inbox",
71
+ label: "Inbox"
72
+ },
73
+ {
74
+ id: "today",
75
+ label: "Today"
76
+ },
77
+ {
78
+ id: "next",
79
+ label: "Next Actions"
80
+ },
81
+ {
82
+ id: "projects",
83
+ label: "Projects"
84
+ },
85
+ {
86
+ id: "waiting",
87
+ label: "Waiting For"
88
+ },
89
+ {
90
+ id: "someday",
91
+ label: "Someday / Maybe"
92
+ },
93
+ {
94
+ id: "reference",
95
+ label: "Reference"
96
+ },
97
+ {
98
+ id: "calendar",
99
+ label: "Calendar"
100
+ },
101
+ {
102
+ id: "review",
103
+ label: "Weekly Review"
104
+ },
105
+ {
106
+ id: "done",
107
+ label: "Done"
108
+ }
109
+ ];
110
+ const ALL_IDS = ITEMS.map((i) => i.id);
111
+ function ItemRow({ item, sel, onSelect }) {
112
+ const isCursor = sel ? S.cursor(sel) === item.id : false;
113
+ const isAnchor = sel ? S.anchor(sel) === item.id : false;
114
+ const isSelected = sel ? S.includes(sel, item.id) : false;
115
+ const isEditing = isCursor && sel ? S.isEditing(sel) : false;
116
+ const marker = isCursor ? "►" : isSelected ? "●" : " ";
117
+ const anchorMark = isAnchor && !isCursor ? " ⚓" : "";
118
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
119
+ onClick: (e) => onSelect(item.id, e.metaKey, e.shiftKey),
120
+ onDoubleClick: () => onSelect(item.id, false, false),
121
+ children: [
122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123
+ color: isSelected ? "$primary" : "$muted",
124
+ children: [marker, " "]
125
+ }),
126
+ isEditing ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
127
+ backgroundColor: "$surface",
128
+ color: "$text",
129
+ bold: true,
130
+ children: [
131
+ " ",
132
+ item.label,
133
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
134
+ color: "$primary",
135
+ children: "│"
136
+ }),
137
+ " "
138
+ ]
139
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
140
+ bold: isCursor,
141
+ color: isCursor ? "$primary" : isSelected ? "$primary" : "$text",
142
+ dim: !isSelected && !isCursor,
143
+ children: item.label
144
+ }),
145
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
146
+ color: "$muted",
147
+ dim: true,
148
+ children: anchorMark
149
+ })
150
+ ]
151
+ });
152
+ }
153
+ function StatusBar({ sel }) {
154
+ const mode = S.inputMode(sel);
155
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
156
+ flexDirection: "column",
157
+ borderStyle: "single",
158
+ borderColor: "$border",
159
+ paddingX: 1,
160
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
161
+ gap: 2,
162
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
163
+ color: mode === "text" ? "$success" : mode === "node" ? "$primary" : "$muted",
164
+ bold: true,
165
+ children: mode.toUpperCase()
166
+ }), sel && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
167
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
168
+ color: "$muted",
169
+ children: ["cursor=", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
170
+ color: "$primary",
171
+ children: S.cursor(sel)
172
+ })]
173
+ }),
174
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
175
+ color: "$muted",
176
+ children: ["anchor=", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
177
+ color: "$text",
178
+ children: S.anchor(sel)
179
+ })]
180
+ }),
181
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
182
+ color: "$muted",
183
+ children: ["selected=", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
184
+ color: "$text",
185
+ children: sel.nodes.length
186
+ })]
187
+ }),
188
+ sel.text && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
189
+ color: "$muted",
190
+ children: ["text=", /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
191
+ color: "$success",
192
+ children: ["offset ", sel.text[0].offset]
193
+ })]
194
+ })
195
+ ] })]
196
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
197
+ color: "$muted",
198
+ dim: true,
199
+ children: "j/k nav · Enter edit · Esc back · Shift+j/k extend · Cmd+click toggle · q quit"
200
+ })]
201
+ });
202
+ }
203
+ function SelectionDemo() {
204
+ const [sel, setSel] = useState(void 0);
205
+ const [editTexts, setEditTexts] = useState(() => Object.fromEntries(ITEMS.map((i) => [i.id, i.label])));
206
+ const cursorIndex = sel ? ALL_IDS.indexOf(S.cursor(sel)) : -1;
207
+ const handleSelect = useCallback((id, meta, shift) => {
208
+ setSel((prev) => {
209
+ if (meta && prev) return S.toggle(prev, id);
210
+ if (shift && prev) return S.extend(prev, id, ALL_IDS);
211
+ return S.select(id);
212
+ });
213
+ }, []);
214
+ useInput$1((input, key) => {
215
+ if (input === "q" || key.escape && !sel) return "exit";
216
+ const mode = S.inputMode(sel);
217
+ if (mode === "text" && sel) {
218
+ if (key.escape) {
219
+ setSel(S.stopEditing(sel));
220
+ return;
221
+ }
222
+ if (key.backspace && sel.text) {
223
+ const nodeId = S.cursor(sel);
224
+ const offset = sel.text[0].offset;
225
+ if (offset > 0) {
226
+ setEditTexts((prev) => ({
227
+ ...prev,
228
+ [nodeId]: prev[nodeId].slice(0, offset - 1) + prev[nodeId].slice(offset)
229
+ }));
230
+ setSel(S.edit(sel, offset - 1));
231
+ }
232
+ return;
233
+ }
234
+ if (key.leftArrow && sel.text) {
235
+ setSel(S.edit(sel, Math.max(0, sel.text[0].offset - 1)));
236
+ return;
237
+ }
238
+ if (key.rightArrow && sel.text) {
239
+ const maxLen = editTexts[S.cursor(sel)]?.length ?? 0;
240
+ setSel(S.edit(sel, Math.min(maxLen, sel.text[0].offset + 1)));
241
+ return;
242
+ }
243
+ if (input && !key.ctrl && !key.meta && sel.text) {
244
+ const nodeId = S.cursor(sel);
245
+ const offset = sel.text[0].offset;
246
+ setEditTexts((prev) => ({
247
+ ...prev,
248
+ [nodeId]: prev[nodeId].slice(0, offset) + input + prev[nodeId].slice(offset)
249
+ }));
250
+ setSel(S.edit(sel, offset + input.length));
251
+ return;
252
+ }
253
+ return;
254
+ }
255
+ if (mode === "node" && sel) {
256
+ if (key.escape) {
257
+ if (sel.nodes.length > 1) setSel(S.collapseToCursor(sel));
258
+ else setSel(S.clear());
259
+ return;
260
+ }
261
+ if (key.return) {
262
+ const text = editTexts[S.cursor(sel)] ?? "";
263
+ setSel(S.edit(sel, text.length));
264
+ return;
265
+ }
266
+ if (input === "j" || key.downArrow) {
267
+ const next = Math.min(ALL_IDS.length - 1, cursorIndex + 1);
268
+ if (key.shift) setSel(S.extend(sel, ALL_IDS[next], ALL_IDS));
269
+ else setSel(S.select(ALL_IDS[next]));
270
+ return;
271
+ }
272
+ if (input === "k" || key.upArrow) {
273
+ const next = Math.max(0, cursorIndex - 1);
274
+ if (key.shift) setSel(S.extend(sel, ALL_IDS[next], ALL_IDS));
275
+ else setSel(S.select(ALL_IDS[next]));
276
+ return;
277
+ }
278
+ return;
279
+ }
280
+ if (input === "j" || key.downArrow) setSel(S.select(ALL_IDS[0]));
281
+ if (input === "k" || key.upArrow) setSel(S.select(ALL_IDS.at(-1)));
282
+ });
283
+ const displayItems = ITEMS.map((item) => ({
284
+ ...item,
285
+ label: editTexts[item.id] ?? item.label
286
+ }));
287
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
288
+ flexDirection: "column",
289
+ padding: 1,
290
+ height: "100%",
291
+ children: [
292
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
293
+ marginBottom: 1,
294
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
295
+ bold: true,
296
+ color: "$primary",
297
+ children: "Selection Model Demo"
298
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
299
+ color: "$muted",
300
+ children: " — silvery reactive selection"
301
+ })]
302
+ }),
303
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
304
+ flexDirection: "column",
305
+ flexGrow: 1,
306
+ borderStyle: "round",
307
+ borderColor: "$border",
308
+ overflow: "hidden",
309
+ children: displayItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ItemRow, {
310
+ item,
311
+ sel,
312
+ onSelect: handleSelect
313
+ }, item.id))
314
+ }),
315
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusBar, { sel }),
316
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
317
+ marginTop: 1,
318
+ flexDirection: "column",
319
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
320
+ color: "$muted",
321
+ dim: true,
322
+ children: [
323
+ "nodes=[",
324
+ sel?.nodes.join(", ") ?? "",
325
+ "]"
326
+ ]
327
+ })
328
+ })
329
+ ]
330
+ });
331
+ }
332
+ const meta = {
333
+ name: "Selection",
334
+ description: "Reactive selection model — node/text modes, multi-select, mode ladder",
335
+ demo: true,
336
+ features: [
337
+ "Selection model",
338
+ "mode ladder",
339
+ "multi-select",
340
+ "text editing"
341
+ ]
342
+ };
343
+ async function main() {
344
+ try {
345
+ var _usingCtx$1 = _usingCtx();
346
+ await _usingCtx$1.u(await run(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectionDemo, {}), { mode: "fullscreen" })).waitUntilExit();
347
+ } catch (_) {
348
+ _usingCtx$1.e = _;
349
+ } finally {
350
+ _usingCtx$1.d();
351
+ }
352
+ }
353
+ if (import.meta.main) await main();
354
+ //#endregion
355
+ export { main, meta };