@nan0web/ui-cli 1.1.0 → 2.0.0

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/README.md +114 -207
  2. package/package.json +22 -12
  3. package/src/CLI.js +22 -29
  4. package/src/CLiMessage.js +2 -3
  5. package/src/Command.js +26 -24
  6. package/src/CommandError.js +3 -5
  7. package/src/CommandHelp.js +40 -36
  8. package/src/CommandMessage.js +56 -40
  9. package/src/CommandParser.js +27 -25
  10. package/src/InputAdapter.js +630 -90
  11. package/src/OutputAdapter.js +7 -8
  12. package/src/README.md.js +190 -316
  13. package/src/components/Alert.js +3 -6
  14. package/src/components/prompt/Autocomplete.js +12 -0
  15. package/src/components/prompt/Confirm.js +29 -0
  16. package/src/components/prompt/DateTime.js +26 -0
  17. package/src/components/prompt/Input.js +15 -0
  18. package/src/components/prompt/Mask.js +12 -0
  19. package/src/components/prompt/Multiselect.js +26 -0
  20. package/src/components/prompt/Next.js +8 -0
  21. package/src/components/prompt/Password.js +13 -0
  22. package/src/components/prompt/Pause.js +9 -0
  23. package/src/components/prompt/ProgressBar.js +16 -0
  24. package/src/components/prompt/Select.js +29 -0
  25. package/src/components/prompt/Slider.js +16 -0
  26. package/src/components/prompt/Spinner.js +29 -0
  27. package/src/components/prompt/Toggle.js +13 -0
  28. package/src/components/prompt/Tree.js +17 -0
  29. package/src/components/view/Alert.js +78 -0
  30. package/src/components/view/Badge.js +11 -0
  31. package/src/components/view/Nav.js +23 -0
  32. package/src/components/view/Table.js +12 -0
  33. package/src/components/view/Toast.js +9 -0
  34. package/src/core/Component.js +79 -0
  35. package/src/core/PropValidation.js +138 -0
  36. package/src/core/render.js +37 -0
  37. package/src/index.js +80 -41
  38. package/src/test/PlaygroundTest.js +37 -25
  39. package/src/test/index.js +2 -4
  40. package/src/ui/alert.js +58 -0
  41. package/src/ui/autocomplete.js +86 -0
  42. package/src/ui/badge.js +35 -0
  43. package/src/ui/confirm.js +49 -0
  44. package/src/ui/date-time.js +45 -0
  45. package/src/ui/form.js +120 -55
  46. package/src/ui/index.js +18 -4
  47. package/src/ui/input.js +79 -152
  48. package/src/ui/mask.js +132 -0
  49. package/src/ui/multiselect.js +59 -0
  50. package/src/ui/nav.js +74 -0
  51. package/src/ui/next.js +18 -13
  52. package/src/ui/progress.js +88 -0
  53. package/src/ui/select.js +49 -72
  54. package/src/ui/slider.js +154 -0
  55. package/src/ui/spinner.js +65 -0
  56. package/src/ui/table.js +163 -0
  57. package/src/ui/toast.js +34 -0
  58. package/src/ui/toggle.js +34 -0
  59. package/src/ui/tree.js +393 -0
  60. package/src/utils/parse.js +1 -1
  61. package/types/CLI.d.ts +5 -5
  62. package/types/CLiMessage.d.ts +1 -1
  63. package/types/Command.d.ts +2 -2
  64. package/types/CommandHelp.d.ts +3 -3
  65. package/types/CommandMessage.d.ts +8 -8
  66. package/types/CommandParser.d.ts +3 -3
  67. package/types/InputAdapter.d.ts +149 -15
  68. package/types/OutputAdapter.d.ts +1 -1
  69. package/types/README.md.d.ts +1 -1
  70. package/types/UiMessage.d.ts +31 -29
  71. package/types/components/prompt/Autocomplete.d.ts +6 -0
  72. package/types/components/prompt/Confirm.d.ts +6 -0
  73. package/types/components/prompt/DateTime.d.ts +6 -0
  74. package/types/components/prompt/Input.d.ts +6 -0
  75. package/types/components/prompt/Mask.d.ts +6 -0
  76. package/types/components/prompt/Multiselect.d.ts +6 -0
  77. package/types/components/prompt/Next.d.ts +6 -0
  78. package/types/components/prompt/Password.d.ts +6 -0
  79. package/types/components/prompt/Pause.d.ts +6 -0
  80. package/types/components/prompt/ProgressBar.d.ts +12 -0
  81. package/types/components/prompt/Select.d.ts +18 -0
  82. package/types/components/prompt/Slider.d.ts +6 -0
  83. package/types/components/prompt/Spinner.d.ts +21 -0
  84. package/types/components/prompt/Toggle.d.ts +6 -0
  85. package/types/components/prompt/Tree.d.ts +6 -0
  86. package/types/components/view/Alert.d.ts +21 -0
  87. package/types/components/view/Badge.d.ts +5 -0
  88. package/types/components/view/Nav.d.ts +15 -0
  89. package/types/components/view/Table.d.ts +10 -0
  90. package/types/components/view/Toast.d.ts +5 -0
  91. package/types/core/Component.d.ts +34 -0
  92. package/types/core/PropValidation.d.ts +48 -0
  93. package/types/core/render.d.ts +6 -0
  94. package/types/index.d.ts +47 -15
  95. package/types/test/PlaygroundTest.d.ts +12 -8
  96. package/types/test/index.d.ts +1 -1
  97. package/types/ui/alert.d.ts +14 -0
  98. package/types/ui/autocomplete.d.ts +20 -0
  99. package/types/ui/badge.d.ts +8 -0
  100. package/types/ui/confirm.d.ts +21 -0
  101. package/types/ui/date-time.d.ts +19 -0
  102. package/types/ui/form.d.ts +43 -12
  103. package/types/ui/index.d.ts +17 -2
  104. package/types/ui/input.d.ts +31 -74
  105. package/types/ui/mask.d.ts +29 -0
  106. package/types/ui/multiselect.d.ts +25 -0
  107. package/types/ui/nav.d.ts +27 -0
  108. package/types/ui/progress.d.ts +43 -0
  109. package/types/ui/select.d.ts +25 -64
  110. package/types/ui/slider.d.ts +23 -0
  111. package/types/ui/spinner.d.ts +28 -0
  112. package/types/ui/table.d.ts +28 -0
  113. package/types/ui/toast.d.ts +8 -0
  114. package/types/ui/toggle.d.ts +17 -0
  115. package/types/ui/tree.d.ts +48 -0
package/src/index.js CHANGED
@@ -1,53 +1,92 @@
1
- import { CancelError } from "@nan0web/ui/core"
2
-
3
- import CLiInputAdapter from "./InputAdapter.js"
4
- import OutputAdapter from "./OutputAdapter.js"
5
- import CLI from "./CLI.js"
6
- import Command from "./Command.js"
7
- import CommandError from "./CommandError.js"
8
- import CommandMessage from "./CommandMessage.js"
9
- import CommandParser from "./CommandParser.js"
10
- import CommandHelp from "./CommandHelp.js"
11
- export { str2argv } from "./utils/parse.js"
1
+ import CLiInputAdapter from './InputAdapter.js'
2
+ import OutputAdapter from './OutputAdapter.js'
3
+ import { CancelError } from '@nan0web/ui/core'
4
+ import CLI from './CLI.js'
5
+ import CommandParser from './CommandParser.js'
12
6
 
13
- export {
14
- select,
15
- next,
16
- pause,
17
- createInput,
18
- ask,
19
- Input,
20
- } from "./ui/index.js"
7
+ // V2 Component Exports
8
+ import { render } from './core/render.js'
9
+
10
+ // Views
11
+ import { Alert } from './components/view/Alert.js'
12
+ import { Badge } from './components/view/Badge.js'
13
+ import { Table } from './components/view/Table.js'
14
+ import { Breadcrumbs, Tabs, Steps } from './components/view/Nav.js'
15
+ import { Toast } from './components/view/Toast.js'
21
16
 
22
- /** @typedef {import("./CommandHelp.js").CommandHelpField} CommandHelpField */
17
+ // Prompts
18
+ import { Select } from './components/prompt/Select.js'
19
+ import { Input } from './components/prompt/Input.js'
20
+ import { Password } from './components/prompt/Password.js'
21
+ import { Confirm } from './components/prompt/Confirm.js'
22
+ import { Multiselect } from './components/prompt/Multiselect.js'
23
+ import { Mask } from './components/prompt/Mask.js'
24
+ import { Autocomplete } from './components/prompt/Autocomplete.js'
25
+ import { Slider } from './components/prompt/Slider.js'
26
+ import { Toggle } from './components/prompt/Toggle.js'
27
+ import { DateTime } from './components/prompt/DateTime.js'
28
+ import { Next } from './components/prompt/Next.js'
29
+ import { Pause } from './components/prompt/Pause.js'
30
+ import { Tree } from './components/prompt/Tree.js'
31
+ import { Spinner } from './components/prompt/Spinner.js'
32
+ import { ProgressBar } from './components/prompt/ProgressBar.js'
23
33
 
34
+ // Legacy utils still needed for internal logic or compat
35
+ export { createInput, ask, text } from './ui/input.js'
36
+ export { select } from './ui/select.js'
37
+ export { confirm } from './ui/confirm.js'
38
+ export { next } from './ui/next.js'
39
+ export { multiselect } from './ui/multiselect.js'
40
+ export { mask } from './ui/mask.js'
41
+ export { table } from './ui/table.js'
42
+ export { autocomplete } from './ui/autocomplete.js'
43
+ export { pause } from './ui/next.js'
44
+ export { alert } from './ui/alert.js'
45
+ export { badge } from './ui/badge.js'
46
+ export { toast } from './ui/toast.js'
47
+ export { spinner } from './ui/spinner.js'
48
+ export { progress } from './ui/progress.js'
49
+ export { breadcrumbs, tabs, steps } from './ui/nav.js'
50
+ export { tree } from './ui/tree.js'
51
+ export { datetime } from './ui/date-time.js'
52
+ export { toggle } from './ui/toggle.js'
53
+ export { slider } from './ui/slider.js'
54
+
55
+ // Public V2 API
24
56
  export {
25
- CLI,
57
+ // Core
58
+ render,
26
59
  CLiInputAdapter,
27
60
  CancelError,
28
- OutputAdapter,
29
61
 
30
- /** @deprecated */
31
- Command,
32
- /** @deprecated */
33
- CommandError,
34
- /** @deprecated */
35
- CommandMessage,
62
+ // Components
63
+ Alert,
64
+ Badge,
65
+ Table,
66
+ Breadcrumbs,
67
+ Tabs,
68
+ Steps,
69
+ Toast,
70
+ Select,
71
+ Input,
72
+ Password,
73
+ Confirm,
74
+ Multiselect,
75
+ Mask,
76
+ Autocomplete,
77
+ Slider,
78
+ Toggle,
79
+ DateTime,
80
+ Next,
81
+ Pause,
82
+ Tree,
83
+ Spinner,
84
+ ProgressBar,
36
85
 
86
+ // Tools
87
+ CLI,
37
88
  CommandParser,
38
- CommandHelp,
89
+ OutputAdapter,
39
90
  }
40
91
 
41
- /* New public API */
42
- export { generateForm } from "./ui/form.js"
43
-
44
- export const renderers = new Map([
45
- [
46
- "UIProcess",
47
- data => {
48
- return `${data.title || "Process"}: ${data.status || "running"}`
49
- },
50
- ],
51
- ])
52
-
53
92
  export default CLiInputAdapter
@@ -8,13 +8,13 @@
8
8
  */
9
9
 
10
10
  /* eslint-disable no-use-before-define */
11
- import { spawn } from "node:child_process"
12
- import event, { EventContext } from "@nan0web/event"
11
+ import { spawn } from 'node:child_process'
12
+ import event, { EventContext } from '@nan0web/event'
13
13
 
14
14
  /**
15
15
  * @typedef {object} PlaygroundTestConfig
16
16
  * @property {NodeJS.ProcessEnv} env Environment variables for the child process.
17
- * @property {{ includeDebugger?: boolean }} [config={}] Configuration options.
17
+ * @property {{ includeDebugger?: boolean, includeEmptyLines?: boolean, feedStdin?: boolean }} [config={}] Configuration options.
18
18
  */
19
19
 
20
20
  /**
@@ -33,7 +33,7 @@ export default class PlaygroundTest {
33
33
  #bus
34
34
  /**
35
35
  * @param {NodeJS.ProcessEnv} env Environment variables for the child process.
36
- * @param {{ includeDebugger?: boolean, includeEmptyLines?: boolean }} [config={}] Configuration options.
36
+ * @param {{ includeDebugger?: boolean, includeEmptyLines?: boolean, feedStdin?: boolean }} [config={}] Configuration options.
37
37
  */
38
38
  constructor(env, config = {}) {
39
39
  this.env = env
@@ -41,6 +41,7 @@ export default class PlaygroundTest {
41
41
  /** @type {boolean} Include debugger lines in output (default: false). */
42
42
  this.includeDebugger = config.includeDebugger ?? false
43
43
  this.incldeEmptyLines = config.includeEmptyLines ?? false
44
+ this.feedStdin = config.feedStdin ?? true
44
45
  }
45
46
 
46
47
  /**
@@ -66,18 +67,18 @@ export default class PlaygroundTest {
66
67
  */
67
68
  filterDebugger(str) {
68
69
  if (this.includeDebugger) return str
69
- const words = ["debugger", "https://nodejs.org/en/docs/inspector"]
70
+ const words = ['debugger', 'https://nodejs.org/en/docs/inspector']
70
71
  return str
71
- .split("\n")
72
- .filter(s => !words.some(w => s.toLowerCase().includes(w)))
73
- .join("\n")
72
+ .split('\n')
73
+ .filter((s) => !words.some((w) => s.toLowerCase().includes(w)))
74
+ .join('\n')
74
75
  }
75
76
  /**
76
77
  * Slice lines from stdout or stderr.
77
78
  */
78
79
  slice(target, start, end) {
79
- const txt = (this.recentResult?.[target] ?? "")
80
- return txt.split("\n").slice(start, end)
80
+ const txt = this.recentResult?.[target] ?? ''
81
+ return txt.split('\n').slice(start, end)
81
82
  }
82
83
  /**
83
84
  * Write the answer sequence to the child process **asynchronously**,
@@ -89,18 +90,20 @@ export default class PlaygroundTest {
89
90
  const raw = this.env.PLAY_DEMO_SEQUENCE
90
91
  if (!raw) return
91
92
 
92
- const sequence = raw.split(",").map(s => s.trim()).filter(Boolean)
93
+ const sequence = raw.split(',').map((s) => s.trim())
94
+ // Allow empty strings to signify "Enter" (default)
93
95
  if (sequence.length === 0) return
94
96
 
95
- if (child.stdin) child.stdin.setDefaultEncoding("utf-8")
97
+ if (child.stdin) child.stdin.setDefaultEncoding('utf-8')
96
98
 
97
99
  const writeNext = (idx) => {
98
100
  if (idx >= sequence.length) {
99
101
  try {
100
102
  child.stdin?.end()
101
- } catch (_) { }
103
+ } catch (_) {}
102
104
  return
103
105
  }
106
+ // Use 200ms delay for rock-solid stability across all platforms.
104
107
  setTimeout(() => {
105
108
  try {
106
109
  if (!child.killed && child.stdin?.writable) {
@@ -108,7 +111,7 @@ export default class PlaygroundTest {
108
111
  writeNext(idx + 1)
109
112
  }
110
113
  } catch (_) {
111
- // Silently swallow EPIPE and stop feeding.
114
+ // Silently swallow EPIPE
112
115
  }
113
116
  }, 200)
114
117
  }
@@ -119,33 +122,42 @@ export default class PlaygroundTest {
119
122
  *
120
123
  * @param {string[]} [args=["play/main.js"]] Arguments passed to the node process.
121
124
  */
122
- async run(args = ["play/main.js"]) {
123
- const child = spawn("node", args, {
125
+ async run(args = ['play/main.js']) {
126
+ // Optimization: if we have --demo or --lang in env equivalent, let's use them directly
127
+ // But for now, we follow the args passed.
128
+ const child = spawn(process.execPath, args, {
124
129
  env: this.env,
125
- stdio: ["pipe", "pipe", "pipe"],
130
+ stdio: ['pipe', 'pipe', 'pipe'],
126
131
  })
127
132
 
128
- this.#feedSequence(child)
133
+ if (this.feedStdin) {
134
+ this.#feedSequence(child)
135
+ }
129
136
 
130
- let stdout = ""
137
+ let stdout = ''
131
138
  for await (const chunk of child.stdout) {
132
139
  stdout += chunk.toString()
133
- this.emit("stdout", { chunk })
140
+ await this.emit('stdout', { chunk })
134
141
  }
135
142
 
136
- let stderr = ""
143
+ let stderr = ''
137
144
  for await (const chunk of child.stderr) {
138
145
  const clean = this.filterDebugger(chunk.toString())
139
146
  stderr += clean
140
- this.emit("stderr", { chunk, clean })
147
+ this.emit('stderr', { chunk, clean })
141
148
  }
142
149
 
143
- const exitCode = await new Promise(resolve => child.on("close", resolve))
150
+ const exitCode = await new Promise((resolve) => child.on('close', resolve))
144
151
 
145
152
  // Trim leading whitespace from every line – the test suite expects raw
146
153
  // output without logger prefixes or indentation.
147
- const normalize = txt => this.incldeEmptyLines ? txt
148
- : txt.split("\n").filter(row => row.trim() !== "").join("\n")
154
+ const normalize = (txt) =>
155
+ this.incldeEmptyLines
156
+ ? txt
157
+ : txt
158
+ .split('\n')
159
+ .filter((row) => row.trim() !== '')
160
+ .join('\n')
149
161
 
150
162
  this.recentResult = {
151
163
  stdout: normalize(stdout),
package/src/test/index.js CHANGED
@@ -1,7 +1,5 @@
1
- import PlaygroundTest from "./PlaygroundTest.js"
1
+ import PlaygroundTest from './PlaygroundTest.js'
2
2
 
3
- export {
4
- PlaygroundTest,
5
- }
3
+ export { PlaygroundTest }
6
4
 
7
5
  export default PlaygroundTest
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Alert component – displays a prominent message box.
3
+ *
4
+ * @module ui/alert
5
+ */
6
+
7
+ import Logger from '@nan0web/log'
8
+ import { beep } from './input.js'
9
+
10
+ /**
11
+ * Renders an alert box and optionally plays a sound.
12
+ *
13
+ * @param {string} message - Message content.
14
+ * @param {'info'|'success'|'warning'|'error'} [variant='info']
15
+ * @param {Object} [options]
16
+ * @param {string} [options.title] - Optional title.
17
+ * @param {boolean} [options.sound=false] - Play beep sound.
18
+ * @returns {string} Styled message block.
19
+ */
20
+ export function alert(message, variant = 'info', options = {}) {
21
+ const { title, sound = variant === 'error' || variant === 'warning' } = options
22
+
23
+ if (sound) beep()
24
+
25
+ const colors = {
26
+ info: Logger.CYAN,
27
+ success: Logger.GREEN,
28
+ warning: Logger.YELLOW,
29
+ error: Logger.RED,
30
+ }
31
+
32
+ const color = colors[variant] || Logger.WHITE
33
+ const icon =
34
+ {
35
+ info: 'ℹ',
36
+ success: '✔',
37
+ warning: '⚠',
38
+ error: '✖',
39
+ }[variant] || '•'
40
+
41
+ const lines = String(message || '').split('\n')
42
+ const maxLineLen = Math.max(title ? String(title).length + 4 : 0, ...lines.map((l) => l.length))
43
+ const len = Math.max(60, maxLineLen + 4)
44
+ const border = Logger.style('━'.repeat(len), { color })
45
+
46
+ let out = ''
47
+ out += `\n${border}\n`
48
+ if (title) {
49
+ out += Logger.style(` ${icon} ${title} `, { color }) + '\n'
50
+ out += Logger.style('─'.repeat(len) + '\n', { color: Logger.DIM })
51
+ }
52
+ lines.forEach((line) => {
53
+ out += ' ' + Logger.style(line.trim(), { color }) + '\n'
54
+ })
55
+ out += `${border}\n`
56
+
57
+ return out
58
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Autocomplete module - provides a searchable selection list.
3
+ *
4
+ * @module ui/autocomplete
5
+ */
6
+
7
+ import prompts from 'prompts'
8
+ import { CancelError } from '@nan0web/ui/core'
9
+ import Logger from '@nan0web/log'
10
+
11
+ /**
12
+ * Highlights the matching part of the text based on the query.
13
+ * @param {string} text
14
+ * @param {string} query
15
+ * @returns {string}
16
+ */
17
+ function highlight(text, query) {
18
+ if (!query) return text
19
+ const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
20
+ const regex = new RegExp(`(${escapedQuery})`, 'gi')
21
+ return text.replace(regex, `${Logger.MAGENTA}$1${Logger.RESET}`)
22
+ }
23
+
24
+ /**
25
+ * Renders a searchable selection list.
26
+ *
27
+ * @param {Object} input
28
+ * @param {string} [input.message] - Prompt message.
29
+ * @param {string} [input.title] - Alternative prompt title.
30
+ * @param {Array|Function} input.options - List of options or async function to fetch them.
31
+ * @param {number} [input.limit=10] - Max visible items.
32
+ * @returns {Promise<{index:number,value:any,cancelled:boolean}>}
33
+ */
34
+ export async function autocomplete(input) {
35
+ const { message, title, options: initOptions, limit = 30 } = input
36
+
37
+ let choices = []
38
+ const fetch = async (query = '') => {
39
+ let currentOptions = typeof initOptions === 'function' ? await initOptions(query) : initOptions
40
+
41
+ // Normalize Map or Array
42
+ if (currentOptions instanceof Map) {
43
+ currentOptions = Array.from(currentOptions.entries()).map(([value, label]) => ({
44
+ label,
45
+ value,
46
+ }))
47
+ }
48
+
49
+ const filtered = currentOptions.filter((el) => {
50
+ const label = typeof el === 'string' ? el : el.title || el.label || ''
51
+ return label.toLowerCase().includes(query.toLowerCase())
52
+ })
53
+
54
+ return filtered.map((el) => {
55
+ const label = typeof el === 'string' ? el : el.title || el.label
56
+ const value = typeof el === 'string' ? el : el.value
57
+ return {
58
+ title: highlight(label, query),
59
+ value: value,
60
+ }
61
+ })
62
+ }
63
+
64
+ choices = await fetch('')
65
+
66
+ const response = await prompts(
67
+ {
68
+ type: 'autocomplete',
69
+ name: 'value',
70
+ message: message || title,
71
+ limit,
72
+ choices: choices,
73
+ suggest: (input, choices) => fetch(input),
74
+ },
75
+ {
76
+ onCancel: () => {
77
+ throw new CancelError()
78
+ },
79
+ }
80
+ )
81
+
82
+ const finalChoices = typeof initOptions === 'function' ? await fetch('') : choices
83
+ const index = finalChoices.findIndex((c) => c.value === response.value)
84
+
85
+ return { index, value: response.value, cancelled: false }
86
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Badge component – displays a small status label.
3
+ *
4
+ * @module ui/badge
5
+ */
6
+
7
+ import Logger from '@nan0web/log'
8
+
9
+ /**
10
+ * Renders a status badge.
11
+ *
12
+ * @param {string} label - Text to display.
13
+ * @param {'info'|'success'|'warning'|'error'|'neutral'} [variant='neutral']
14
+ * @returns {string} Styled string.
15
+ */
16
+ export function badge(label, variant = 'neutral') {
17
+ const colors = {
18
+ info: { bg: 'BLUE', fg: 'WHITE' }, // Use keys compatible with Logger lookup if possible, or Logger constants
19
+ success: { bg: 'GREEN', fg: 'BLACK' },
20
+ warning: { bg: 'YELLOW', fg: 'BLACK' },
21
+ error: { bg: 'RED', fg: 'WHITE' },
22
+ neutral: { bg: 'WHITE', fg: 'BLACK' },
23
+ }
24
+
25
+ // Logger.style expects bgColor name or code.
26
+ // Logger.BG_BLUE is a code.
27
+ // But Logger.style implementation tries `Logger['BG_' + bgColor.toUpperCase()]`.
28
+ // So if I pass 'BLUE', it looks for BG_BLUE.
29
+ // If I pass Logger.BG_BLUE (the code), it uses it as raw.
30
+
31
+ // Let's use string keys 'BLUE', etc.
32
+
33
+ const style = colors[variant] || colors.neutral
34
+ return Logger.style(` ${label} `, { color: Logger[style.fg] || style.fg, bgColor: style.bg })
35
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Confirm module – simple Yes/No prompt.
3
+ * @module ui/confirm
4
+ */
5
+ import prompts from 'prompts'
6
+ import { CancelError } from '@nan0web/ui/core'
7
+ import { validateString, validateBoolean, validateFunction } from '../core/PropValidation.js'
8
+
9
+ /**
10
+ * @param {Object} config
11
+ * @param {string} config.message
12
+ * @param {boolean} [config.initial=true] - Default value (true=Yes, false=No)
13
+ * @param {Function} [config.format] - Output value formatter
14
+ * @param {string} [config.active] - Label for "Yes" state
15
+ * @param {string} [config.inactive] - Label for "No" state
16
+ * @param {Function} [config.t] - Optional translation function.
17
+ * @returns {Promise<{value:boolean, cancelled:boolean}>}
18
+ */
19
+ export async function confirm(config) {
20
+ // Validation
21
+ validateString(config.message, 'message', 'Confirm', true)
22
+ validateBoolean(config.initial, 'initial', 'Confirm')
23
+ validateFunction(config.format, 'format', 'Confirm')
24
+ validateString(config.active, 'active', 'Confirm')
25
+ validateString(config.inactive, 'inactive', 'Confirm')
26
+ validateFunction(config.t, 't', 'Confirm')
27
+
28
+ const { message, initial = true, format, t = (k) => k } = config
29
+ const active = config.active || t('yes')
30
+ const inactive = config.inactive || t('no')
31
+
32
+ const response = await prompts(
33
+ {
34
+ type: 'toggle',
35
+ name: 'value',
36
+ message,
37
+ initial,
38
+ format,
39
+ active,
40
+ inactive,
41
+ },
42
+ {
43
+ onCancel: () => {
44
+ throw new CancelError()
45
+ },
46
+ }
47
+ )
48
+ return { value: response.value, cancelled: false }
49
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Date/Time Picker module.
3
+ * @module ui/date-time
4
+ */
5
+ import prompts from 'prompts'
6
+ import { CancelError } from '@nan0web/ui/core'
7
+
8
+ /**
9
+ * Prompt user for a date or time.
10
+ *
11
+ * @param {Object} config
12
+ * @param {string} config.message
13
+ * @param {Date} [config.initial]
14
+ * @param {string} [config.mask] - Optional mask (e.g. 'YYYY-MM-DD HH:mm')
15
+ * @param {Function} [config.t] - Optional translation function.
16
+ * @returns {Promise<{value:Date, cancelled:boolean}>}
17
+ */
18
+ export async function datetime(config) {
19
+ const { message, initial: rawInitial = new Date(), mask, t = (k) => k } = config
20
+ const initial = rawInitial instanceof Date ? rawInitial : new Date(rawInitial)
21
+
22
+ try {
23
+ const result = await prompts(
24
+ {
25
+ type: 'date',
26
+ name: 'value',
27
+ message: t(message),
28
+ initial,
29
+ mask: mask || undefined,
30
+ },
31
+ {
32
+ onCancel: () => {
33
+ throw new CancelError()
34
+ },
35
+ }
36
+ )
37
+
38
+ return { value: result.value, cancelled: false }
39
+ } catch (err) {
40
+ if (err instanceof CancelError) {
41
+ return { value: initial, cancelled: true }
42
+ }
43
+ throw err
44
+ }
45
+ }