termcast 1.3.46 → 1.3.48

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 (115) hide show
  1. package/dist/apis/cache.d.ts.map +1 -1
  2. package/dist/apis/cache.js +1 -2
  3. package/dist/apis/cache.js.map +1 -1
  4. package/dist/apis/localstorage.d.ts.map +1 -1
  5. package/dist/apis/localstorage.js +1 -2
  6. package/dist/apis/localstorage.js.map +1 -1
  7. package/dist/apis/sqlite.d.ts +7 -0
  8. package/dist/apis/sqlite.d.ts.map +1 -0
  9. package/dist/apis/sqlite.js +13 -0
  10. package/dist/apis/sqlite.js.map +1 -0
  11. package/dist/build.d.ts.map +1 -1
  12. package/dist/build.js +7 -1
  13. package/dist/build.js.map +1 -1
  14. package/dist/compile.js +1 -1
  15. package/dist/compile.js.map +1 -1
  16. package/dist/components/actions.d.ts +18 -0
  17. package/dist/components/actions.d.ts.map +1 -1
  18. package/dist/components/actions.js +70 -5
  19. package/dist/components/actions.js.map +1 -1
  20. package/dist/components/detail.d.ts.map +1 -1
  21. package/dist/components/detail.js +3 -1
  22. package/dist/components/detail.js.map +1 -1
  23. package/dist/components/dropdown.d.ts.map +1 -1
  24. package/dist/components/dropdown.js +40 -11
  25. package/dist/components/dropdown.js.map +1 -1
  26. package/dist/components/form/dropdown.d.ts.map +1 -1
  27. package/dist/components/form/dropdown.js +26 -10
  28. package/dist/components/form/dropdown.js.map +1 -1
  29. package/dist/components/list.d.ts +7 -0
  30. package/dist/components/list.d.ts.map +1 -1
  31. package/dist/components/list.js +82 -15
  32. package/dist/components/list.js.map +1 -1
  33. package/dist/components/metadata.d.ts.map +1 -1
  34. package/dist/components/metadata.js +2 -1
  35. package/dist/components/metadata.js.map +1 -1
  36. package/dist/components/spinner.d.ts +6 -0
  37. package/dist/components/spinner.d.ts.map +1 -0
  38. package/dist/components/spinner.js +12 -0
  39. package/dist/components/spinner.js.map +1 -0
  40. package/dist/examples/action-shortcut.d.ts +2 -0
  41. package/dist/examples/action-shortcut.d.ts.map +1 -0
  42. package/dist/examples/action-shortcut.js +20 -0
  43. package/dist/examples/action-shortcut.js.map +1 -0
  44. package/dist/examples/internal/scrollbox-with-descendants.js +19 -8
  45. package/dist/examples/internal/scrollbox-with-descendants.js.map +1 -1
  46. package/dist/examples/list-spacing-default.d.ts +5 -0
  47. package/dist/examples/list-spacing-default.d.ts.map +1 -0
  48. package/dist/examples/list-spacing-default.js +10 -0
  49. package/dist/examples/list-spacing-default.js.map +1 -0
  50. package/dist/examples/list-spacing-mode.d.ts +10 -0
  51. package/dist/examples/list-spacing-mode.d.ts.map +1 -0
  52. package/dist/examples/list-spacing-mode.js +26 -0
  53. package/dist/examples/list-spacing-mode.js.map +1 -0
  54. package/dist/examples/list-spacing-relaxed.d.ts +5 -0
  55. package/dist/examples/list-spacing-relaxed.d.ts.map +1 -0
  56. package/dist/examples/list-spacing-relaxed.js +10 -0
  57. package/dist/examples/list-spacing-relaxed.js.map +1 -0
  58. package/dist/index.d.ts +2 -1
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +1 -0
  61. package/dist/index.js.map +1 -1
  62. package/dist/logger.d.ts.map +1 -1
  63. package/dist/logger.js +2 -1
  64. package/dist/logger.js.map +1 -1
  65. package/dist/state.d.ts +9 -0
  66. package/dist/state.d.ts.map +1 -1
  67. package/dist/state.js +2 -0
  68. package/dist/state.js.map +1 -1
  69. package/dist/swift-runtime.d.ts.map +1 -1
  70. package/dist/swift-runtime.js +20 -5
  71. package/dist/swift-runtime.js.map +1 -1
  72. package/dist/utils.js +1 -1
  73. package/dist/utils.js.map +1 -1
  74. package/package.json +5 -5
  75. package/src/apis/cache.test.ts +1 -1
  76. package/src/apis/cache.tsx +1 -2
  77. package/src/apis/localstorage.tsx +1 -2
  78. package/src/apis/sqlite.ts +14 -0
  79. package/src/build.tsx +7 -1
  80. package/src/compile.tsx +1 -1
  81. package/src/components/actions.tsx +77 -5
  82. package/src/components/detail.tsx +3 -1
  83. package/src/components/dropdown.tsx +49 -11
  84. package/src/components/form/dropdown.tsx +32 -10
  85. package/src/components/list.tsx +157 -18
  86. package/src/components/metadata.tsx +3 -2
  87. package/src/components/spinner.tsx +25 -0
  88. package/src/examples/action-shortcut.tsx +53 -0
  89. package/src/examples/action-shortcut.vitest.tsx +178 -0
  90. package/src/examples/actions-context.vitest.tsx +4 -4
  91. package/src/examples/detail-metadata-showcase.vitest.tsx +32 -18
  92. package/src/examples/form-dropdown.vitest.tsx +8 -8
  93. package/src/examples/form-tagpicker.vitest.tsx +4 -4
  94. package/src/examples/github.vitest.tsx +16 -9
  95. package/src/examples/internal/scrollbox-with-descendants.tsx +27 -8
  96. package/src/examples/list-detail-metadata.vitest.tsx +3 -1
  97. package/src/examples/list-loading-empty-view.vitest.tsx +5 -5
  98. package/src/examples/list-scrollbox.vitest.tsx +5 -5
  99. package/src/examples/list-spacing-default.tsx +38 -0
  100. package/src/examples/list-spacing-mode.tsx +137 -0
  101. package/src/examples/list-spacing-mode.vitest.tsx +158 -0
  102. package/src/examples/list-spacing-relaxed.tsx +38 -0
  103. package/src/examples/list-with-detail.vitest.tsx +63 -28
  104. package/src/examples/list-with-sections.vitest.tsx +14 -14
  105. package/src/examples/simple-detail-markdown.vitest.tsx +3 -4
  106. package/src/examples/simple-file-picker.vitest.tsx +1 -1
  107. package/src/examples/simple-grid.vitest.tsx +42 -35
  108. package/src/examples/simple-navigation.vitest.tsx +14 -14
  109. package/src/extensions/dev.vitest.tsx +9 -3
  110. package/src/index.tsx +2 -0
  111. package/src/logger.tsx +2 -1
  112. package/src/state.tsx +13 -0
  113. package/src/swift-runtime.tsx +19 -5
  114. package/src/utils.test.tsx +1 -1
  115. package/src/utils.tsx +1 -1
@@ -33,7 +33,8 @@ test('detail metadata showcase renders markdown and metadata together', async ()
33
33
  text.includes('Project Update') &&
34
34
  text.includes('Basic Information') &&
35
35
  text.includes('Alice Johnson') &&
36
- text.includes('Watchers')
36
+ text.includes('Watchers') &&
37
+ text.includes('powered by termcast')
37
38
  )
38
39
  },
39
40
  timeout: 20000,
@@ -59,6 +60,7 @@ test('detail metadata showcase renders markdown and metadata together', async ()
59
60
  - Migrated 85% of users to the new platform
60
61
  - Reduced API response time by 40%
61
62
 
63
+
62
64
  Technical Details
63
65
 
64
66
  The refactoring effort focused on three main areas:
@@ -67,6 +69,7 @@ test('detail metadata showcase renders markdown and metadata together', async ()
67
69
  2. Caching layer - Added Redis for session management
68
70
  3. Code cleanup - Removed deprecated endpoints
69
71
 
72
+
70
73
  Next Steps
71
74
 
72
75
  We will continue with Phase 2 in the upcoming sprint. The team should prioritize:
@@ -75,6 +78,7 @@ test('detail metadata showcase renders markdown and metadata together', async ()
75
78
  - Implementing the new dashboard
76
79
  - Writing integration tests
77
80
 
81
+
78
82
  ---
79
83
 
80
84
  Last updated: January 20, 2024
@@ -157,9 +161,6 @@ test('detail metadata showcase renders markdown and metadata together', async ()
157
161
 
158
162
 
159
163
 
160
-
161
-
162
-
163
164
  "
164
165
  `)
165
166
 
@@ -202,7 +203,11 @@ test('detail metadata showcase renders markdown and metadata together', async ()
202
203
 
203
204
  test('detail metadata renders long values in column layout', async () => {
204
205
  const snapshot = await session.text({
205
- waitFor: (text) => text.includes('Description:') && text.includes('comprehensive'),
206
+ waitFor: (text) =>
207
+ text.includes('Description:') &&
208
+ text.includes('comprehensive') &&
209
+ text.includes('PR Link:') &&
210
+ text.includes('powered by termcast'),
206
211
  timeout: 15000,
207
212
  })
208
213
 
@@ -226,6 +231,7 @@ test('detail metadata renders long values in column layout', async () => {
226
231
  - Migrated 85% of users to the new platform
227
232
  - Reduced API response time by 40%
228
233
 
234
+
229
235
  Technical Details
230
236
 
231
237
  The refactoring effort focused on three main areas:
@@ -234,6 +240,7 @@ test('detail metadata renders long values in column layout', async () => {
234
240
  2. Caching layer - Added Redis for session management
235
241
  3. Code cleanup - Removed deprecated endpoints
236
242
 
243
+
237
244
  Next Steps
238
245
 
239
246
  We will continue with Phase 2 in the upcoming sprint. The team should prioritize:
@@ -242,6 +249,7 @@ test('detail metadata renders long values in column layout', async () => {
242
249
  - Implementing the new dashboard
243
250
  - Writing integration tests
244
251
 
252
+
245
253
  ---
246
254
 
247
255
  Last updated: January 20, 2024
@@ -324,9 +332,6 @@ test('detail metadata renders long values in column layout', async () => {
324
332
 
325
333
 
326
334
 
327
-
328
-
329
-
330
335
  "
331
336
  `)
332
337
 
@@ -344,8 +349,17 @@ test('detail metadata renders long values in column layout', async () => {
344
349
  }, 30000)
345
350
 
346
351
  test('detail metadata renders links', async () => {
352
+ // The showcase is long; scroll so the link section is visible.
353
+ await session.scrollDown(40)
354
+
347
355
  const snapshot = await session.text({
348
- waitFor: (text) => text.includes('Repository:') && text.includes('github.com/example'),
356
+ waitFor: (text) =>
357
+ text.includes('Repository:') &&
358
+ text.includes('github.com/example') &&
359
+ text.includes('Docs:') &&
360
+ text.includes('docs.example.com') &&
361
+ text.includes('PR Link:') &&
362
+ text.includes('github.com/organization/repository/pull/12345'),
349
363
  timeout: 15000,
350
364
  })
351
365
 
@@ -398,6 +412,7 @@ test('detail metadata renders tag lists with multiple items', async () => {
398
412
  - Migrated 85% of users to the new platform
399
413
  - Reduced API response time by 40%
400
414
 
415
+
401
416
  Technical Details
402
417
 
403
418
  The refactoring effort focused on three main areas:
@@ -406,6 +421,7 @@ test('detail metadata renders tag lists with multiple items', async () => {
406
421
  2. Caching layer - Added Redis for session management
407
422
  3. Code cleanup - Removed deprecated endpoints
408
423
 
424
+
409
425
  Next Steps
410
426
 
411
427
  We will continue with Phase 2 in the upcoming sprint. The team should prioritize:
@@ -414,6 +430,7 @@ test('detail metadata renders tag lists with multiple items', async () => {
414
430
  - Implementing the new dashboard
415
431
  - Writing integration tests
416
432
 
433
+
417
434
  ---
418
435
 
419
436
  Last updated: January 20, 2024
@@ -426,7 +443,7 @@ test('detail metadata renders tag lists with multiple items', async () => {
426
443
 
427
444
  Team: Platform
428
445
 
429
- ─────────────────────────────────────────────────────────────────────────────────────────────
446
+ ────────────────────────────────────────────────────────────────────────────────────────────
430
447
 
431
448
  Status: Active
432
449
 
@@ -436,7 +453,7 @@ test('detail metadata renders tag lists with multiple items', async () => {
436
453
 
437
454
  Risk: Medium
438
455
 
439
- ─────────────────────────────────────────────────────────────────────────────────────────────
456
+ ────────────────────────────────────────────────────────────────────────────────────────────
440
457
 
441
458
  Description: This is a comprehensive metadata showcase that demonstrates all the different
442
459
  ways you can display information using the Detail.Metadata component.
@@ -447,7 +464,7 @@ test('detail metadata renders tag lists with multiple items', async () => {
447
464
 
448
465
  Reviewer: Bob Smith
449
466
 
450
- ─────────────────────────────────────────────────────────────────────────────────────────────
467
+ ────────────────────────────────────────────────────────────────────────────────────────────
451
468
 
452
469
  Repository: github.com/example
453
470
 
@@ -455,7 +472,7 @@ test('detail metadata renders tag lists with multiple items', async () => {
455
472
 
456
473
  PR Link: github.com/organization/repository/pull/12345
457
474
 
458
- ─────────────────────────────────────────────────────────────────────────────────────────────
475
+ ────────────────────────────────────────────────────────────────────────────────────────────
459
476
 
460
477
  Labels: documentation enhancement good first issue
461
478
 
@@ -469,7 +486,7 @@ test('detail metadata renders tag lists with multiple items', async () => {
469
486
 
470
487
  Due Date: 2024-02-01
471
488
 
472
- ─────────────────────────────────────────────────────────────────────────────────────────────
489
+ ────────────────────────────────────────────────────────────────────────────────────────────
473
490
 
474
491
  Metrics
475
492
 
@@ -483,10 +500,6 @@ test('detail metadata renders tag lists with multiple items', async () => {
483
500
 
484
501
 
485
502
 
486
- esc go back powered by termcast
487
-
488
-
489
-
490
503
 
491
504
 
492
505
 
@@ -498,6 +511,7 @@ test('detail metadata renders tag lists with multiple items', async () => {
498
511
 
499
512
 
500
513
 
514
+ esc go back powered by termcast
501
515
 
502
516
  "
503
517
  `)
@@ -351,11 +351,11 @@ test('form dropdown keyboard navigation', async () => {
351
351
  ◆ Programming Languages █
352
352
  │ TypeScript, Rust █
353
353
  │ █
354
- │ ○ React █
355
- │ ○ Vue █
356
354
  │› ○ Svelte █
357
355
  │ Backend █
358
356
  │ ○ Node.js █
357
+ │ ○ Python █
358
+ │ ○ Go █
359
359
  │ █
360
360
  │ Choose your preferred programming languages █
361
361
  │ █
@@ -465,11 +465,11 @@ test('form dropdown keyboard navigation', async () => {
465
465
  ◆ Programming Languages █
466
466
  │ TypeScript, Rust █
467
467
  │ █
468
- │ ○ React █
469
- │ ○ Vue █
470
468
  │› ○ Svelte █
471
469
  │ Backend █
472
470
  │ ○ Node.js █
471
+ │ ○ Python █
472
+ │ ○ Go █
473
473
  │ █
474
474
  │ Choose your preferred programming languages █
475
475
  │ █
@@ -522,11 +522,11 @@ test('form dropdown keyboard navigation', async () => {
522
522
  ◆ Programming Languages █
523
523
  │ TypeScript, Rust █
524
524
  │ █
525
- │ ○ React █
526
- │ ○ Vue █
527
525
  │› ○ Svelte █
528
526
  │ Backend █
529
527
  │ ○ Node.js █
528
+ │ ○ Python █
529
+ │ ○ Go █
530
530
  │ █
531
531
  │ Choose your preferred programming languages █
532
532
  │ █
@@ -715,11 +715,11 @@ test('selecting second-to-last visible item should not scroll', async () => {
715
715
  ◆ Programming Languages █
716
716
  │ TypeScript, Rust █
717
717
  │ █
718
+ │ Frontend █
718
719
  │ ● TypeScript █
719
720
  │ ○ JavaScript █
720
721
  │› ○ React █
721
722
  │ ○ Vue █
722
- │ ○ Svelte █
723
723
  │ █
724
724
  │ Choose your preferred programming languages █
725
725
  │ █
@@ -772,11 +772,11 @@ test('selecting second-to-last visible item should not scroll', async () => {
772
772
  ◆ Programming Languages █
773
773
  │ TypeScript, Rust, React █
774
774
  │ █
775
+ │ Frontend █
775
776
  │ ● TypeScript █
776
777
  │ ○ JavaScript █
777
778
  │› ● React █
778
779
  │ ○ Vue █
779
- │ ○ Svelte █
780
780
  │ █
781
781
  │ Choose your preferred programming languages █
782
782
  │ █
@@ -395,11 +395,11 @@ test('form tagpicker keyboard navigation', async () => {
395
395
  ◆ Favorite Sport
396
396
  │ Choose your favorite sport...
397
397
 
398
+ │ ○ Basketball
399
+ │ ○ Football
398
400
  │ ○ Tennis
399
401
  │ ○ Baseball
400
402
  │› ○ Golf
401
- │ ○ Swimming
402
- │ ○ Cycling
403
403
 
404
404
  │ Select your favorite sport from the list
405
405
 
@@ -509,11 +509,11 @@ test('form tagpicker keyboard navigation', async () => {
509
509
  ◆ Favorite Sport
510
510
  │ Choose your favorite sport...
511
511
 
512
- │ ○ Tennis
513
512
  │ ○ Baseball
514
513
  │› ○ Golf
515
514
  │ ○ Swimming
516
515
  │ ○ Cycling
516
+ │ ○ Running
517
517
 
518
518
  │ Select your favorite sport from the list
519
519
 
@@ -566,11 +566,11 @@ test('form tagpicker keyboard navigation', async () => {
566
566
  ◆ Favorite Sport
567
567
  │ Choose your favorite sport...
568
568
 
569
- │ ○ Tennis
570
569
  │ ○ Baseball
571
570
  │› ○ Golf
572
571
  │ ○ Swimming
573
572
  │ ○ Cycling
573
+ │ ○ Running
574
574
 
575
575
  │ Select your favorite sport from the list
576
576
 
@@ -61,6 +61,13 @@ test.skipIf(!extensionExists)('github extension shows command list on launch', a
61
61
  timeout: 30000,
62
62
  })
63
63
 
64
+ // Wait for the full command list to render.
65
+ // The list can paint the first item before all descendants are registered.
66
+ await session.text({
67
+ waitFor: (text) => text.includes('My Pull Requests') && text.includes('Search Repositories'),
68
+ timeout: 30000,
69
+ })
70
+
64
71
  expect(initialView).toContain('My Pull Requests')
65
72
  expect(initialView).toContain('Search Repositories')
66
73
  expect(initialView).toMatchInlineSnapshot(`
@@ -101,7 +108,7 @@ test.skipIf(!extensionExists)('github extension shows command list on launch', a
101
108
  test.skipIf(!extensionExists)('github extension can navigate commands', async () => {
102
109
  // Wait for command list
103
110
  await session.text({
104
- waitFor: (text) => /My Pull Requests|Search Repositories/i.test(text),
111
+ waitFor: (text) => text.includes('My Pull Requests') && text.includes('Search Repositories'),
105
112
  timeout: 30000,
106
113
  })
107
114
 
@@ -172,26 +179,26 @@ test.skipIf(!extensionExists)('github extension can open actions panel', async (
172
179
 
173
180
  > Search commands...
174
181
 
175
- Commands
176
- ›My Pull Requests List pull requests you created, participated in, or view
177
182
  ╭──────────────────────────────────────────────────────────────────────────╮
178
183
  │ │
179
184
  │ Actions esc │
180
185
  │ │
181
186
  │ > Search actions... │
182
187
  │ │
183
- │ ›Run Command
184
- │ Copy File Path
185
- │ Copy Command Info
186
-
188
+ │ ›Run Command
189
+ │ Copy File Path
190
+ │ Copy Command Info
191
+
187
192
  │ Settings │
188
- │ Configure GitHub... ⌃⇧,
193
+ │ Configure GitHub... ⌃⇧,
189
194
  │ Change Theme... │
195
+ │ See Console Logs │
196
+ │ │
197
+ │ │
190
198
  │ │
191
199
  │ ↵ select ↑↓ navigate │
192
200
  │ │
193
201
  ╰──────────────────────────────────────────────────────────────────────────╯
194
- ↵ run command ↑↓ navigate ^k actions powered by termcast
195
202
 
196
203
 
197
204
 
@@ -16,20 +16,39 @@ function ScrollboxWithDescendants() {
16
16
  const [selectedIndex, setSelectedIndex] = React.useState(0)
17
17
  const scrollBoxRef = React.useRef<any>(null)
18
18
 
19
- const scrollToItem = (item: { props?: ItemDescendant }) => {
19
+ const scrollToItemIfNeeded = ({
20
+ item,
21
+ direction,
22
+ }: {
23
+ item: { props?: ItemDescendant }
24
+ direction: -1 | 1
25
+ }) => {
20
26
  const scrollBox = scrollBoxRef.current
21
27
  const elementRef = item.props?.elementRef
22
28
  if (!scrollBox || !elementRef) return
23
29
 
24
30
  const contentY = scrollBox.content?.y || 0
31
+ const scrollTop = scrollBox.scrollTop || 0
25
32
  const viewportHeight = scrollBox.viewport?.height || 10
26
33
 
27
- // Calculate item position relative to content
28
34
  const itemTop = elementRef.y - contentY
35
+ const itemHeight = elementRef.height || 1
36
+ const itemBottom = itemTop + itemHeight
29
37
 
30
- // Scroll so the top of the item is centered in the viewport
31
- const targetScrollTop = itemTop - viewportHeight / 2
32
- scrollBox.scrollTo(Math.max(0, targetScrollTop))
38
+ const viewportTop = scrollTop
39
+ const viewportBottom = scrollTop + viewportHeight
40
+
41
+ if (direction === 1) {
42
+ if (itemBottom > viewportBottom) {
43
+ scrollBox.scrollTo(Math.max(0, itemTop))
44
+ }
45
+ return
46
+ }
47
+
48
+ if (itemTop < viewportTop) {
49
+ const targetScrollTop = itemBottom - viewportHeight
50
+ scrollBox.scrollTo(Math.max(0, targetScrollTop))
51
+ }
33
52
  }
34
53
 
35
54
  const move = (direction: -1 | 1) => {
@@ -40,15 +59,15 @@ function ScrollboxWithDescendants() {
40
59
  if (items.length === 0) return
41
60
 
42
61
  let nextIndex = selectedIndex + direction
43
- if (nextIndex < 0) nextIndex = items.length - 1
44
- if (nextIndex >= items.length) nextIndex = 0
62
+ if (nextIndex < 0) return
63
+ if (nextIndex >= items.length) return
45
64
 
46
65
  const nextItem = items[nextIndex]
47
66
  if (nextItem) {
48
67
  flushSync(() => {
49
68
  setSelectedIndex(nextIndex)
50
69
  })
51
- scrollToItem(nextItem)
70
+ scrollToItemIfNeeded({ item: nextItem, direction })
52
71
  }
53
72
  }
54
73
 
@@ -23,7 +23,9 @@ test('list detail metadata label renders short values in row layout (key: value)
23
23
  text.includes('Metadata Test') &&
24
24
  text.includes('Name') &&
25
25
  text.includes('John Doe') &&
26
- text.includes('Email')
26
+ text.includes('Email') &&
27
+ text.includes('Website') &&
28
+ text.includes('↑↓ navigate')
27
29
  )
28
30
  },
29
31
  })
@@ -25,19 +25,19 @@ test('empty view shows spinner when list is loading', async () => {
25
25
  },
26
26
  })
27
27
 
28
- // Spinner animates. Normalize for stable snapshots.
29
- const normalized = text.replace(/[◰◳◲◱]/g, '')
28
+ // Spinner animates (pulsing dots: ' ' · •). Normalize for stable snapshots.
29
+ const normalized = text.replace(/[·•]/g, '')
30
30
  expect(normalized).toMatchInlineSnapshot(`
31
31
  "
32
32
 
33
33
 
34
34
  Loading Empty View ───────────────────────────────────
35
35
 
36
- > Search...
36
+ Search...
37
37
 
38
38
 
39
39
 
40
- loading...
40
+ loading...
41
41
 
42
42
 
43
43
 
@@ -45,5 +45,5 @@ test('empty view shows spinner when list is loading', async () => {
45
45
 
46
46
  ↑↓ navigate ^k actions"
47
47
  `)
48
- expect(text).toMatch(/[◰◳◲◱]\s+loading\.\.\./)
48
+ expect(text).toMatch(/[·• ]\s*loading\.\.\./)
49
49
  }, 10000)
@@ -69,12 +69,12 @@ test('list scrollbox auto-scrolls when navigating down', async () => {
69
69
 
70
70
  > Search items...
71
71
 
72
+ ○ Item 1 Description for item 1
73
+ ★ Item 2 Description for item 2
72
74
  ◆ Item 3 Description for item 3
73
75
  ↯ Item 4 Description for item 4
74
76
  ▷ Item 5 Description for item 5
75
- ›▦ Item 6 Description for item 6
76
- ◴ Item 7 Description for item 7
77
- ▯ Item 8 Description for item 8"
77
+ ›▦ Item 6 Description for item 6"
78
78
  `)
79
79
 
80
80
  await session.press('down')
@@ -90,12 +90,12 @@ test('list scrollbox auto-scrolls when navigating down', async () => {
90
90
 
91
91
  > Search items...
92
92
 
93
- ▦ Item 6 Description for item 6
94
93
  ◴ Item 7 Description for item 7
95
94
  ▯ Item 8 Description for item 8
96
95
  ›▤ Item 9 Description for item 9
97
96
  Item 10 Description for item 10
98
- ○ Item 11 Description for item 11"
97
+ ○ Item 11 Description for item 11
98
+ ★ Item 12 Description for item 12"
99
99
  `)
100
100
 
101
101
  await session.press('up')
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Test file for List with spacingMode="default" (single-line items)
3
+ */
4
+
5
+ import { List, Icon, Color, renderWithProviders } from 'termcast'
6
+
7
+ function ListSpacingDefault() {
8
+ return (
9
+ <List navigationTitle="Default Mode" spacingMode="default">
10
+ <List.Section title="With Icons">
11
+ <List.Item
12
+ icon={Icon.Document}
13
+ title="Report"
14
+ subtitle="Q4 financial summary"
15
+ accessories={[{ tag: { value: 'Draft', color: Color.Yellow } }]}
16
+ />
17
+ <List.Item
18
+ icon={Icon.Code}
19
+ title="API Docs"
20
+ subtitle="REST endpoints guide"
21
+ accessories={[{ text: 'v2.1' }]}
22
+ />
23
+ </List.Section>
24
+ <List.Section title="Without Icons">
25
+ <List.Item
26
+ title="Meeting Notes"
27
+ subtitle="Weekly standup points"
28
+ accessories={[{ tag: 'Important' }]}
29
+ />
30
+ </List.Section>
31
+ <List.Section title="No Subtitle">
32
+ <List.Item icon={Icon.Star} title="Favorites" />
33
+ </List.Section>
34
+ </List>
35
+ )
36
+ }
37
+
38
+ await renderWithProviders(<ListSpacingDefault />)
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Example demonstrating List spacingMode prop.
3
+ *
4
+ * - 'default': Single-line items with title and subtitle on same row
5
+ * - 'relaxed': Two-line items with title on first row, subtitle below aligned with title start
6
+ *
7
+ * Use Action to toggle between modes and see the difference.
8
+ */
9
+
10
+ import { useState } from 'react'
11
+ import {
12
+ List,
13
+ ActionPanel,
14
+ Action,
15
+ Icon,
16
+ Color,
17
+ renderWithProviders,
18
+ type ListSpacingMode,
19
+ } from 'termcast'
20
+
21
+ function ListSpacingModeExample() {
22
+ const [spacingMode, setSpacingMode] = useState<ListSpacingMode>('relaxed')
23
+
24
+ const toggleMode = () => {
25
+ setSpacingMode((prev) => (prev === 'default' ? 'relaxed' : 'default'))
26
+ }
27
+
28
+ return (
29
+ <List
30
+ navigationTitle={`Spacing Mode: ${spacingMode}`}
31
+ spacingMode={spacingMode}
32
+ >
33
+ <List.Section title="With Icons" subtitle="Items have icon, title, subtitle">
34
+ <List.Item
35
+ icon={Icon.Document}
36
+ title="Quarterly Report"
37
+ subtitle="Q4 2024 financial summary and projections"
38
+ accessories={[{ tag: { value: 'Draft', color: Color.Yellow } }]}
39
+ actions={
40
+ <ActionPanel>
41
+ <Action title="Toggle Spacing Mode" onAction={toggleMode} />
42
+ </ActionPanel>
43
+ }
44
+ />
45
+ <List.Item
46
+ icon={Icon.Code}
47
+ title="API Documentation"
48
+ subtitle="REST endpoints and authentication guide"
49
+ accessories={[
50
+ { text: 'v2.1' },
51
+ { date: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000) },
52
+ ]}
53
+ actions={
54
+ <ActionPanel>
55
+ <Action title="Toggle Spacing Mode" onAction={toggleMode} />
56
+ </ActionPanel>
57
+ }
58
+ />
59
+ <List.Item
60
+ icon={Icon.Gear}
61
+ title="Configuration"
62
+ subtitle="System settings and preferences"
63
+ accessories={[{ tag: { value: 'Active', color: Color.Green } }]}
64
+ actions={
65
+ <ActionPanel>
66
+ <Action title="Toggle Spacing Mode" onAction={toggleMode} />
67
+ </ActionPanel>
68
+ }
69
+ />
70
+ </List.Section>
71
+
72
+ <List.Section title="Without Icons" subtitle="Plain text items">
73
+ <List.Item
74
+ title="Meeting Notes"
75
+ subtitle="Weekly standup discussion points"
76
+ accessories={[{ date: new Date(Date.now() - 60 * 60 * 1000) }]}
77
+ actions={
78
+ <ActionPanel>
79
+ <Action title="Toggle Spacing Mode" onAction={toggleMode} />
80
+ </ActionPanel>
81
+ }
82
+ />
83
+ <List.Item
84
+ title="Project Timeline"
85
+ subtitle="Milestones and deadlines for Q1"
86
+ accessories={[{ text: { value: 'Important', color: Color.Red } }]}
87
+ actions={
88
+ <ActionPanel>
89
+ <Action title="Toggle Spacing Mode" onAction={toggleMode} />
90
+ </ActionPanel>
91
+ }
92
+ />
93
+ </List.Section>
94
+
95
+ <List.Section title="No Subtitle" subtitle="Title only items">
96
+ <List.Item
97
+ icon={Icon.Star}
98
+ title="Favorites"
99
+ accessories={[{ tag: '12 items' }]}
100
+ actions={
101
+ <ActionPanel>
102
+ <Action title="Toggle Spacing Mode" onAction={toggleMode} />
103
+ </ActionPanel>
104
+ }
105
+ />
106
+ <List.Item
107
+ title="Recent"
108
+ accessories={[{ date: new Date() }]}
109
+ actions={
110
+ <ActionPanel>
111
+ <Action title="Toggle Spacing Mode" onAction={toggleMode} />
112
+ </ActionPanel>
113
+ }
114
+ />
115
+ </List.Section>
116
+
117
+ <List.Section title="Long Content" subtitle="Testing overflow behavior">
118
+ <List.Item
119
+ icon={Icon.Text}
120
+ title="Very Long Title That Might Need Truncation"
121
+ subtitle="This is a particularly verbose subtitle that describes the item in great detail"
122
+ accessories={[
123
+ { tag: { value: 'Beta', color: Color.Blue } },
124
+ { text: 'Updated' },
125
+ ]}
126
+ actions={
127
+ <ActionPanel>
128
+ <Action title="Toggle Spacing Mode" onAction={toggleMode} />
129
+ </ActionPanel>
130
+ }
131
+ />
132
+ </List.Section>
133
+ </List>
134
+ )
135
+ }
136
+
137
+ await renderWithProviders(<ListSpacingModeExample />)