@nan0web/ui 1.0.4 → 1.3.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.
- package/README.md +19 -14
- package/package.json +6 -4
- package/src/App/Command/DepsCommand.js +25 -0
- package/src/App/Core/CoreApp.js +18 -17
- package/src/App/Core/UI.js +12 -19
- package/src/App/Core/Widget.js +6 -10
- package/src/App/Core/index.js +3 -3
- package/src/App/Scenario.js +4 -4
- package/src/App/User/Command/Message.js +2 -29
- package/src/App/User/Command/index.js +3 -4
- package/src/App/User/UserApp.js +30 -23
- package/src/App/User/UserUI.js +2 -2
- package/src/App/User/index.js +2 -2
- package/src/App/index.js +5 -10
- package/src/Component/Process/Input.js +10 -17
- package/src/Component/Process/Process.js +3 -5
- package/src/Component/Process/index.js +2 -2
- package/src/Component/SortableList/SortableList.js +100 -0
- package/src/Component/SortableList/index.js +3 -0
- package/src/Component/Welcome/Input.js +2 -4
- package/src/Component/Welcome/Welcome.js +5 -9
- package/src/Component/Welcome/index.js +2 -2
- package/src/Component/index.js +5 -3
- package/src/Frame/Frame.js +163 -146
- package/src/Frame/Props.js +20 -20
- package/src/Locale.js +17 -18
- package/src/Model/User/User.js +3 -6
- package/src/Model/index.js +1 -1
- package/src/README.md.js +84 -94
- package/src/StdIn.js +8 -12
- package/src/StdOut.js +23 -27
- package/src/View/RenderOptions.js +1 -1
- package/src/View/View.js +42 -38
- package/src/core/Error/CancelError.js +2 -2
- package/src/core/Error/index.js +3 -5
- package/src/core/Flow.js +347 -0
- package/src/core/Form/Form.js +35 -33
- package/src/core/Form/Input.js +29 -14
- package/src/core/Form/Message.js +3 -6
- package/src/core/Form/index.js +4 -8
- package/src/core/InputAdapter.js +4 -6
- package/src/core/Message/Message.js +9 -12
- package/src/core/Message/OutputMessage.js +19 -17
- package/src/core/Message/index.js +2 -2
- package/src/core/OutputAdapter.js +12 -10
- package/src/core/Stream.js +4 -3
- package/src/core/StreamEntry.js +2 -2
- package/src/core/UiAdapter.js +57 -29
- package/src/core/index.js +38 -9
- package/src/functions.js +8 -15
- package/src/index.js +21 -32
- package/types/App/Command/DepsCommand.d.ts +16 -0
- package/types/App/Command/Options.d.ts +37 -40
- package/types/App/Command/index.d.ts +6 -6
- package/types/App/Core/CoreApp.d.ts +2 -2
- package/types/App/Core/UI.d.ts +6 -7
- package/types/App/Core/Widget.d.ts +4 -4
- package/types/App/Core/index.d.ts +3 -3
- package/types/App/Scenario.d.ts +1 -1
- package/types/App/User/Command/Message.d.ts +2 -16
- package/types/App/User/Command/Options.d.ts +29 -29
- package/types/App/User/Command/index.d.ts +2 -3
- package/types/App/User/UserApp.d.ts +5 -5
- package/types/App/User/index.d.ts +2 -2
- package/types/App/index.d.ts +4 -4
- package/types/Component/Process/Process.d.ts +2 -2
- package/types/Component/Process/index.d.ts +2 -2
- package/types/Component/SortableList/SortableList.d.ts +58 -0
- package/types/Component/SortableList/index.d.ts +2 -0
- package/types/Component/Welcome/Input.d.ts +1 -1
- package/types/Component/Welcome/Welcome.d.ts +1 -1
- package/types/Component/Welcome/index.d.ts +2 -2
- package/types/Component/index.d.ts +5 -3
- package/types/Frame/Frame.d.ts +1 -1
- package/types/Frame/Props.d.ts +1 -1
- package/types/Model/index.d.ts +1 -1
- package/types/StdIn.d.ts +2 -2
- package/types/StdOut.d.ts +1 -1
- package/types/View/View.d.ts +7 -7
- package/types/core/Error/index.d.ts +1 -1
- package/types/core/Flow.d.ts +320 -0
- package/types/core/Form/Form.d.ts +2 -2
- package/types/core/Form/Input.d.ts +15 -4
- package/types/core/Form/Message.d.ts +1 -1
- package/types/core/Form/index.d.ts +3 -3
- package/types/core/InputAdapter.d.ts +2 -2
- package/types/core/Intent.d.ts +65 -68
- package/types/core/Message/InputMessage.d.ts +65 -65
- package/types/core/Message/Message.d.ts +1 -1
- package/types/core/Message/OutputMessage.d.ts +1 -1
- package/types/core/Message/index.d.ts +2 -2
- package/types/core/Stream.d.ts +1 -2
- package/types/core/UiAdapter.d.ts +22 -4
- package/types/core/index.d.ts +5 -2
- package/types/index.d.ts +10 -10
package/src/core/Form/Input.js
CHANGED
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
* @property {string} type - Input type (text, email, number, select, etc.).
|
|
17
17
|
* @property {boolean} required - Whether the field is required.
|
|
18
18
|
* @property {string} placeholder - Placeholder text.
|
|
19
|
-
* @property {
|
|
20
|
-
* @property {Function
|
|
19
|
+
* @property {InputOptions} options - Select options (if type is 'select').
|
|
20
|
+
* @property {Function} validation - Custom validation function.
|
|
21
|
+
* @property {string} mask - Mask pattern (e.g. '###-###').
|
|
21
22
|
* @property {*} defaultValue - Default value.
|
|
22
23
|
*/
|
|
23
24
|
export default class FormInput {
|
|
@@ -27,7 +28,8 @@ export default class FormInput {
|
|
|
27
28
|
/** @type {boolean} */ required = false
|
|
28
29
|
/** @type {string} */ placeholder = ''
|
|
29
30
|
/** @type {InputOptions} */ options = []
|
|
30
|
-
/** @type {
|
|
31
|
+
/** @type {Function} */ validation = () => true
|
|
32
|
+
/** @type {string} */ mask = ''
|
|
31
33
|
/** @type {*} */ defaultValue = null
|
|
32
34
|
|
|
33
35
|
/**
|
|
@@ -39,7 +41,14 @@ export default class FormInput {
|
|
|
39
41
|
NUMBER: 'number',
|
|
40
42
|
SELECT: 'select',
|
|
41
43
|
CHECKBOX: 'checkbox',
|
|
42
|
-
TEXTAREA: 'textarea'
|
|
44
|
+
TEXTAREA: 'textarea',
|
|
45
|
+
PASSWORD: 'password',
|
|
46
|
+
SECRET: 'secret',
|
|
47
|
+
MASK: 'mask',
|
|
48
|
+
CONFIRM: 'confirm',
|
|
49
|
+
TOGGLE: 'toggle',
|
|
50
|
+
MULTISELECT: 'multiselect',
|
|
51
|
+
AUTOCOMPLETE: 'autocomplete',
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
/**
|
|
@@ -52,7 +61,8 @@ export default class FormInput {
|
|
|
52
61
|
* @param {boolean} [props.required=false] - Is required.
|
|
53
62
|
* @param {string} [props.placeholder=''] - Placeholder.
|
|
54
63
|
* @param {InputOptions} [props.options=[]] - Select options or async function to retrieve data with the search and page.
|
|
55
|
-
* @param {Function} [props.validation
|
|
64
|
+
* @param {Function} [props.validation] - Custom validation.
|
|
65
|
+
* @param {string} [props.mask=''] - Mask pattern.
|
|
56
66
|
* @param {*} [props.defaultValue=null] - Default value.
|
|
57
67
|
*/
|
|
58
68
|
constructor(props) {
|
|
@@ -64,7 +74,8 @@ export default class FormInput {
|
|
|
64
74
|
placeholder = this.placeholder,
|
|
65
75
|
options = [],
|
|
66
76
|
validation = this.validation,
|
|
67
|
-
|
|
77
|
+
mask = '',
|
|
78
|
+
defaultValue = this.defaultValue,
|
|
68
79
|
} = props
|
|
69
80
|
|
|
70
81
|
if (!name) {
|
|
@@ -78,6 +89,7 @@ export default class FormInput {
|
|
|
78
89
|
this.placeholder = String(placeholder)
|
|
79
90
|
this.options = options
|
|
80
91
|
this.validation = validation
|
|
92
|
+
this.mask = String(mask)
|
|
81
93
|
this.defaultValue = defaultValue
|
|
82
94
|
|
|
83
95
|
this.requireValidType()
|
|
@@ -85,12 +97,14 @@ export default class FormInput {
|
|
|
85
97
|
|
|
86
98
|
requireValidType() {
|
|
87
99
|
if (!Object.values(FormInput.TYPES).includes(this.type)) {
|
|
88
|
-
throw new TypeError(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
100
|
+
throw new TypeError(
|
|
101
|
+
[
|
|
102
|
+
'FormInput.type is invalid!',
|
|
103
|
+
['Provided', this.type].join(': '),
|
|
104
|
+
'Available types:',
|
|
105
|
+
...Object.values(FormInput.TYPES).map((t) => ` - ${t}`),
|
|
106
|
+
].join('\n'),
|
|
107
|
+
)
|
|
94
108
|
}
|
|
95
109
|
}
|
|
96
110
|
|
|
@@ -107,7 +121,8 @@ export default class FormInput {
|
|
|
107
121
|
required: this.required,
|
|
108
122
|
placeholder: this.placeholder,
|
|
109
123
|
options: this.options,
|
|
110
|
-
|
|
124
|
+
mask: this.mask,
|
|
125
|
+
defaultValue: this.defaultValue,
|
|
111
126
|
}
|
|
112
127
|
}
|
|
113
128
|
|
|
@@ -117,7 +132,7 @@ export default class FormInput {
|
|
|
117
132
|
*/
|
|
118
133
|
static from(input) {
|
|
119
134
|
if (input instanceof FormInput) return input
|
|
120
|
-
if (
|
|
135
|
+
if (typeof input === 'string') {
|
|
121
136
|
return new FormInput({ name: input, label: input })
|
|
122
137
|
}
|
|
123
138
|
return new FormInput(input)
|
package/src/core/Form/Message.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import UiMessage from
|
|
1
|
+
import UiMessage from '../Message/Message.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* FormMessage – specialized UiMessage for forms.
|
|
@@ -15,10 +15,7 @@ export default class FormMessage extends UiMessage {
|
|
|
15
15
|
*/
|
|
16
16
|
constructor(input = {}) {
|
|
17
17
|
super(input)
|
|
18
|
-
const {
|
|
19
|
-
data = {},
|
|
20
|
-
schema = {},
|
|
21
|
-
} = input
|
|
18
|
+
const { data = {}, schema = {} } = input
|
|
22
19
|
|
|
23
20
|
// Store data and schema for easy access
|
|
24
21
|
this.data = data
|
|
@@ -81,4 +78,4 @@ export default class FormMessage extends UiMessage {
|
|
|
81
78
|
|
|
82
79
|
return { isValid: Object.keys(errors).length === 0, errors }
|
|
83
80
|
}
|
|
84
|
-
}
|
|
81
|
+
}
|
package/src/core/Form/index.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import FormInput from
|
|
2
|
-
import FormMessage from
|
|
3
|
-
import Form from
|
|
1
|
+
import FormInput from './Input.js'
|
|
2
|
+
import FormMessage from './Message.js'
|
|
3
|
+
import Form from './Form.js'
|
|
4
4
|
|
|
5
|
-
export {
|
|
6
|
-
FormInput,
|
|
7
|
-
FormMessage,
|
|
8
|
-
Form,
|
|
9
|
-
}
|
|
5
|
+
export { FormInput, FormMessage, Form }
|
|
10
6
|
|
|
11
7
|
export default Form
|
package/src/core/InputAdapter.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import Event from
|
|
2
|
-
import CancelError from
|
|
3
|
-
import UiMessage from
|
|
1
|
+
import Event from '@nan0web/event/oop'
|
|
2
|
+
import CancelError from './Error/CancelError.js'
|
|
3
|
+
import UiMessage from './Message/Message.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Abstract input adapter for UI implementations.
|
|
@@ -20,9 +20,7 @@ export default class InputAdapter extends Event {
|
|
|
20
20
|
* @returns {void}
|
|
21
21
|
*/
|
|
22
22
|
start() {
|
|
23
|
-
this.emit('input',
|
|
24
|
-
UiMessage.from({ body: "Adapter started" })
|
|
25
|
-
)
|
|
23
|
+
this.emit('input', UiMessage.from({ body: 'Adapter started' }))
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Message } from
|
|
1
|
+
import { Message } from '@nan0web/co'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {Object} MessageBodySchema
|
|
@@ -40,13 +40,13 @@ export default class UiMessage extends Message {
|
|
|
40
40
|
SUCCESS: 'success',
|
|
41
41
|
WARNING: 'warning',
|
|
42
42
|
COMMAND: 'command',
|
|
43
|
-
NAVIGATION: 'navigation'
|
|
43
|
+
NAVIGATION: 'navigation',
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** @type {string} */
|
|
47
|
-
type =
|
|
47
|
+
type = ''
|
|
48
48
|
/** @type {string} */
|
|
49
|
-
id =
|
|
49
|
+
id = ''
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Creates a UiMessage.
|
|
@@ -56,10 +56,7 @@ export default class UiMessage extends Message {
|
|
|
56
56
|
constructor(input = {}) {
|
|
57
57
|
super(input)
|
|
58
58
|
|
|
59
|
-
const {
|
|
60
|
-
type = this.type,
|
|
61
|
-
id = this.id,
|
|
62
|
-
} = input
|
|
59
|
+
const { type = this.type, id = this.id } = input
|
|
63
60
|
this.id = id || `ui-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
|
|
64
61
|
this.type = String(type)
|
|
65
62
|
|
|
@@ -96,7 +93,7 @@ export default class UiMessage extends Message {
|
|
|
96
93
|
for (const [field, schema] of entries) {
|
|
97
94
|
const value = body[field]
|
|
98
95
|
const fn = schema?.validate
|
|
99
|
-
if (
|
|
96
|
+
if ('function' === typeof fn) {
|
|
100
97
|
const ok = fn.apply(body, [value])
|
|
101
98
|
if (ok !== true) {
|
|
102
99
|
result.set(field, String(ok))
|
|
@@ -105,7 +102,7 @@ export default class UiMessage extends Message {
|
|
|
105
102
|
}
|
|
106
103
|
const required = schema?.required ?? false
|
|
107
104
|
if (required && !value) {
|
|
108
|
-
result.set(field,
|
|
105
|
+
result.set(field, 'Required')
|
|
109
106
|
continue
|
|
110
107
|
}
|
|
111
108
|
if (schema?.pattern && schema.pattern instanceof RegExp) {
|
|
@@ -116,10 +113,10 @@ export default class UiMessage extends Message {
|
|
|
116
113
|
}
|
|
117
114
|
if (schema?.options) {
|
|
118
115
|
if (!Array.isArray(schema.options)) {
|
|
119
|
-
throw new Error(
|
|
116
|
+
throw new Error('Schema options must be an array of possible values')
|
|
120
117
|
}
|
|
121
118
|
if (!schema.options.includes(value)) {
|
|
122
|
-
result.set(field,
|
|
119
|
+
result.set(field, 'Enumeration must have one value')
|
|
123
120
|
continue
|
|
124
121
|
}
|
|
125
122
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import UiMessage from
|
|
1
|
+
import UiMessage from './Message.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* OutputMessage – message sent from the system to the UI.
|
|
@@ -11,7 +11,7 @@ export default class OutputMessage extends UiMessage {
|
|
|
11
11
|
LOW: 0,
|
|
12
12
|
NORMAL: 1,
|
|
13
13
|
HIGH: 2,
|
|
14
|
-
CRITICAL: 3
|
|
14
|
+
CRITICAL: 3,
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/** @type {string[]} */
|
|
@@ -36,19 +36,19 @@ export default class OutputMessage extends UiMessage {
|
|
|
36
36
|
body = [],
|
|
37
37
|
meta = {},
|
|
38
38
|
error = null,
|
|
39
|
-
priority = OutputMessage.PRIORITY.NORMAL
|
|
39
|
+
priority = OutputMessage.PRIORITY.NORMAL,
|
|
40
40
|
} = input
|
|
41
41
|
|
|
42
|
-
const contentSource = 'body' in input ? body :
|
|
43
|
-
'content' in input ? content : []
|
|
42
|
+
const contentSource = 'body' in input ? body : 'content' in input ? content : []
|
|
44
43
|
|
|
45
|
-
this.body = Array.isArray(contentSource)
|
|
46
|
-
contentSource
|
|
47
|
-
|
|
44
|
+
this.body = Array.isArray(contentSource)
|
|
45
|
+
? contentSource
|
|
46
|
+
: contentSource
|
|
47
|
+
? [String(contentSource)]
|
|
48
|
+
: []
|
|
48
49
|
|
|
49
50
|
this.meta = meta
|
|
50
|
-
this.error = error instanceof Error ? error :
|
|
51
|
-
error ? new Error(String(error)) : null
|
|
51
|
+
this.error = error instanceof Error ? error : error ? new Error(String(error)) : null
|
|
52
52
|
this.priority = Number(priority)
|
|
53
53
|
|
|
54
54
|
if (!this.type && this.error) {
|
|
@@ -91,7 +91,7 @@ export default class OutputMessage extends UiMessage {
|
|
|
91
91
|
let combinedError = this.error
|
|
92
92
|
let combinedPriority = this.priority
|
|
93
93
|
|
|
94
|
-
messages.forEach(msg => {
|
|
94
|
+
messages.forEach((msg) => {
|
|
95
95
|
if (msg instanceof OutputMessage) {
|
|
96
96
|
combinedContent.push(...msg.content)
|
|
97
97
|
combinedMeta = { ...combinedMeta, ...msg.meta }
|
|
@@ -105,7 +105,7 @@ export default class OutputMessage extends UiMessage {
|
|
|
105
105
|
meta: combinedMeta,
|
|
106
106
|
error: combinedError,
|
|
107
107
|
priority: combinedPriority,
|
|
108
|
-
type: this.type
|
|
108
|
+
type: this.type,
|
|
109
109
|
})
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -122,11 +122,13 @@ export default class OutputMessage extends UiMessage {
|
|
|
122
122
|
type: this.type,
|
|
123
123
|
id: this.id,
|
|
124
124
|
time: this.time.toISOString(),
|
|
125
|
-
error: this.error
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
error: this.error
|
|
126
|
+
? {
|
|
127
|
+
message: this.error.message,
|
|
128
|
+
stack: this.error.stack,
|
|
129
|
+
}
|
|
130
|
+
: null,
|
|
131
|
+
priority: this.priority,
|
|
130
132
|
}
|
|
131
133
|
}
|
|
132
134
|
|
|
@@ -16,7 +16,7 @@ class OutputAdapter extends Event {
|
|
|
16
16
|
* @throws {Error} If not overridden by a subclass.
|
|
17
17
|
*/
|
|
18
18
|
render(message) {
|
|
19
|
-
throw new Error(
|
|
19
|
+
throw new Error('render() must be implemented by subclass')
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -27,14 +27,16 @@ class OutputAdapter extends Event {
|
|
|
27
27
|
* @returns {void}
|
|
28
28
|
*/
|
|
29
29
|
progress(progress, metadata = {}) {
|
|
30
|
-
this.render(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
this.render(
|
|
31
|
+
OutputMessage.from({
|
|
32
|
+
content: [],
|
|
33
|
+
metadata: {
|
|
34
|
+
...metadata,
|
|
35
|
+
progress,
|
|
36
|
+
elementType: 'progress',
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
)
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
/**
|
|
@@ -47,4 +49,4 @@ class OutputAdapter extends Event {
|
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
export default OutputAdapter
|
|
52
|
+
export default OutputAdapter
|
package/src/core/Stream.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import StreamEntry from
|
|
2
|
-
|
|
3
|
-
export { StreamEntry } // Export if needed
|
|
1
|
+
import StreamEntry from './StreamEntry.js'
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
4
|
* Agnostic UI stream for processing progress using async generators.
|
|
@@ -18,6 +16,9 @@ export default class UIStream {
|
|
|
18
16
|
static createProcessor(signal, processorFn) {
|
|
19
17
|
return async function* () {
|
|
20
18
|
try {
|
|
19
|
+
if (signal.aborted) {
|
|
20
|
+
throw new DOMException('Aborted', 'AbortError')
|
|
21
|
+
}
|
|
21
22
|
const result = await processorFn()
|
|
22
23
|
yield result
|
|
23
24
|
} catch (/** @type {any} */ error) {
|
package/src/core/StreamEntry.js
CHANGED
|
@@ -24,7 +24,7 @@ export default class StreamEntry {
|
|
|
24
24
|
* Error message associated with the stream entry.
|
|
25
25
|
* @type {string}
|
|
26
26
|
*/
|
|
27
|
-
error =
|
|
27
|
+
error = ''
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Creates a new StreamEntry instance.
|
|
@@ -56,4 +56,4 @@ export default class StreamEntry {
|
|
|
56
56
|
if (input instanceof StreamEntry) return input
|
|
57
57
|
return new StreamEntry(input)
|
|
58
58
|
}
|
|
59
|
-
}
|
|
59
|
+
}
|
package/src/core/UiAdapter.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import EventProcessor from
|
|
2
|
-
import CancelError from
|
|
3
|
-
import UIMessage from
|
|
4
|
-
import UIForm from
|
|
5
|
-
import FormInput from
|
|
1
|
+
import EventProcessor from '@nan0web/event/oop'
|
|
2
|
+
import CancelError from './Error/CancelError.js'
|
|
3
|
+
import UIMessage from './Message/Message.js'
|
|
4
|
+
import UIForm from './Form/Form.js'
|
|
5
|
+
import FormInput from './Form/Input.js'
|
|
6
|
+
import OutputAdapter from './OutputAdapter.js'
|
|
7
|
+
import OutputMessage from './Message/OutputMessage.js'
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Unified UI Adapter that handles both input and output operations.
|
|
@@ -34,7 +36,7 @@ export default class UiAdapter extends EventProcessor {
|
|
|
34
36
|
* @returns {void}
|
|
35
37
|
*/
|
|
36
38
|
start() {
|
|
37
|
-
this.emit('input', UIMessage.from({ body:
|
|
39
|
+
this.emit('input', UIMessage.from({ body: 'Adapter started' }))
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
/**
|
|
@@ -81,6 +83,30 @@ export default class UiAdapter extends EventProcessor {
|
|
|
81
83
|
throw new Error('select() method must be implemented in subclass')
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Process a UIForm and return its result.
|
|
88
|
+
*
|
|
89
|
+
* This default implementation follows an **agnostic UI** approach:
|
|
90
|
+
* it simply returns the form instance (with optional initial state merged)
|
|
91
|
+
* without UI interaction. Concrete adapters (CLI, Web, etc.) can override
|
|
92
|
+
* this method to render the form, collect user input and return a richer
|
|
93
|
+
* result object (`{ form, cancelled }`).
|
|
94
|
+
*
|
|
95
|
+
* @param {UIForm} form - The UIForm instance to process.
|
|
96
|
+
* @param {object} [initialState={}] - Pre‑filled values for the form.
|
|
97
|
+
* @returns {Promise<{ form: UIForm, cancelled?: boolean }>} Form processing result.
|
|
98
|
+
*/
|
|
99
|
+
async processForm(form, initialState = {}) {
|
|
100
|
+
// Merge any provided initial state into the form's internal state.
|
|
101
|
+
if (initialState && typeof initialState === 'object') {
|
|
102
|
+
form.state = { ...form.state, ...initialState }
|
|
103
|
+
}
|
|
104
|
+
// In the agnostic baseline we do not perform any interactive I/O.
|
|
105
|
+
// Sub‑classes may provide a UI (render, ask, etc.) and return
|
|
106
|
+
// `{ form, cancelled: true/false, ... }`.
|
|
107
|
+
return { form }
|
|
108
|
+
}
|
|
109
|
+
|
|
84
110
|
/**
|
|
85
111
|
* Ensures a message's body is fully and validly filled.
|
|
86
112
|
* Generates a form from the message's static Body schema,
|
|
@@ -96,22 +122,21 @@ export default class UiAdapter extends EventProcessor {
|
|
|
96
122
|
*/
|
|
97
123
|
async requireInput(msg) {
|
|
98
124
|
if (!msg) {
|
|
99
|
-
throw new Error(
|
|
125
|
+
throw new Error('Message instance is required')
|
|
100
126
|
}
|
|
101
127
|
if (!(msg instanceof UIMessage)) {
|
|
102
|
-
throw new TypeError(
|
|
128
|
+
throw new TypeError('Message must be an instance of UIMessage')
|
|
103
129
|
}
|
|
104
130
|
/** @type {Map<string,string>} */
|
|
105
131
|
let errors = msg.validate()
|
|
106
132
|
while (errors.size > 0) {
|
|
107
|
-
const form = generateForm(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
throw new CancelError("User cancelled form")
|
|
133
|
+
const form = generateForm(/** @type {any} */ (msg.constructor).Body, {
|
|
134
|
+
initialState: msg.body,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
const formResult = (await this.processForm) ? this.processForm(form, msg.body) : {} // Assume method exists or handle differently, but error indicates missing method; perhaps remove if not used
|
|
138
|
+
if (formResult.cancelled) {
|
|
139
|
+
throw new CancelError('User cancelled form')
|
|
115
140
|
}
|
|
116
141
|
|
|
117
142
|
const updatedBody = { ...msg.body, ...formResult.form.state }
|
|
@@ -119,10 +144,13 @@ export default class UiAdapter extends EventProcessor {
|
|
|
119
144
|
|
|
120
145
|
if (updatedErrors.size > 0) {
|
|
121
146
|
if (this.output) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
147
|
+
this.output.render(
|
|
148
|
+
new OutputMessage({
|
|
149
|
+
type: 'Alert',
|
|
150
|
+
variant: 'error',
|
|
151
|
+
body: Array.from(updatedErrors.values()).join('\n'),
|
|
152
|
+
}),
|
|
153
|
+
)
|
|
126
154
|
}
|
|
127
155
|
errors = updatedErrors
|
|
128
156
|
continue
|
|
@@ -142,8 +170,8 @@ export default class UiAdapter extends EventProcessor {
|
|
|
142
170
|
* @throws {Error} If not implemented in subclass.
|
|
143
171
|
*/
|
|
144
172
|
render(message) {
|
|
145
|
-
throw new Error(
|
|
146
|
-
this.emit(
|
|
173
|
+
throw new Error('render() must be implemented by subclass')
|
|
174
|
+
this.emit('rendered', message)
|
|
147
175
|
}
|
|
148
176
|
}
|
|
149
177
|
|
|
@@ -157,14 +185,14 @@ export default class UiAdapter extends EventProcessor {
|
|
|
157
185
|
* @returns {UIForm} Form instance ready for input.
|
|
158
186
|
*/
|
|
159
187
|
export function generateForm(BodyClass, options = {}) {
|
|
160
|
-
const { initialState = {}, t = v => v } = options
|
|
188
|
+
const { initialState = {}, t = (v) => v } = options
|
|
161
189
|
const fields = []
|
|
162
190
|
|
|
163
191
|
for (const [name, schema] of Object.entries(BodyClass)) {
|
|
164
|
-
if (typeof schema !==
|
|
192
|
+
if (typeof schema !== 'object' || schema === null || !name || !schema.help) continue
|
|
165
193
|
|
|
166
194
|
const label = t(schema.help) || name
|
|
167
|
-
const placeholder = t(schema.placeholder || schema.defaultValue ||
|
|
195
|
+
const placeholder = t(schema.placeholder || schema.defaultValue || '')
|
|
168
196
|
const isRequired = !!schema.required
|
|
169
197
|
|
|
170
198
|
fields.push(
|
|
@@ -174,12 +202,12 @@ export function generateForm(BodyClass, options = {}) {
|
|
|
174
202
|
type: schema.type || FormInput.TYPES.TEXT,
|
|
175
203
|
required: isRequired,
|
|
176
204
|
placeholder,
|
|
177
|
-
options: schema.options ? schema.options.map(o => String(o)) : [],
|
|
205
|
+
options: schema.options ? schema.options.map((o) => String(o)) : [],
|
|
178
206
|
validation: schema.validate
|
|
179
207
|
? (value) => {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
208
|
+
const res = schema.validate(value)
|
|
209
|
+
return res === true ? true : typeof res === 'string' ? res : `Invalid ${name}`
|
|
210
|
+
}
|
|
183
211
|
: () => true,
|
|
184
212
|
}),
|
|
185
213
|
)
|
package/src/core/index.js
CHANGED
|
@@ -1,12 +1,41 @@
|
|
|
1
|
-
export { default as InputAdapter } from
|
|
2
|
-
export { default as OutputAdapter } from
|
|
3
|
-
export { default as UIStream } from "./Stream.js"
|
|
1
|
+
export { default as InputAdapter } from './InputAdapter.js'
|
|
2
|
+
export { default as OutputAdapter } from './OutputAdapter.js'
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
export {
|
|
7
|
-
|
|
8
|
-
export {
|
|
4
|
+
import UIStream from './Stream.js'
|
|
5
|
+
export { UIStream, UIStream as UiStream }
|
|
6
|
+
import StreamEntry from './StreamEntry.js'
|
|
7
|
+
export { StreamEntry, StreamEntry as UiStreamEntry }
|
|
9
8
|
|
|
10
|
-
export { default as
|
|
9
|
+
export { default as UiMessage } from './Message/Message.js'
|
|
10
|
+
export { default as FormMessage } from './Form/Message.js'
|
|
11
|
+
export { default as FormInput } from './Form/Input.js'
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
import UIForm from './Form/Form.js'
|
|
14
|
+
export { UIForm, UIForm as UiForm }
|
|
15
|
+
|
|
16
|
+
export { default as Error, CancelError } from './Error/index.js'
|
|
17
|
+
|
|
18
|
+
export { default as UiAdapter } from './UiAdapter.js'
|
|
19
|
+
|
|
20
|
+
// Flow — Yield-Based Universal UI Architecture
|
|
21
|
+
export {
|
|
22
|
+
runFlow,
|
|
23
|
+
flow,
|
|
24
|
+
View,
|
|
25
|
+
Prompt,
|
|
26
|
+
Stream,
|
|
27
|
+
Alert,
|
|
28
|
+
Toast,
|
|
29
|
+
Badge,
|
|
30
|
+
Text,
|
|
31
|
+
Table,
|
|
32
|
+
Input,
|
|
33
|
+
Select,
|
|
34
|
+
Confirm,
|
|
35
|
+
Multiselect,
|
|
36
|
+
Mask,
|
|
37
|
+
Password,
|
|
38
|
+
Spinner,
|
|
39
|
+
Progress,
|
|
40
|
+
} from './Flow.js'
|
|
41
|
+
export { default as Flow } from './Flow.js'
|
package/src/functions.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
import { empty } from
|
|
1
|
+
import { empty } from '@nan0web/types'
|
|
2
2
|
|
|
3
3
|
export const spaces = (options = {}) => {
|
|
4
4
|
const { cols = [], padding = 1, aligns = [] } = options
|
|
5
|
-
return (row) =>
|
|
5
|
+
return (row) =>
|
|
6
6
|
row.map((str, i) => {
|
|
7
|
-
const pad =
|
|
8
|
-
return aligns[i] ===
|
|
7
|
+
const pad = ' '.repeat(cols[i] - str.length + padding)
|
|
8
|
+
return aligns[i] === 'r' ? pad + str : str + pad
|
|
9
9
|
})
|
|
10
|
-
)
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
export const weight = (arr) => {
|
|
14
|
-
return (Fn = v => v) => {
|
|
13
|
+
return (Fn = (v) => v) => {
|
|
15
14
|
const cols = []
|
|
16
|
-
arr.forEach(m => {
|
|
15
|
+
arr.forEach((m) => {
|
|
17
16
|
Fn(m).forEach((str, i) => {
|
|
18
17
|
if (undefined === cols[i]) cols[i] = 0
|
|
19
18
|
cols[i] = Math.max(str.length, cols[i])
|
|
@@ -23,16 +22,10 @@ export const weight = (arr) => {
|
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
|
|
27
25
|
export const table = (options = {}) => {
|
|
28
|
-
const {
|
|
29
|
-
Fn = v => v,
|
|
30
|
-
cols = [],
|
|
31
|
-
padding = 1,
|
|
32
|
-
aligns = []
|
|
33
|
-
} = options
|
|
26
|
+
const { Fn = (v) => v, cols = [], padding = 1, aligns = [] } = options
|
|
34
27
|
return (arr) => {
|
|
35
28
|
const options = { cols: empty(cols) ? weight(arr)(Fn) : cols, padding, aligns }
|
|
36
|
-
return arr.map(row => spaces(options)(row))
|
|
29
|
+
return arr.map((row) => spaces(options)(row))
|
|
37
30
|
}
|
|
38
31
|
}
|