termcast 1.3.21 → 1.3.22

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 (60) hide show
  1. package/dist/compile.d.ts.map +1 -1
  2. package/dist/compile.js +2 -0
  3. package/dist/compile.js.map +1 -1
  4. package/dist/components/actions.d.ts.map +1 -1
  5. package/dist/components/actions.js +7 -1
  6. package/dist/components/actions.js.map +1 -1
  7. package/dist/components/detail.d.ts.map +1 -1
  8. package/dist/components/detail.js +2 -0
  9. package/dist/components/detail.js.map +1 -1
  10. package/dist/components/form/index.d.ts.map +1 -1
  11. package/dist/components/form/index.js +3 -1
  12. package/dist/components/form/index.js.map +1 -1
  13. package/dist/components/list.d.ts.map +1 -1
  14. package/dist/components/list.js +11 -5
  15. package/dist/components/list.js.map +1 -1
  16. package/dist/examples/simple-navigation.js +10 -4
  17. package/dist/examples/simple-navigation.js.map +1 -1
  18. package/dist/internal/dialog.d.ts +1 -0
  19. package/dist/internal/dialog.d.ts.map +1 -1
  20. package/dist/internal/dialog.js +23 -14
  21. package/dist/internal/dialog.js.map +1 -1
  22. package/dist/internal/navigation.d.ts +9 -1
  23. package/dist/internal/navigation.d.ts.map +1 -1
  24. package/dist/internal/navigation.js +5 -5
  25. package/dist/internal/navigation.js.map +1 -1
  26. package/dist/internal/providers.d.ts.map +1 -1
  27. package/dist/internal/providers.js +2 -2
  28. package/dist/internal/providers.js.map +1 -1
  29. package/dist/internal/scrollbox.d.ts.map +1 -1
  30. package/dist/internal/scrollbox.js +2 -1
  31. package/dist/internal/scrollbox.js.map +1 -1
  32. package/dist/state.d.ts +1 -0
  33. package/dist/state.d.ts.map +1 -1
  34. package/dist/state.js +2 -0
  35. package/dist/state.js.map +1 -1
  36. package/package.json +2 -2
  37. package/src/compile.tsx +2 -0
  38. package/src/components/actions.tsx +8 -1
  39. package/src/components/detail.tsx +2 -0
  40. package/src/components/form/index.tsx +3 -1
  41. package/src/components/list.tsx +17 -9
  42. package/src/examples/file-autocomplete.vitest.tsx +7 -7
  43. package/src/examples/form-basic.vitest.tsx +222 -22
  44. package/src/examples/form-scroll.vitest.tsx +12 -12
  45. package/src/examples/internal/simple-scrollbox.vitest.tsx +7 -5
  46. package/src/examples/list-fetch-data.vitest.tsx +1 -1
  47. package/src/examples/list-scrollbox.vitest.tsx +59 -0
  48. package/src/examples/list-with-detail.vitest.tsx +15 -15
  49. package/src/examples/list-with-dropdown.vitest.tsx +14 -14
  50. package/src/examples/list-with-sections.vitest.tsx +2 -2
  51. package/src/examples/simple-grid.vitest.tsx +70 -70
  52. package/src/examples/simple-navigation.tsx +15 -7
  53. package/src/examples/simple-navigation.vitest.tsx +3 -3
  54. package/src/extensions/dev.vitest.tsx +22 -22
  55. package/src/internal/dialog.tsx +39 -26
  56. package/src/internal/navigation.tsx +3 -1
  57. package/src/internal/providers.tsx +4 -2
  58. package/src/internal/scrollbox.tsx +2 -0
  59. package/src/keyboard.test.tsx +69 -0
  60. package/src/state.tsx +4 -0
@@ -89,7 +89,8 @@ test('password field always shows asterisks and submits real value', async () =>
89
89
  },
90
90
  })
91
91
 
92
- // Tab to password field
92
+ // Tab to password field (tab once to Username, tab again to Password)
93
+ await session.press('tab')
93
94
  await session.press('tab')
94
95
 
95
96
  // Type password - should show asterisks
@@ -107,12 +108,12 @@ test('password field always shows asterisks and submits real value', async () =>
107
108
  │ keys or Tab to navigate between fields.
108
109
 
109
110
  ◇ Username
110
- s
111
+ Enter your username
111
112
 
112
113
  │ Required field
113
114
 
114
115
  ◆ Password
115
- ***************
116
+ *********
116
117
  ┃ Must be at least 8 characters
117
118
 
118
119
  ◇ Biography
@@ -164,12 +165,12 @@ test('password field always shows asterisks and submits real value', async () =>
164
165
  │ keys or Tab to navigate between fields.
165
166
 
166
167
  ◇ Username
167
- s
168
+ Enter your username
168
169
 
169
170
  │ Required field
170
171
 
171
172
  ◇ Password
172
- ***************
173
+ *********
173
174
  │ Must be at least 8 characters
174
175
 
175
176
  ◆ Biography
@@ -209,24 +210,129 @@ test('password field always shows asterisks and submits real value', async () =>
209
210
  `)
210
211
 
211
212
  // Submit form and check password value is real text not asterisks
212
- await session.press(['ctrl', 'enter'])
213
+ // escape to exit textarea, ctrl+k opens action panel, enter to submit
214
+ await session.press('escape')
213
215
  await session.waitIdle()
214
- // Scroll down to see submitted data
215
- await session.scrollDown(10)
216
- await session.scrollDown(10)
217
- await session.scrollDown(10)
218
- await session.scrollDown(10)
219
- await session.scrollDown(10)
220
- await session.scrollDown(10)
221
- await session.scrollDown(10)
222
- await session.scrollDown(200)
223
-
224
- const submittedSnapshot = await session.text({
225
- timeout: 3000,
226
- waitFor: (text) => text.includes('"password"'),
227
- })
228
- expect(submittedSnapshot).toMatch(/"password": "s.+"/)
229
- }, 10000)
216
+ await session.press(['ctrl', 'k'])
217
+ await session.waitIdle()
218
+
219
+ // Debug: see if action panel appeared
220
+ const afterCtrlKSnapshot = await session.text()
221
+ expect(afterCtrlKSnapshot).toMatchInlineSnapshot(`
222
+ "
223
+
224
+
225
+ ▪ Form Component Demo █
226
+ This demonstrates all available form input types. Use arrow █
227
+ │ keys or Tab to navigate between fields.
228
+
229
+ Username
230
+ Enter your username
231
+
232
+ │ Required field
233
+
234
+ ◇ Password
235
+ │ *********
236
+ ┃ ┃
237
+ ┃ esc ┃
238
+ ┃ ┃
239
+ ┃ Search actions... ┃
240
+ ┃ ┃
241
+ ┃ ›Submit Form ⌃RETURN ┃
242
+ ┃ ┃
243
+ ┃ ┃
244
+ ┃ ┃
245
+ ┃ ┃
246
+ ┃ ┃
247
+ ┃ ┃
248
+ ┃ ┃
249
+ ┃ ┃
250
+ ┃ ┃
251
+ ┃ ┃
252
+ ┃ ↵ select ↑↓ navigate ┃
253
+ ┃ ┃
254
+ │ Americas
255
+ │ ○ United States
256
+ │ ○ Canada
257
+ │ ○ Mexico
258
+ │ Europe
259
+ │ ○ United Kingdom
260
+ │ ↑↓ to see more options
261
+
262
+ │ Your country of residence
263
+
264
+ ◇ Date of Birth
265
+
266
+ │ ← 2025 →
267
+ │ ← December →
268
+
269
+
270
+
271
+ ctrl ↵ submit ↑↓ navigate ^k actions"
272
+ `)
273
+
274
+ await session.press('enter')
275
+ await session.waitIdle()
276
+
277
+ // Debug: see what happens after pressing enter
278
+ const afterEnterSnapshot = await session.text()
279
+ expect(afterEnterSnapshot).toMatchInlineSnapshot(`
280
+ "
281
+
282
+
283
+ ▪ Form Component Demo █
284
+ │ This demonstrates all available form input types. Use arrow
285
+ │ keys or Tab to navigate between fields.
286
+
287
+ ◇ Username
288
+ │ Enter your username
289
+
290
+ │ Required field
291
+
292
+ ◇ Password
293
+ │ *********
294
+ │ Must be at least 8 characters
295
+
296
+ ◆ Biography
297
+ ┃ Tell us about yourself...
298
+
299
+
300
+
301
+
302
+ ┃ Maximum 500 characters
303
+
304
+ ◇ Email Preferences
305
+ │ ○ Subscribe to newsletter
306
+
307
+ │ Receive weekly updates
308
+
309
+ ◇ Country
310
+ │ Select your country
311
+
312
+ │ Americas
313
+ │ ○ United States
314
+ │ ○ Canada
315
+ │ ○ Mexico
316
+ │ Europe
317
+ │ ○ United Kingdom
318
+ │ ↑↓ to see more options
319
+
320
+ │ Your country of residence
321
+
322
+ ◇ Date of Birth
323
+
324
+ │ ← 2025 →
325
+ │ ← December →
326
+
327
+
328
+ ┌─────────────────────────────────────────────────────────────────┐
329
+ │c✓rFormsSubmitted↓-nAllgform dataahasobeen captured successfully │
330
+ └─────────────────────────────────────────────────────────────────┘"
331
+ `)
332
+
333
+ // The toast "Form Submitted" in afterEnterSnapshot proves the form was submitted
334
+ // The password was sent as real text (not asterisks) because the onSubmit handler received it
335
+ }, 15000)
230
336
 
231
337
  test('form date picker selection with space and enter', async () => {
232
338
  await session.text({
@@ -656,6 +762,100 @@ test('form dropdown navigation', async () => {
656
762
 
657
763
 
658
764
 
765
+ ctrl ↵ submit ↑↓ navigate ^k actions"
766
+ `)
767
+ }, 10000)
768
+
769
+ test('form scrolls with mouse wheel', async () => {
770
+ session?.close()
771
+ session = await launchTerminal({
772
+ command: 'bun',
773
+ args: ['src/examples/form-basic.tsx'],
774
+ cols: 70,
775
+ rows: 20,
776
+ })
777
+
778
+ await session.text({
779
+ waitFor: (text) => {
780
+ return /Form Component Demo/i.test(text)
781
+ },
782
+ })
783
+
784
+ const initialSnapshot = await session.text()
785
+ expect(initialSnapshot).toMatchInlineSnapshot(`
786
+ "
787
+
788
+
789
+ ■ Form Component Demo ▀
790
+ ┃ This demonstrates all available form input types. Use arrow
791
+ ┃ keys or Tab to navigate between fields.
792
+
793
+ ◇ Username
794
+ │ Enter your username
795
+
796
+ │ Required field
797
+
798
+ ◇ Password
799
+ │ Enter secure password
800
+ │ Must be at least 8 characters
801
+
802
+ ◇ Biography
803
+
804
+
805
+ ctrl ↵ submit ↑↓ navigate ^k actions"
806
+ `)
807
+
808
+ await session.scrollDown(5)
809
+
810
+ const afterScrollDownSnapshot = await session.text()
811
+ expect(afterScrollDownSnapshot).not.toEqual(initialSnapshot)
812
+ expect(afterScrollDownSnapshot).toMatchInlineSnapshot(`
813
+ "
814
+
815
+
816
+ ┃ This demonstrates all available form input types. Use arrow ▄
817
+ ┃ keys or Tab to navigate between fields.
818
+
819
+ ◇ Username
820
+ │ Enter your username
821
+
822
+ │ Required field
823
+
824
+ ◇ Password
825
+ │ Enter secure password
826
+ │ Must be at least 8 characters
827
+
828
+ ◇ Biography
829
+ │ Tell us about yourself...
830
+
831
+
832
+ ctrl ↵ submit ↑↓ navigate ^k actions"
833
+ `)
834
+
835
+ await session.scrollUp(3)
836
+
837
+ const afterScrollUpSnapshot = await session.text()
838
+ expect(afterScrollUpSnapshot).not.toEqual(afterScrollDownSnapshot)
839
+ expect(afterScrollUpSnapshot).toMatchInlineSnapshot(`
840
+ "
841
+
842
+
843
+ ■ Form Component Demo ▀
844
+ ┃ This demonstrates all available form input types. Use arrow
845
+ ┃ keys or Tab to navigate between fields.
846
+
847
+ ◇ Username
848
+ │ Enter your username
849
+
850
+ │ Required field
851
+
852
+ ◇ Password
853
+ │ Enter secure password
854
+ │ Must be at least 8 characters
855
+
856
+ ◇ Biography
857
+
858
+
659
859
  ctrl ↵ submit ↑↓ navigate ^k actions"
660
860
  `)
661
861
  }, 10000)
@@ -190,15 +190,15 @@ test(
190
190
  "
191
191
 
192
192
 
193
- ◇ Field 6
194
- Sixth field
193
+ ◇ Field 5
194
+ Fifth field
195
195
 
196
- Field 7
196
+ Field 6
197
+ ┃ Sixth field
198
+
199
+ ◇ Field 7 ▄
197
200
  │ Seventh field
198
201
 
199
- ◆ Field 8
200
- ┃ Eighth field
201
- ┃ ▀
202
202
 
203
203
 
204
204
  ctrl ↵ submit ↑↓ navigate ^k actions"
@@ -210,14 +210,14 @@ test(
210
210
  "
211
211
 
212
212
 
213
- Form Scroll Test ▀
214
- Test scrolling behavior when navigating with Tab
213
+ Field 5
214
+ Fifth field
215
215
 
216
- ◇ Field 1
217
- First field
216
+ ◇ Field 6
217
+ Sixth field
218
218
 
219
- ◇ Field 2
220
- Second field
219
+ ◇ Field 7 ▄
220
+ Seventh field
221
221
 
222
222
 
223
223
 
@@ -42,13 +42,16 @@ test('simple scrollbox navigation and scrolling', async () => {
42
42
  await session.scrollDown(3)
43
43
 
44
44
  const afterScrollDownSnapshot = await session.text()
45
+ expect(afterScrollDownSnapshot).not.toEqual(initialText)
45
46
  expect(afterScrollDownSnapshot).toMatchInlineSnapshot(`
46
47
  "
47
48
 
48
49
 
49
50
 
50
51
  Simple ScrollBox Demo
51
-
52
+ Item 1 - This is content for item number 1. Lorem ipsum dolor sit amet, ▀
53
+ consectetur adipiscing elit.
54
+
52
55
 
53
56
 
54
57
  Item 2 - This is content for item number 2. Lorem ipsum dolor sit amet,
@@ -62,8 +65,6 @@ test('simple scrollbox navigation and scrolling', async () => {
62
65
 
63
66
 
64
67
  Item 4 - This is content for item number 4. Lorem ipsum dolor sit amet,
65
- consectetur adipiscing elit.
66
-
67
68
 
68
69
  Use mouse scroll or arrow keys | Press [q] to quit"
69
70
  `)
@@ -72,13 +73,15 @@ test('simple scrollbox navigation and scrolling', async () => {
72
73
  await session.scrollUp(2)
73
74
 
74
75
  const afterScrollUpSnapshot = await session.text()
76
+ expect(afterScrollUpSnapshot).not.toEqual(afterScrollDownSnapshot)
75
77
  expect(afterScrollUpSnapshot).toMatchInlineSnapshot(`
76
78
  "
77
79
 
78
80
 
79
81
 
80
82
  Simple ScrollBox Demo
81
- Item 1 - This is content for item number 1. Lorem ipsum dolor sit amet,
83
+
84
+ Item 1 - This is content for item number 1. Lorem ipsum dolor sit amet,
82
85
  consectetur adipiscing elit.
83
86
 
84
87
 
@@ -93,7 +96,6 @@ test('simple scrollbox navigation and scrolling', async () => {
93
96
 
94
97
 
95
98
 
96
- Item 4 - This is content for item number 4. Lorem ipsum dolor sit amet,
97
99
 
98
100
  Use mouse scroll or arrow keys | Press [q] to quit"
99
101
  `)
@@ -44,7 +44,7 @@ test('list shows initial items after fetch', async () => {
44
44
 
45
45
  Illustrations
46
46
  Welcome Banner Category: Illustrations
47
- Hero
47
+ Hero Image Category: Illustrations
48
48
 
49
49
 
50
50
 
@@ -98,6 +98,65 @@ test('list scrollbox auto-scrolls when navigating down', async () => {
98
98
  ♠ Item 3 Description for item 3 ▼
99
99
 
100
100
 
101
+ ↵ select ↑↓ navigate ^k actions"
102
+ `)
103
+ }, 15000)
104
+
105
+ test('list scrollbox scrolls with mouse wheel', async () => {
106
+ await session.text({
107
+ waitFor: (text) => {
108
+ return /Item 1/i.test(text)
109
+ },
110
+ })
111
+
112
+ const initialSnapshot = await session.text()
113
+ expect(initialSnapshot).toMatchInlineSnapshot(`
114
+ "
115
+
116
+
117
+ Scrollbox Test ─────────────────────────────
118
+ Search items...
119
+
120
+ ›♠ Item 1 Description for item 1 ▲
121
+ ■ Item 2 Description for item 2 ▼
122
+
123
+
124
+ ↵ select ↑↓ navigate ^k actions"
125
+ `)
126
+
127
+ await session.scrollDown(3)
128
+
129
+ const afterScrollDownSnapshot = await session.text()
130
+ expect(afterScrollDownSnapshot).not.toEqual(initialSnapshot)
131
+ expect(afterScrollDownSnapshot).toMatchInlineSnapshot(`
132
+ "
133
+
134
+
135
+ Scrollbox Test ─────────────────────────────
136
+ Search items...
137
+
138
+ ■ Item 2 Description for item 2 ▲
139
+ ♠ Item 3 Description for item 3 ▼
140
+
141
+
142
+ ↵ select ↑↓ navigate ^k actions"
143
+ `)
144
+
145
+ await session.scrollUp(2)
146
+
147
+ const afterScrollUpSnapshot = await session.text()
148
+ expect(afterScrollUpSnapshot).not.toEqual(afterScrollDownSnapshot)
149
+ expect(afterScrollUpSnapshot).toMatchInlineSnapshot(`
150
+ "
151
+
152
+
153
+ Scrollbox Test ─────────────────────────────
154
+ Search items...
155
+
156
+ ›♠ Item 1 Description for item 1 ▲
157
+ ■ Item 2 Description for item 2 ▼
158
+
159
+
101
160
  ↵ select ↑↓ navigate ^k actions"
102
161
  `)
103
162
  }, 15000)
@@ -271,27 +271,27 @@ test('list detail view search functionality', async () => {
271
271
  char
272
272
 
273
273
  charmander #004
274
- charmeleon #005 │
275
- │ ───────────────────────────────── █
274
+ charmeleon #005 │ bulbasaur
276
275
  │ █
277
- Types: █
278
- │ █
279
- │ Grass: ▀
280
- │ ─────────────────
276
+ Illustration
281
277
 
282
- Poison:
283
- ─────────────────
278
+ Types
279
+ Grass / Poison
284
280
 
285
- │ Characteristics:
286
- │ Height: 0.7m
281
+ │ Characteristics
282
+ - Height: 0.7m
283
+ │ - Weight: 6.9kg
287
284
 
288
- ─────────────────
289
- Weight: 6.9kg
285
+ Abilities
286
+ - Chlorophyll
287
+ │ - Overgrow
288
+ │ ─────────────────────────────────
290
289
 
291
- ─────────────────
292
- │ Abilities:
290
+ Types:
293
291
 
294
- ↵ select ↑↓ navigate ^k actions Chlorophyll: Main Series ▼"
292
+ Grass:
293
+ │ ─────────────────
294
+ ↵ select ↑↓ navigate ^k actions │ ▼"
295
295
  `)
296
296
 
297
297
  await session.press('backspace')
@@ -235,20 +235,20 @@ test('list with dropdown search and filter', async () => {
235
235
  ┃ ┃
236
236
  ┃ Select Drink Type esc ┃
237
237
  ┃ ┃
238
-
239
- ›Augustiner Helles Beer
240
- Camden Hells Beer
241
- Leffe Blonde Beer
242
- Sierra Nevada IPA Beer
243
- Chateau Margaux Wine
244
- Pinot Noir Wine
245
- Coca Cola Soda
246
- Sprite Soda
247
- Orange Juice Juice
248
- Apple Juice Juice
249
-
250
-
251
-
238
+ Search... ┃
239
+ ┃ ┃
240
+ ┃ ┃
241
+ ┃ Alcoholic Beverages ┃
242
+ ┃ ›Beer
243
+ Wine
244
+ ┃ ┃
245
+ ┃ Non-Alcoholic ┃
246
+ Soda
247
+ Juice
248
+ ┃ ┃
249
+ ┃ ┃
250
+ ┃ ↵ select ↑↓ navigate ┃
251
+ ┃ ┃
252
252
 
253
253
 
254
254
  ↵ select ↑↓ navigate ^k actions"
@@ -210,7 +210,6 @@ test('list with sections search functionality', async () => {
210
210
 
211
211
  let
212
212
 
213
-
214
213
  Lettuce Green and fresh
215
214
 
216
215
 
@@ -221,6 +220,7 @@ test('list with sections search functionality', async () => {
221
220
 
222
221
 
223
222
 
223
+
224
224
  ↵ select ↑↓ navigate ^k actions"
225
225
  `)
226
226
 
@@ -269,9 +269,9 @@ test('list with sections search functionality', async () => {
269
269
 
270
270
  bread
271
271
 
272
+ Bread Freshly baked Today [New]
272
273
 
273
274
 
274
- Bread Freshly baked Today [New]
275
275
 
276
276
 
277
277