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
@@ -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/logger.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'fs'
2
2
  import * as path from 'path'
3
+ import util from 'node:util'
3
4
  import { useEffect } from 'react'
4
5
 
5
6
  const LOG_FILE = path.join(process.cwd(), 'app.log')
@@ -16,7 +17,7 @@ function serialize(msg: any): string {
16
17
  if (typeof msg === 'string') {
17
18
  return msg
18
19
  }
19
- return Bun.inspect(msg, { depth: 3 })
20
+ return util.inspect(msg, { depth: 3 })
20
21
  }
21
22
 
22
23
  export const logger = {
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
  }))
@@ -1,3 +1,4 @@
1
+ import childProcess from 'node:child_process'
1
2
  import { logger } from './logger'
2
3
 
3
4
  export async function runSwiftFunction(
@@ -9,14 +10,27 @@ export async function runSwiftFunction(
9
10
 
10
11
  logger.log(`Swift: calling ${functionName} with args:`, jsonArgs)
11
12
 
12
- const proc = Bun.spawn([binaryPath, functionName, ...jsonArgs], {
13
- stdout: 'pipe',
14
- stderr: 'inherit',
13
+ const proc = childProcess.spawn(binaryPath, [functionName, ...jsonArgs], {
14
+ stdio: ['ignore', 'pipe', 'inherit'],
15
15
  })
16
16
 
17
17
  const [stdout, exitCode] = await Promise.all([
18
- new Response(proc.stdout).text(),
19
- proc.exited,
18
+ new Promise<string>((resolve, reject) => {
19
+ const chunks: Buffer[] = []
20
+ proc.stdout!.on('data', (chunk) => { chunks.push(chunk) })
21
+ proc.stdout!.on('error', reject)
22
+ proc.stdout!.on('end', () => { resolve(Buffer.concat(chunks).toString()) })
23
+ }),
24
+ new Promise<number | null>((resolve, reject) => {
25
+ proc.on('error', reject)
26
+ proc.on('close', (code, signal) => {
27
+ if (signal) {
28
+ reject(new Error(`Swift function "${functionName}" was killed by signal ${signal}`))
29
+ } else {
30
+ resolve(code)
31
+ }
32
+ })
33
+ }),
20
34
  ])
21
35
 
22
36
  if (exitCode !== 0) {
@@ -6,7 +6,7 @@ import {
6
6
  executeSQL,
7
7
  runAppleScript,
8
8
  } from './utils'
9
- import { Database } from 'bun:sqlite'
9
+ import { Database } from './apis/sqlite'
10
10
  import * as fs from 'fs'
11
11
  import * as os from 'os'
12
12
  import * as path from 'path'
package/src/utils.tsx CHANGED
@@ -812,7 +812,7 @@ export async function executeSQL<T = unknown>(
812
812
  databasePath: string,
813
813
  query: string,
814
814
  ): Promise<T[]> {
815
- const { Database } = await import('bun:sqlite')
815
+ const { Database } = await import('./apis/sqlite')
816
816
 
817
817
  if (!fs.existsSync(databasePath)) {
818
818
  throw new Error(`Database file not found: ${databasePath}`)