termcast 1.3.46 → 1.3.47

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 (85) hide show
  1. package/dist/build.d.ts.map +1 -1
  2. package/dist/build.js +12 -3
  3. package/dist/build.js.map +1 -1
  4. package/dist/components/actions.d.ts +18 -0
  5. package/dist/components/actions.d.ts.map +1 -1
  6. package/dist/components/actions.js +70 -5
  7. package/dist/components/actions.js.map +1 -1
  8. package/dist/components/detail.d.ts.map +1 -1
  9. package/dist/components/detail.js +3 -1
  10. package/dist/components/detail.js.map +1 -1
  11. package/dist/components/dropdown.d.ts.map +1 -1
  12. package/dist/components/dropdown.js +40 -11
  13. package/dist/components/dropdown.js.map +1 -1
  14. package/dist/components/form/dropdown.d.ts.map +1 -1
  15. package/dist/components/form/dropdown.js +26 -10
  16. package/dist/components/form/dropdown.js.map +1 -1
  17. package/dist/components/list.d.ts +7 -0
  18. package/dist/components/list.d.ts.map +1 -1
  19. package/dist/components/list.js +82 -15
  20. package/dist/components/list.js.map +1 -1
  21. package/dist/components/metadata.d.ts.map +1 -1
  22. package/dist/components/metadata.js +2 -1
  23. package/dist/components/metadata.js.map +1 -1
  24. package/dist/components/spinner.d.ts +6 -0
  25. package/dist/components/spinner.d.ts.map +1 -0
  26. package/dist/components/spinner.js +12 -0
  27. package/dist/components/spinner.js.map +1 -0
  28. package/dist/examples/action-shortcut.d.ts +2 -0
  29. package/dist/examples/action-shortcut.d.ts.map +1 -0
  30. package/dist/examples/action-shortcut.js +20 -0
  31. package/dist/examples/action-shortcut.js.map +1 -0
  32. package/dist/examples/internal/scrollbox-with-descendants.js +19 -8
  33. package/dist/examples/internal/scrollbox-with-descendants.js.map +1 -1
  34. package/dist/examples/list-spacing-default.d.ts +5 -0
  35. package/dist/examples/list-spacing-default.d.ts.map +1 -0
  36. package/dist/examples/list-spacing-default.js +10 -0
  37. package/dist/examples/list-spacing-default.js.map +1 -0
  38. package/dist/examples/list-spacing-mode.d.ts +10 -0
  39. package/dist/examples/list-spacing-mode.d.ts.map +1 -0
  40. package/dist/examples/list-spacing-mode.js +26 -0
  41. package/dist/examples/list-spacing-mode.js.map +1 -0
  42. package/dist/examples/list-spacing-relaxed.d.ts +5 -0
  43. package/dist/examples/list-spacing-relaxed.d.ts.map +1 -0
  44. package/dist/examples/list-spacing-relaxed.js +10 -0
  45. package/dist/examples/list-spacing-relaxed.js.map +1 -0
  46. package/dist/index.d.ts +2 -1
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +1 -0
  49. package/dist/index.js.map +1 -1
  50. package/dist/state.d.ts +9 -0
  51. package/dist/state.d.ts.map +1 -1
  52. package/dist/state.js +2 -0
  53. package/dist/state.js.map +1 -1
  54. package/package.json +3 -3
  55. package/src/build.tsx +12 -3
  56. package/src/components/actions.tsx +77 -5
  57. package/src/components/detail.tsx +3 -1
  58. package/src/components/dropdown.tsx +49 -11
  59. package/src/components/form/dropdown.tsx +32 -10
  60. package/src/components/list.tsx +157 -18
  61. package/src/components/metadata.tsx +3 -2
  62. package/src/components/spinner.tsx +25 -0
  63. package/src/examples/action-shortcut.tsx +53 -0
  64. package/src/examples/action-shortcut.vitest.tsx +178 -0
  65. package/src/examples/actions-context.vitest.tsx +4 -4
  66. package/src/examples/detail-metadata-showcase.vitest.tsx +17 -3
  67. package/src/examples/form-dropdown.vitest.tsx +8 -8
  68. package/src/examples/form-tagpicker.vitest.tsx +4 -4
  69. package/src/examples/github.vitest.tsx +16 -9
  70. package/src/examples/internal/scrollbox-with-descendants.tsx +27 -8
  71. package/src/examples/list-detail-metadata.vitest.tsx +3 -1
  72. package/src/examples/list-loading-empty-view.vitest.tsx +5 -5
  73. package/src/examples/list-scrollbox.vitest.tsx +5 -5
  74. package/src/examples/list-spacing-default.tsx +38 -0
  75. package/src/examples/list-spacing-mode.tsx +137 -0
  76. package/src/examples/list-spacing-mode.vitest.tsx +158 -0
  77. package/src/examples/list-spacing-relaxed.tsx +38 -0
  78. package/src/examples/list-with-detail.vitest.tsx +63 -28
  79. package/src/examples/list-with-sections.vitest.tsx +13 -13
  80. package/src/examples/simple-file-picker.vitest.tsx +1 -1
  81. package/src/examples/simple-grid.vitest.tsx +42 -35
  82. package/src/examples/simple-navigation.vitest.tsx +14 -14
  83. package/src/extensions/dev.vitest.tsx +9 -3
  84. package/src/index.tsx +2 -0
  85. package/src/state.tsx +13 -0
@@ -8,7 +8,7 @@ beforeEach(async () => {
8
8
  command: 'bun',
9
9
  args: ['src/examples/list-with-detail.tsx'],
10
10
  cols: 80,
11
- rows: 30,
11
+ rows: 33,
12
12
  })
13
13
  })
14
14
 
@@ -58,7 +58,10 @@ test('list with detail view display and navigation', async () => {
58
58
 
59
59
 
60
60
  │ Types
61
- ↵ toggle detail ↑↓ navigate ^k a
61
+
62
+ │ Grass
63
+
64
+ ↵ toggle detail ↑↓ navigate ^k a │ ───────────────────────────────── ▼
62
65
 
63
66
  "
64
67
  `)
@@ -97,7 +100,10 @@ test('list with detail view display and navigation', async () => {
97
100
 
98
101
 
99
102
  │ Types
100
- ↵ toggle detail ↑↓ navigate ^k a
103
+
104
+ │ Grass
105
+
106
+ ↵ toggle detail ↑↓ navigate ^k a │ ───────────────────────────────── ▼
101
107
 
102
108
  "
103
109
  `)
@@ -134,7 +140,10 @@ test('list with detail view display and navigation', async () => {
134
140
 
135
141
 
136
142
  │ Types
137
- ↵ toggle detail ↑↓ navigate ^k a
143
+
144
+ │ Fire
145
+
146
+ ↵ toggle detail ↑↓ navigate ^k a │ ───────────────────────────────── ▼
138
147
 
139
148
  "
140
149
  `)
@@ -170,11 +179,14 @@ test('list with detail view display and navigation', async () => {
170
179
  │ See Console Logs │
171
180
  │ │
172
181
  │ │
182
+ │ │
183
+ │ │
184
+ │ │
173
185
  │ ↵ select ↑↓ navigate │
174
186
  │ │
175
187
  ╰──────────────────────────────────────────────────────────────────────────╯
176
- Types
177
- ↵ toggle detail ↑↓ navigate ^k a │
188
+
189
+ ↵ toggle detail ↑↓ navigate ^k a │ ─────────────────────────────────
178
190
 
179
191
  "
180
192
  `)
@@ -217,6 +229,9 @@ test('list with detail view display and navigation', async () => {
217
229
 
218
230
 
219
231
 
232
+
233
+
234
+
220
235
  "
221
236
  `)
222
237
 
@@ -257,7 +272,10 @@ test('list with detail view display and navigation', async () => {
257
272
 
258
273
 
259
274
  │ Types
260
- ↵ toggle detail ↑↓ navigate ^k a
275
+
276
+ │ Fire
277
+
278
+ ↵ toggle detail ↑↓ navigate ^k a │ ───────────────────────────────── ▼
261
279
 
262
280
  "
263
281
  `)
@@ -306,7 +324,10 @@ test('list detail view search functionality', async () => {
306
324
 
307
325
 
308
326
  │ Types
309
- ↵ toggle detail ↑↓ navigate ^k a
327
+
328
+ │ Fire
329
+
330
+ ↵ toggle detail ↑↓ navigate ^k a │ ───────────────────────────────── ▼
310
331
 
311
332
  "
312
333
  `)
@@ -332,27 +353,30 @@ test('list detail view search functionality', async () => {
332
353
  > water
333
354
 
334
355
 
335
- bulbasaur
336
- No items found │
356
+ wartortle
357
+ No items found │
337
358
  │ Illustration
338
359
 
339
360
  │ Types
340
361
 
341
- Grass / Poison
362
+ Water
342
363
 
343
364
  │ Characteristics
344
365
 
345
- │ - Height: 0.7m
346
- │ - Weight: 6.9kg
366
+ │ - Height: 1m
367
+ │ - Weight: 22.5kg
347
368
 
348
369
  │ Abilities
349
370
 
350
- │ - Chlorophyll
351
- │ - Overgrow
371
+ │ - Torrent
372
+ │ - Rain Dish
352
373
 
353
374
 
354
375
  │ Types
355
- ↵ toggle detail ↑↓ navigate ^k a
376
+
377
+ │ Water
378
+
379
+ ↵ toggle detail ↑↓ navigate ^k a │ ───────────────────────────────── ▼
356
380
 
357
381
  "
358
382
  `)
@@ -369,27 +393,30 @@ test('list detail view search functionality', async () => {
369
393
  > water
370
394
 
371
395
 
372
- bulbasaur
373
- No items found │
396
+ wartortle
397
+ No items found │
374
398
  │ Illustration
375
399
 
376
400
  │ Types
377
401
 
378
- Grass / Poison
402
+ Water
379
403
 
380
404
  │ Characteristics
381
405
 
382
- │ - Height: 0.7m
383
- │ - Weight: 6.9kg
406
+ │ - Height: 1m
407
+ │ - Weight: 22.5kg
384
408
 
385
409
  │ Abilities
386
410
 
387
- │ - Chlorophyll
388
- │ - Overgrow
411
+ │ - Torrent
412
+ │ - Rain Dish
389
413
 
390
414
 
391
415
  │ Types
392
- ↵ toggle detail ↑↓ navigate ^k a
416
+
417
+ │ Water
418
+
419
+ ↵ toggle detail ↑↓ navigate ^k a │ ───────────────────────────────── ▼
393
420
 
394
421
  "
395
422
  `)
@@ -438,7 +465,10 @@ test('list detail metadata rendering', async () => {
438
465
 
439
466
 
440
467
  │ Types
441
- ↵ toggle detail ↑↓ navigate ^k a
468
+
469
+ │ Grass
470
+
471
+ ↵ toggle detail ↑↓ navigate ^k a │ ───────────────────────────────── ▼
442
472
 
443
473
  "
444
474
  `)
@@ -482,7 +512,10 @@ test('list detail metadata rendering', async () => {
482
512
 
483
513
 
484
514
  │ Types
485
- ↵ toggle detail ↑↓ navigate ^k a
515
+
516
+ │ Water
517
+
518
+ ↵ toggle detail ↑↓ navigate ^k a │ ───────────────────────────────── ▼
486
519
 
487
520
  "
488
521
  `)
@@ -502,7 +535,8 @@ test('list with detail layout consistency - short vs long detail content', async
502
535
  waitFor: (text) => {
503
536
  return (
504
537
  text.includes('›Short Detail') &&
505
- text.includes('Brief content')
538
+ text.includes('Brief content') &&
539
+ text.includes('↑↓ navigate')
506
540
  )
507
541
  },
508
542
  })
@@ -514,7 +548,8 @@ test('list with detail layout consistency - short vs long detail content', async
514
548
  waitFor: (text) => {
515
549
  return (
516
550
  text.includes('›Long Detail') &&
517
- text.includes('extensive')
551
+ text.includes('extensive') &&
552
+ text.includes('↑↓ navigate')
518
553
  )
519
554
  },
520
555
  })
@@ -440,8 +440,6 @@ test('list actions panel with ctrl+k', async () => {
440
440
  "
441
441
 
442
442
 
443
- Simple List Example ────────────────────────────────────────────
444
-
445
443
  ╭────────────────────────────────────────────────────────────────╮
446
444
  │ │
447
445
  │ Actions esc │
@@ -456,8 +454,10 @@ test('list actions panel with ctrl+k', async () => {
456
454
  │ See Console Logs │
457
455
  │ │
458
456
  │ │
459
- ↵ select ↑↓ navigate
460
- │ │"
457
+
458
+ │ │
459
+ │ │
460
+ │ ↵ select ↑↓ navigate │"
461
461
  `)
462
462
 
463
463
  // Navigate down to second action
@@ -468,8 +468,6 @@ test('list actions panel with ctrl+k', async () => {
468
468
  "
469
469
 
470
470
 
471
- Simple List Example ────────────────────────────────────────────
472
-
473
471
  ╭────────────────────────────────────────────────────────────────╮
474
472
  │ │
475
473
  │ Actions esc │
@@ -484,8 +482,10 @@ test('list actions panel with ctrl+k', async () => {
484
482
  │ See Console Logs │
485
483
  │ │
486
484
  │ │
487
- ↵ select ↑↓ navigate
488
- │ │"
485
+
486
+ │ │
487
+ │ │
488
+ │ ↵ select ↑↓ navigate │"
489
489
  `)
490
490
 
491
491
  // Trigger the second action (Add to Cart)
@@ -706,15 +706,15 @@ test('list scrollbox scrolling with sections', async () => {
706
706
 
707
707
  > Search items...
708
708
 
709
+ Fruits
710
+ Apple Red and sweet Fresh [Popular]
711
+ Banana Yellow and nutritious Ripe
709
712
  Orange Citrus and juicy Fresh
710
713
  Grape Sweet clusters [Seasonal]
711
714
  Mango Tropical delight Imported
712
715
  Pineapple Sweet and tangy
713
716
  ›Strawberry Red and sweet [Popular]
714
717
 
715
- Vegetables
716
- Carrot Orange and crunchy [Healthy]
717
- Lettuce Green and fresh
718
718
 
719
719
 
720
720
  ↑↓ navigate ^k actions
@@ -736,8 +736,6 @@ test('list scrollbox scrolling with sections', async () => {
736
736
 
737
737
  > Search items...
738
738
 
739
-
740
- Vegetables
741
739
  Carrot Orange and crunchy [Healthy]
742
740
  Lettuce Green and fresh
743
741
  ›Broccoli Green florets [Healthy]
@@ -746,6 +744,8 @@ test('list scrollbox scrolling with sections', async () => {
746
744
  Cucumber Cool and crisp
747
745
  Bell Pepper Colorful and crunchy [Fresh]
748
746
 
747
+ Bread Freshly baked Today [New]
748
+
749
749
 
750
750
  ↑↓ navigate ^k actions
751
751
 
@@ -51,6 +51,7 @@ test('file picker shows form fields', async () => {
51
51
 
52
52
 
53
53
 
54
+ ctrl ↵ submit tab navigate ^k actions
54
55
 
55
56
 
56
57
 
@@ -76,7 +77,6 @@ test('file picker shows form fields', async () => {
76
77
 
77
78
 
78
79
 
79
- ctrl ↵ submit tab navigate ^k actions
80
80
 
81
81
  "
82
82
  `)
@@ -137,7 +137,6 @@ test('grid navigation and display', async () => {
137
137
  Simple Grid Example ────────────────────────────────────────────
138
138
 
139
139
  > Search items...
140
-
141
140
  ╭────────────────────────────────────────────────────────────────╮
142
141
  │ │
143
142
  │ Actions esc │
@@ -152,11 +151,12 @@ test('grid navigation and display', async () => {
152
151
  │ See Console Logs │
153
152
  │ │
154
153
  │ │
154
+ │ │
155
+ │ │
156
+ │ │
155
157
  │ ↵ select ↑↓ navigate │
156
158
  │ │
157
- ╰────────────────────────────────────────────────────────────────╯
158
-
159
- "
159
+ ╰────────────────────────────────────────────────────────────────╯"
160
160
  `)
161
161
 
162
162
  // Close actions with escape
@@ -522,7 +522,6 @@ test('grid mouse interaction', async () => {
522
522
  Simple Grid Example ────────────────────────────────────────────
523
523
 
524
524
  > Search items...
525
-
526
525
  ╭────────────────────────────────────────────────────────────────╮
527
526
  │ │
528
527
  │ Actions esc │
@@ -537,23 +536,31 @@ test('grid mouse interaction', async () => {
537
536
  │ See Console Logs │
538
537
  │ │
539
538
  │ │
539
+ │ │
540
+ │ │
541
+ │ │
540
542
  │ ↵ select ↑↓ navigate │
541
543
  │ │
542
- ╰────────────────────────────────────────────────────────────────╯
543
-
544
- "
544
+ ╰────────────────────────────────────────────────────────────────╯"
545
545
  `)
546
546
 
547
547
  // Close the actions panel first
548
548
  await session.press('esc')
549
549
 
550
- // Navigate back up to make Apple visible
551
- await session.press('up')
552
- await session.press('up')
553
- await session.press('up')
554
- await session.press('up')
555
- await session.press('up')
556
- await session.press('up')
550
+ // Navigate back up to make Apple visible.
551
+ // Grid is implemented via List, which uses edge-triggered pagination.
552
+ // Pressing up a fixed small number of times can leave the viewport unchanged.
553
+ // Overshooting is safe since List doesn't wrap at the top boundary.
554
+ for (let i = 0; i < 30; i++) {
555
+ await session.press('up')
556
+ }
557
+
558
+ await session.text({
559
+ waitFor: (text) => {
560
+ return text.includes('Apple')
561
+ },
562
+ timeout: 5000,
563
+ })
557
564
 
558
565
  // Click on "Apple" to go back to first section
559
566
  await session.click('Apple', { first: true })
@@ -566,25 +573,25 @@ test('grid mouse interaction', async () => {
566
573
  Simple Grid Example ────────────────────────────────────────────
567
574
 
568
575
  > Search items...
569
-
570
- Fruits
571
- ›🍎 Apple
572
- 🍌 Banana
573
- 🍒 Cherry
574
-
575
- Animals
576
- 🐕 Dog
577
- 🐱 Cat
578
- 🐰 Rabbit
579
-
580
- Others
581
- 🏠 House
582
- 🚗 Car
583
- 🚀 Rocket
584
-
585
-
586
- show details ↑↓ navigate ^k actions
587
-
588
- "
576
+ ╭────────────────────────────────────────────────────────────────╮
577
+ │ │
578
+ │ Actions esc │
579
+ │ │
580
+ │ > Search actions... │
581
+ │ │
582
+ │ ›Show Details │
583
+ │ Copy Emoji ⌃C │
584
+ │ │
585
+ │ Settings │
586
+ │ Change Theme... │
587
+ │ See Console Logs │
588
+ │ │
589
+ │ │
590
+ │ │
591
+ │ │
592
+ │ │
593
+ select ↑↓ navigate
594
+ │ │
595
+ ╰────────────────────────────────────────────────────────────────╯"
589
596
  `)
590
597
  }, 10000)
@@ -295,7 +295,6 @@ test('navigation with actions panel', async () => {
295
295
  expect(actionsOpenSnapshot).toMatchInlineSnapshot(`
296
296
  "
297
297
 
298
-
299
298
  ╭────────────────────────────────────────────────────────────────╮
300
299
  │ │
301
300
  │ Actions esc │
@@ -310,14 +309,15 @@ test('navigation with actions panel', async () => {
310
309
  │ See Console Logs │
311
310
  │ │
312
311
  │ │
312
+ │ │
313
+ │ │
314
+ │ │
313
315
  │ ↵ select ↑↓ navigate │
314
316
  │ │
315
317
  ╰────────────────────────────────────────────────────────────────╯
316
318
 
317
319
 
318
320
 
319
-
320
-
321
321
  "
322
322
  `)
323
323
 
@@ -328,7 +328,6 @@ test('navigation with actions panel', async () => {
328
328
  expect(secondActionSnapshot).toMatchInlineSnapshot(`
329
329
  "
330
330
 
331
-
332
331
  ╭────────────────────────────────────────────────────────────────╮
333
332
  │ │
334
333
  │ Actions esc │
@@ -343,14 +342,15 @@ test('navigation with actions panel', async () => {
343
342
  │ See Console Logs │
344
343
  │ │
345
344
  │ │
345
+ │ │
346
+ │ │
347
+ │ │
346
348
  │ ↵ select ↑↓ navigate │
347
349
  │ │
348
350
  ╰────────────────────────────────────────────────────────────────╯
349
351
 
350
352
 
351
353
 
352
-
353
-
354
354
  "
355
355
  `)
356
356
 
@@ -439,7 +439,6 @@ test('navigation with actions panel', async () => {
439
439
  expect(detailActionsSnapshot).toMatchInlineSnapshot(`
440
440
  "
441
441
 
442
-
443
442
  ╭────────────────────────────────────────────────────────────────╮
444
443
  │ │
445
444
  │ Actions esc │
@@ -454,14 +453,15 @@ test('navigation with actions panel', async () => {
454
453
  │ See Console Logs │
455
454
  │ │
456
455
  │ │
456
+ │ │
457
+ │ │
458
+ │ │
457
459
  │ ↵ select ↑↓ navigate │
458
460
  │ │
459
461
  ╰────────────────────────────────────────────────────────────────╯
460
462
 
461
463
 
462
464
 
463
-
464
-
465
465
  "
466
466
  `)
467
467
 
@@ -525,12 +525,12 @@ test('search functionality in main and detail views', async () => {
525
525
 
526
526
  Navigation Example ─────────────────────────────────────────────
527
527
 
528
- > Main view
528
+ > second
529
+
530
+ ›Second Item Navigate to second detail
531
+
532
+
529
533
 
530
- Items
531
- ›First Item Navigate to first detail
532
- Second Item Navigate to second detail
533
- Third Item Navigate to third detail
534
534
 
535
535
 
536
536
 
@@ -93,7 +93,6 @@ test('selecting command with arguments shows arguments form', async () => {
93
93
 
94
94
  > Search commands...
95
95
 
96
- Google Oauth view
97
96
  usePromise Demo Shows how to use the usePromise h view
98
97
  Show State Shows the current application state in view
99
98
  ›With Arguments Demonstrates command arguments (te view
@@ -101,6 +100,7 @@ test('selecting command with arguments shows arguments form', async () => {
101
100
  Throw Error Command that throws an error at root view
102
101
 
103
102
 
103
+
104
104
  ↵ run command ↑↓ navigate ^k actions
105
105
  "
106
106
  `)
@@ -161,6 +161,7 @@ test('can fill arguments and run command', async () => {
161
161
 
162
162
  // Type in the search query field
163
163
  await session.type('my search term')
164
+ await session.waitIdle()
164
165
 
165
166
  const afterTypingSnapshot = await session.text()
166
167
  expect(afterTypingSnapshot).toMatchInlineSnapshot(`
@@ -183,9 +184,14 @@ test('can fill arguments and run command', async () => {
183
184
  "
184
185
  `)
185
186
 
186
- // Submit via action panel (ctrl+k then enter)
187
+ // Submit via actions dialog.
188
+ // Terminals don't reliably encode Ctrl+Enter, but Ctrl+K is stable.
187
189
  await session.press(['ctrl', 'k'])
188
- await session.waitIdle()
190
+ await waitForTextWithDebug({
191
+ session,
192
+ waitFor: (text) => text.includes('Search actions...') && text.includes('Run Command'),
193
+ timeout: 10000,
194
+ })
189
195
  await session.press('enter')
190
196
 
191
197
  await waitForTextWithDebug({
package/src/index.tsx CHANGED
@@ -9,6 +9,7 @@ export { List, Grid } from 'termcast/src/components/list'
9
9
  export { List as default } from 'termcast/src/components/list'
10
10
  export type {
11
11
  ListProps,
12
+ ListSpacingMode,
12
13
  ItemProps as ListItemProps,
13
14
  SectionProps as ListSectionProps,
14
15
  DetailProps as ListDetailProps,
@@ -143,6 +144,7 @@ export type {
143
144
 
144
145
  // Icons and Images
145
146
  export { Icon, getIconEmoji, getIconShape, IconComponent } from 'termcast/src/components/icon'
147
+ export { Spinner } from 'termcast/src/components/spinner'
146
148
  import {
147
149
  Image as ImageComponent,
148
150
  ImageMask,
package/src/state.tsx CHANGED
@@ -2,6 +2,14 @@ import { create } from 'zustand'
2
2
  import { type ReactNode } from 'react'
3
3
  import type { TextareaRenderable } from '@opentui/core'
4
4
  import type { RaycastPackageJson } from './package-json'
5
+ import type { KeyboardKeyEquivalent, KeyboardKeyModifier } from 'termcast/src/keyboard'
6
+
7
+ // Registered action shortcuts for global keyboard handling
8
+ // Stored by ActionPanel, consumed by List/Detail/Form keyboard handlers
9
+ export interface RegisteredActionShortcut {
10
+ shortcut: { modifiers?: KeyboardKeyModifier[]; key: KeyboardKeyEquivalent }
11
+ execute: () => void
12
+ }
5
13
 
6
14
  // Toast action keyboard shortcuts (ctrl+t for primary, ctrl+g for secondary)
7
15
  export const toastPrimaryActionKey = { ctrl: true, name: 't' } as const
@@ -73,6 +81,9 @@ interface AppState {
73
81
  currentThemeName: string
74
82
  // Active search input ref - used to clear search before exiting on ESC
75
83
  activeSearchInputRef: TextareaRenderable | null
84
+ // Registered action shortcuts for global keyboard handling
85
+ // ActionPanel populates this, List/Detail/Form consume it
86
+ registeredActionShortcuts: RegisteredActionShortcut[]
76
87
  }
77
88
 
78
89
  export const useStore = create<AppState>(() => ({
@@ -101,4 +112,6 @@ export const useStore = create<AppState>(() => ({
101
112
  currentThemeName: 'termcast',
102
113
  // Active search input ref
103
114
  activeSearchInputRef: null,
115
+ // Registered action shortcuts
116
+ registeredActionShortcuts: [],
104
117
  }))