@navios/commander-tui 1.5.1 → 1.6.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@navios/commander-tui",
3
- "version": "1.5.1",
3
+ "version": "1.6.1",
4
4
  "license": "MIT",
5
5
  "author": {
6
6
  "name": "Oleksandr Hanzha",
@@ -15,8 +15,9 @@ export interface AdapterRoot {
15
15
 
16
16
  /**
17
17
  * Unmount and cleanup the root.
18
+ * Can be async to allow waiting for cleanup to complete.
18
19
  */
19
- unmount(): void
20
+ unmount(): void | Promise<void>
20
21
  }
21
22
 
22
23
  /**
@@ -1,2 +1,2 @@
1
- export * from './keyboard_manager.ts'
2
1
  export * from './create_bindings.ts'
2
+ export * from './keyboard_manager.ts'
@@ -10,7 +10,7 @@ export const ScreenOptionsSchema = z.object({
10
10
  /** Whether the screen is hidden */
11
11
  hidden: z.boolean().optional().default(false),
12
12
  /** Whether the screen is static (ignored in auto-close calculations) */
13
- static: z.boolean().optional().default(false),
13
+ static: z.boolean().optional().default(true),
14
14
  })
15
15
 
16
16
  export type ScreenOptions = z.infer<typeof ScreenOptionsSchema>
@@ -42,7 +42,6 @@ export class ScreenLoggerInstance implements LoggerService {
42
42
  typeof options.screen === 'string'
43
43
  ? {
44
44
  name: options.screen,
45
- static: true,
46
45
  }
47
46
  : options.screen,
48
47
  )
@@ -43,6 +43,7 @@ export class ScreenInstance extends EventEmitter<ScreenEventMap> {
43
43
  // Prompt queue system
44
44
  private promptQueue: PendingPrompt[] = []
45
45
  private activePrompt: PendingPrompt | null = null
46
+ private promptVersion: number = 0
46
47
 
47
48
  constructor(id: string, options: ScreenOptions) {
48
49
  super()
@@ -63,6 +64,14 @@ export class ScreenInstance extends EventEmitter<ScreenEventMap> {
63
64
  return this.version
64
65
  }
65
66
 
67
+ private incrementPromptVersion(): void {
68
+ this.promptVersion++
69
+ }
70
+
71
+ getPromptVersion(): number {
72
+ return this.promptVersion
73
+ }
74
+
66
75
  /**
67
76
  * Internal: Set the manager reference
68
77
  */
@@ -343,10 +352,22 @@ export class ScreenInstance extends EventEmitter<ScreenEventMap> {
343
352
  }
344
353
 
345
354
  /**
346
- * Get the currently active prompt (for rendering)
355
+ * Get the currently active prompt (for rendering).
356
+ * Returns a shallow clone to ensure React detects changes when prompt state mutates.
347
357
  */
348
358
  getActivePrompt(): PromptData | null {
349
- return this.activePrompt?.data ?? null
359
+ if (!this.activePrompt?.data) return null
360
+ const prompt = this.activePrompt.data
361
+
362
+ // Clone with proper Set handling for multiChoice
363
+ if (prompt.type === 'multiChoice') {
364
+ return {
365
+ ...prompt,
366
+ selectedIndices: new Set(prompt.selectedIndices),
367
+ }
368
+ }
369
+
370
+ return { ...prompt }
350
371
  }
351
372
 
352
373
  /**
@@ -372,6 +393,7 @@ export class ScreenInstance extends EventEmitter<ScreenEventMap> {
372
393
  } else if (prompt.type === 'confirm') {
373
394
  ;(prompt as ConfirmPromptData).selectedValue = index === 0
374
395
  }
396
+ this.incrementPromptVersion()
375
397
  this.emit('prompt:updated')
376
398
  }
377
399
 
@@ -415,6 +437,7 @@ export class ScreenInstance extends EventEmitter<ScreenEventMap> {
415
437
  const prompt = this.activePrompt.data
416
438
  if (prompt.type === 'confirm') {
417
439
  ;(prompt as ConfirmPromptData).selectedValue = true
440
+ this.incrementPromptVersion()
418
441
  this.emit('prompt:updated')
419
442
  }
420
443
  }
@@ -424,6 +447,7 @@ export class ScreenInstance extends EventEmitter<ScreenEventMap> {
424
447
  const prompt = this.activePrompt.data
425
448
  if (prompt.type === 'confirm') {
426
449
  ;(prompt as ConfirmPromptData).selectedValue = false
450
+ this.incrementPromptVersion()
427
451
  this.emit('prompt:updated')
428
452
  }
429
453
  }
@@ -441,6 +465,7 @@ export class ScreenInstance extends EventEmitter<ScreenEventMap> {
441
465
  } else if (p.selectedIndices.size < p.maxSelect) {
442
466
  p.selectedIndices.add(p.focusedIndex)
443
467
  }
468
+ this.incrementPromptVersion()
444
469
  this.emit('prompt:updated')
445
470
  }
446
471
  }
@@ -457,6 +482,7 @@ export class ScreenInstance extends EventEmitter<ScreenEventMap> {
457
482
  const choice = prompt.choices[prompt.selectedIndex]
458
483
  if (choice?.input) {
459
484
  ;(prompt as ChoicePromptData).inputMode = true
485
+ this.incrementPromptVersion()
460
486
  this.emit('prompt:updated')
461
487
  return true
462
488
  }
@@ -476,6 +502,7 @@ export class ScreenInstance extends EventEmitter<ScreenEventMap> {
476
502
  const prompt = this.activePrompt.data
477
503
  if (prompt.type === 'choice' && prompt.inputMode) {
478
504
  ;(prompt as ChoicePromptData).inputMode = false
505
+ this.incrementPromptVersion()
479
506
  this.emit('prompt:updated')
480
507
  }
481
508
  // Input prompts cannot exit input mode
@@ -505,9 +532,11 @@ export class ScreenInstance extends EventEmitter<ScreenEventMap> {
505
532
  const prompt = this.activePrompt.data
506
533
  if (prompt.type === 'choice' && prompt.inputMode) {
507
534
  ;(prompt as ChoicePromptData).inputValue = value
535
+ this.incrementPromptVersion()
508
536
  this.emit('prompt:updated')
509
537
  } else if (prompt.type === 'input') {
510
538
  ;(prompt as InputPromptData).value = value
539
+ this.incrementPromptVersion()
511
540
  this.emit('prompt:updated')
512
541
  }
513
542
  }
@@ -521,9 +550,11 @@ export class ScreenInstance extends EventEmitter<ScreenEventMap> {
521
550
  const prompt = this.activePrompt.data
522
551
  if (prompt.type === 'choice' && prompt.inputMode) {
523
552
  ;(prompt as ChoicePromptData).inputValue += char
553
+ this.incrementPromptVersion()
524
554
  this.emit('prompt:updated')
525
555
  } else if (prompt.type === 'input') {
526
556
  ;(prompt as InputPromptData).value += char
557
+ this.incrementPromptVersion()
527
558
  this.emit('prompt:updated')
528
559
  }
529
560
  }
@@ -537,9 +568,11 @@ export class ScreenInstance extends EventEmitter<ScreenEventMap> {
537
568
  const prompt = this.activePrompt.data
538
569
  if (prompt.type === 'choice' && prompt.inputMode) {
539
570
  ;(prompt as ChoicePromptData).inputValue = prompt.inputValue.slice(0, -1)
571
+ this.incrementPromptVersion()
540
572
  this.emit('prompt:updated')
541
573
  } else if (prompt.type === 'input') {
542
574
  ;(prompt as InputPromptData).value = prompt.value.slice(0, -1)
575
+ this.incrementPromptVersion()
543
576
  this.emit('prompt:updated')
544
577
  }
545
578
  }
@@ -606,6 +639,7 @@ export class ScreenInstance extends EventEmitter<ScreenEventMap> {
606
639
  this.activePrompt = null
607
640
  this.activateNextPrompt()
608
641
  this.incrementVersion()
642
+ this.incrementPromptVersion()
609
643
  this.emit('prompt:resolved')
610
644
  }
611
645
 
@@ -618,6 +652,7 @@ export class ScreenInstance extends EventEmitter<ScreenEventMap> {
618
652
  // Notify manager to focus this screen
619
653
  this.manager?.onScreenPromptActivated(this)
620
654
  this.incrementVersion()
655
+ this.incrementPromptVersion()
621
656
  this.emit('prompt:activated')
622
657
  }
623
658
  }
@@ -74,6 +74,8 @@ export class ScreenManagerInstance
74
74
  // Set first visible screen as active by default
75
75
  if (!this.activeScreenId && !screen.isHidden()) {
76
76
  this.activeScreenId = id
77
+ // Emit activeScreen:changed so subscribers know about the new active screen
78
+ this.emit('activeScreen:changed', id)
77
79
  }
78
80
 
79
81
  this.checkAutoClose()
@@ -125,6 +127,12 @@ export class ScreenManagerInstance
125
127
  async bind(options?: BindOptions): Promise<void> {
126
128
  if (this.mode !== RenderMode.UNBOUND) return
127
129
 
130
+ // In non-interactive environments (no TTY), stay in UNBOUND mode
131
+ // This allows prompts to return defaults and screens to print on completion
132
+ if (!process.stdout.isTTY) {
133
+ return
134
+ }
135
+
128
136
  this.bindOptions = options ?? {}
129
137
 
130
138
  // Resolve theme from options
@@ -266,15 +274,15 @@ export class ScreenManagerInstance
266
274
  return this.globalLogLevels ? Array.from(this.globalLogLevels) : null
267
275
  }
268
276
 
269
- onServiceDestroy(): void {
270
- this.unbind()
277
+ async onServiceDestroy(): Promise<void> {
278
+ await this.unbind()
271
279
  }
272
280
 
273
281
  /**
274
282
  * Stop TUI rendering and cleanup
275
283
  * Flushes screens to stdout/stderr based on mode
276
284
  */
277
- unbind(): void {
285
+ async unbind(): Promise<void> {
278
286
  if (this.mode === RenderMode.UNBOUND) {
279
287
  // Even in unbound mode, flush any remaining screens on destroy
280
288
  this.flushRemainingScreens()
@@ -292,7 +300,7 @@ export class ScreenManagerInstance
292
300
  // Cleanup TUI if active
293
301
  if (previousMode === RenderMode.TUI_ACTIVE) {
294
302
  if (this.root) {
295
- this.root.unmount()
303
+ await this.root.unmount()
296
304
  }
297
305
 
298
306
  // Explicitly disable mouse tracking before destroy to prevent escape sequence leakage
@@ -504,7 +512,7 @@ export class ScreenManagerInstance
504
512
  // Start auto-close timer (either all non-static screens succeeded, or only static screens exist)
505
513
  const delay = typeof autoClose === 'number' ? autoClose : 5000
506
514
  this.autoCloseTimer = setTimeout(() => {
507
- this.unbind()
515
+ void this.unbind()
508
516
  }, delay)
509
517
  }
510
518