@nan0web/ui 1.0.2 → 1.0.4
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 -54
- package/package.json +21 -17
- package/src/App/Core/CoreApp.js +14 -19
- package/src/App/Core/UI.js +4 -50
- package/src/App/Core/Widget.js +4 -6
- package/src/App/User/Command/Message.js +23 -37
- package/src/App/User/Command/index.js +3 -8
- package/src/App/User/UserApp.js +32 -27
- package/src/App/User/UserUI.js +4 -4
- package/src/App/User/index.js +0 -6
- package/src/App/index.js +0 -3
- package/src/README.md.js +33 -57
- package/src/StdIn.js +12 -15
- package/src/View/View.js +5 -5
- package/src/core/Error/index.js +9 -0
- package/src/core/Form/Form.js +43 -23
- package/src/core/Form/Input.js +16 -7
- package/src/core/Form/Message.js +6 -8
- package/src/core/InputAdapter.js +6 -2
- package/src/core/Message/Message.js +109 -19
- package/src/core/Message/OutputMessage.js +8 -8
- package/src/core/Message/index.js +3 -4
- package/src/core/Stream.js +10 -10
- package/src/core/UiAdapter.js +189 -0
- package/src/core/index.js +5 -6
- package/src/index.js +5 -4
- package/types/App/Core/CoreApp.d.ts +9 -9
- package/types/App/Core/UI.d.ts +5 -15
- package/types/App/Core/Widget.d.ts +7 -8
- package/types/App/User/Command/Message.d.ts +15 -29
- package/types/App/User/Command/Options.d.ts +9 -2
- package/types/App/User/Command/index.d.ts +3 -7
- package/types/App/User/UserApp.d.ts +19 -9
- package/types/App/User/UserUI.d.ts +0 -9
- package/types/App/User/index.d.ts +0 -4
- package/types/App/index.d.ts +1 -3
- package/types/Frame/Frame.d.ts +5 -5
- package/types/StdIn.d.ts +13 -13
- package/types/View/View.d.ts +9 -9
- package/types/core/Error/index.d.ts +6 -0
- package/types/core/Form/Form.d.ts +14 -11
- package/types/core/Form/Input.d.ts +20 -7
- package/types/core/Form/Message.d.ts +5 -4
- package/types/core/InputAdapter.d.ts +2 -0
- package/types/core/Intent.d.ts +91 -0
- package/types/core/Message/InputMessage.d.ts +5 -5
- package/types/core/Message/Message.d.ts +58 -15
- package/types/core/Message/OutputMessage.d.ts +4 -4
- package/types/core/Message/index.d.ts +3 -4
- package/types/core/Stream.d.ts +5 -4
- package/types/core/StreamEntry.d.ts +1 -1
- package/types/core/UiAdapter.d.ts +104 -0
- package/types/core/index.d.ts +3 -3
- package/types/index.d.ts +5 -4
- package/src/App/Command/Options.js +0 -78
- package/src/App/Command/index.js +0 -9
- package/src/App/User/Command/Options.js +0 -48
- package/src/core/Message/InputMessage.js +0 -119
package/src/App/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import Command from "./Command/index.js"
|
|
2
1
|
import Scenario from "./Scenario.js"
|
|
3
2
|
import UI from "./Core/UI.js"
|
|
4
3
|
|
|
@@ -8,7 +7,6 @@ import User from "./User/index.js"
|
|
|
8
7
|
export {
|
|
9
8
|
Core,
|
|
10
9
|
User,
|
|
11
|
-
Command,
|
|
12
10
|
Scenario,
|
|
13
11
|
UI,
|
|
14
12
|
}
|
|
@@ -16,7 +14,6 @@ export {
|
|
|
16
14
|
export default {
|
|
17
15
|
Core,
|
|
18
16
|
User,
|
|
19
|
-
Command,
|
|
20
17
|
Scenario,
|
|
21
18
|
UI,
|
|
22
19
|
}
|
package/src/README.md.js
CHANGED
|
@@ -8,17 +8,13 @@ import {
|
|
|
8
8
|
runSpawn,
|
|
9
9
|
} from "@nan0web/test"
|
|
10
10
|
import {
|
|
11
|
-
App,
|
|
12
|
-
Component,
|
|
13
11
|
Frame,
|
|
14
|
-
InputMessage,
|
|
15
12
|
Model,
|
|
16
13
|
OutputMessage,
|
|
17
|
-
UIMessage,
|
|
18
|
-
UIForm,
|
|
19
|
-
UIStream,
|
|
20
14
|
View,
|
|
21
15
|
FormInput,
|
|
16
|
+
UiMessage,
|
|
17
|
+
UiForm,
|
|
22
18
|
} from "./index.js"
|
|
23
19
|
import { Welcome } from "./Component/index.js"
|
|
24
20
|
|
|
@@ -108,8 +104,7 @@ function testRender() {
|
|
|
108
104
|
*
|
|
109
105
|
* UI communication is built around messages:
|
|
110
106
|
*
|
|
111
|
-
* - **`
|
|
112
|
-
* - **`InputMessage`** – user input message (value, options)
|
|
107
|
+
* - **`UiMessage`** – abstract message base class
|
|
113
108
|
* - **`OutputMessage`** – system output (content, error, priority)
|
|
114
109
|
*
|
|
115
110
|
* Messages are simple, serializable data containers. They help build
|
|
@@ -118,19 +113,22 @@ function testRender() {
|
|
|
118
113
|
it("How to create input and output messages?", () => {
|
|
119
114
|
//import { InputMessage, OutputMessage } from '@nan0web/ui'
|
|
120
115
|
|
|
121
|
-
const input =
|
|
116
|
+
const input = UiMessage.from({ body: 'Hello User' })
|
|
122
117
|
const output = OutputMessage.from({ content: ['Welcome to @nan0web/ui'] })
|
|
123
|
-
console.info(input
|
|
124
|
-
console.info(output
|
|
125
|
-
assert.
|
|
126
|
-
assert.
|
|
118
|
+
console.info(input) // ← Message { body: "Hello User", head: {}, id: "....", type: "" }
|
|
119
|
+
console.info(String(output)) // ← Welcome to @nan0web/ui
|
|
120
|
+
assert.deepStrictEqual(console.output()[0][1].body, "Hello User")
|
|
121
|
+
assert.deepStrictEqual(console.output()[0][1].head, {})
|
|
122
|
+
assert.deepStrictEqual(console.output()[0][1].type, "")
|
|
123
|
+
assert.ok(console.output()[0][1].id)
|
|
124
|
+
assert.ok(console.output()[1][1].endsWith('Welcome to @nan0web/ui'))
|
|
127
125
|
})
|
|
128
126
|
|
|
129
127
|
/**
|
|
130
128
|
* @docs
|
|
131
129
|
* ### Forms
|
|
132
130
|
*
|
|
133
|
-
* `
|
|
131
|
+
* `UiForm` supports field definitions, data management, and schema validation.
|
|
134
132
|
* Every form includes a title, fields, and current state.
|
|
135
133
|
*
|
|
136
134
|
* Field types include:
|
|
@@ -142,10 +140,10 @@ function testRender() {
|
|
|
142
140
|
* - `checkbox`
|
|
143
141
|
* - `textarea`
|
|
144
142
|
*/
|
|
145
|
-
it("How to define and validate a
|
|
146
|
-
//import {
|
|
143
|
+
it("How to define and validate a UiForm?", () => {
|
|
144
|
+
//import { UiForm } from '@nan0web/ui'
|
|
147
145
|
|
|
148
|
-
const form = new
|
|
146
|
+
const form = new UiForm({
|
|
149
147
|
title: "Contact Form",
|
|
150
148
|
fields: [
|
|
151
149
|
FormInput.from({ name: "email", label: "Email Address", type: "email", required: true }),
|
|
@@ -157,12 +155,12 @@ function testRender() {
|
|
|
157
155
|
}
|
|
158
156
|
})
|
|
159
157
|
|
|
160
|
-
const
|
|
161
|
-
console.info(
|
|
162
|
-
console.info(
|
|
158
|
+
const errors = form.validate()
|
|
159
|
+
console.info(errors.size) // ← 1
|
|
160
|
+
console.info(errors.get("email")) // ← Invalid email format
|
|
163
161
|
|
|
164
|
-
assert.equal(
|
|
165
|
-
assert.equal(
|
|
162
|
+
assert.equal(console.output()[0][1], 1)
|
|
163
|
+
assert.equal(console.output()[1][1], "Invalid email format")
|
|
166
164
|
})
|
|
167
165
|
|
|
168
166
|
/**
|
|
@@ -249,30 +247,6 @@ function testRender() {
|
|
|
249
247
|
assert.ok(renderedVisible.includes("Frame content"))
|
|
250
248
|
})
|
|
251
249
|
|
|
252
|
-
/**
|
|
253
|
-
* @docs
|
|
254
|
-
* ### App Architecture
|
|
255
|
-
*
|
|
256
|
-
* `App` provides the main application logic.
|
|
257
|
-
*
|
|
258
|
-
* - Core – minimal UI layer
|
|
259
|
-
* - User – user-specific UI commands
|
|
260
|
-
*
|
|
261
|
-
* Each app registers commands and binds them to UI actions.
|
|
262
|
-
*/
|
|
263
|
-
it("How to create a basic user app that greets?", async () => {
|
|
264
|
-
//import { App, View } from '@nan0web/ui'
|
|
265
|
-
|
|
266
|
-
const app = new App.User.App({ name: "GreetApp" })
|
|
267
|
-
const view = new View()
|
|
268
|
-
view.register("Welcome", Welcome)
|
|
269
|
-
|
|
270
|
-
const cmd = App.Command.Message.parse("welcome --user Bob")
|
|
271
|
-
const result = await app.processCommand(cmd, new App.User.UI(app, view))
|
|
272
|
-
console.info(String(result)) // ← Welcome Bob!
|
|
273
|
-
assert.ok(console.output()[0][1].includes("Welcome Bob!"))
|
|
274
|
-
})
|
|
275
|
-
|
|
276
250
|
/**
|
|
277
251
|
* @docs
|
|
278
252
|
* ### Models
|
|
@@ -301,13 +275,15 @@ function testRender() {
|
|
|
301
275
|
* with minimal setup.
|
|
302
276
|
*/
|
|
303
277
|
it("How to test UI components with assertions?", () => {
|
|
304
|
-
//import { Welcome
|
|
278
|
+
//import { Welcome } from '@nan0web/ui'
|
|
305
279
|
|
|
306
280
|
const output = Welcome({ user: { name: "Test" } })
|
|
307
|
-
|
|
308
|
-
console.
|
|
309
|
-
|
|
310
|
-
|
|
281
|
+
console.info(output) // ← Welcome Test!
|
|
282
|
+
assert.deepStrictEqual(console.output()[0][1], [
|
|
283
|
+
["Welcome", " ", "Test", "!"],
|
|
284
|
+
["What can we do today great?"],
|
|
285
|
+
[""],
|
|
286
|
+
])
|
|
311
287
|
})
|
|
312
288
|
|
|
313
289
|
/**
|
|
@@ -316,10 +292,10 @@ function testRender() {
|
|
|
316
292
|
*
|
|
317
293
|
* The library includes rich playground demos:
|
|
318
294
|
*
|
|
319
|
-
* - [Registration Form](./
|
|
320
|
-
* - [Currency Exchange](./
|
|
321
|
-
* - [Mobile Top-up](./
|
|
322
|
-
* - [Language Selector](./
|
|
295
|
+
* - [Registration Form](./play/registration.form.js)
|
|
296
|
+
* - [Currency Exchange](./play/currency.exchange.js)
|
|
297
|
+
* - [Mobile Top-up](./play/topup.telephone.js)
|
|
298
|
+
* - [Language Selector](./play/language.form.js)
|
|
323
299
|
*
|
|
324
300
|
* Run to explore live functionality:
|
|
325
301
|
*/
|
|
@@ -330,10 +306,10 @@ function testRender() {
|
|
|
330
306
|
* git clone https://github.com/nan0web/ui.git
|
|
331
307
|
* cd ui
|
|
332
308
|
* npm install
|
|
333
|
-
* npm run
|
|
309
|
+
* npm run play
|
|
334
310
|
* ```
|
|
335
311
|
*/
|
|
336
|
-
assert.ok(String(pkg.scripts?.
|
|
312
|
+
assert.ok(String(pkg.scripts?.play).includes("node play"))
|
|
337
313
|
const response = await runSpawn("git", ["remote", "get-url", "origin"])
|
|
338
314
|
assert.ok(response.code === 0, "git command fails (e.g., not in a git repo)")
|
|
339
315
|
assert.ok(response.text.trim().endsWith(":nan0web/ui.git"))
|
package/src/StdIn.js
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import EventProcessor from "@nan0web/event/oop"
|
|
2
|
-
import { CommandMessage } from "@nan0web/co"
|
|
3
2
|
import { typeOf } from "@nan0web/types"
|
|
4
|
-
import
|
|
3
|
+
import { UiMessage } from "./core/index.js"
|
|
5
4
|
|
|
6
5
|
class Processor extends EventProcessor { }
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* Handles standard input stream with message buffering.
|
|
10
9
|
*/
|
|
11
|
-
class StdIn extends EventProcessor {
|
|
10
|
+
export default class StdIn extends EventProcessor {
|
|
12
11
|
/** @type {number} Read interval in milliseconds */
|
|
13
12
|
static READ_INTERVAL = 99
|
|
14
13
|
|
|
15
14
|
/** @type {string[]} Messages to ignore */
|
|
16
15
|
static IGNORE_MESSAGES = ["", "undefined"]
|
|
17
16
|
|
|
18
|
-
/** @type {
|
|
17
|
+
/** @type {UiMessage[]} Input message buffer */
|
|
19
18
|
stream = []
|
|
20
19
|
|
|
21
20
|
/** @type {Processor} Input processor */
|
|
@@ -25,7 +24,7 @@ class StdIn extends EventProcessor {
|
|
|
25
24
|
* Creates a new StdIn instance.
|
|
26
25
|
* @param {object} props - StdIn properties
|
|
27
26
|
* @param {Processor} [props.processor] - Input processor
|
|
28
|
-
* @param {
|
|
27
|
+
* @param {UiMessage[]} [props.stream=[]] - Initial input stream
|
|
29
28
|
*/
|
|
30
29
|
constructor(props = {}) {
|
|
31
30
|
super()
|
|
@@ -59,13 +58,13 @@ class StdIn extends EventProcessor {
|
|
|
59
58
|
/**
|
|
60
59
|
* Reads a message from the input stream.
|
|
61
60
|
* Waits until messages are available if stream is empty.
|
|
62
|
-
* @returns {Promise<
|
|
61
|
+
* @returns {Promise<UiMessage>} Next input message
|
|
63
62
|
*/
|
|
64
63
|
async read() {
|
|
65
64
|
while (this.ended) {
|
|
66
65
|
await new Promise(resolve => setTimeout(resolve, StdIn.READ_INTERVAL))
|
|
67
66
|
}
|
|
68
|
-
return this.stream.shift() ?? new
|
|
67
|
+
return this.stream.shift() ?? new UiMessage()
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
/**
|
|
@@ -84,17 +83,16 @@ class StdIn extends EventProcessor {
|
|
|
84
83
|
}
|
|
85
84
|
|
|
86
85
|
/**
|
|
87
|
-
* Decodes a message into an
|
|
88
|
-
* @param {
|
|
89
|
-
* @returns {
|
|
86
|
+
* Decodes a message into an UiMessage instance.
|
|
87
|
+
* @param {UiMessage | string[] | any} message - Message to decode
|
|
88
|
+
* @returns {UiMessage} Decoded input message
|
|
90
89
|
*/
|
|
91
90
|
decode(message) {
|
|
92
|
-
if (message instanceof
|
|
91
|
+
if (message instanceof UiMessage) return message
|
|
93
92
|
if (Array.isArray(message) && message.every(typeOf(String))) {
|
|
94
|
-
|
|
95
|
-
return new InputMessage({ value: parsed })
|
|
93
|
+
return new UiMessage({ value: message })
|
|
96
94
|
}
|
|
97
|
-
return new
|
|
95
|
+
return new UiMessage(message)
|
|
98
96
|
}
|
|
99
97
|
|
|
100
98
|
/**
|
|
@@ -108,4 +106,3 @@ class StdIn extends EventProcessor {
|
|
|
108
106
|
}
|
|
109
107
|
}
|
|
110
108
|
|
|
111
|
-
export default StdIn
|
package/src/View/View.js
CHANGED
|
@@ -3,13 +3,13 @@ import Frame, { FrameRenderMethod } from "../Frame/Frame.js"
|
|
|
3
3
|
import Locale from "../Locale.js"
|
|
4
4
|
import StdOut from "../StdOut.js"
|
|
5
5
|
import StdIn from "../StdIn.js"
|
|
6
|
-
import InputMessage from "../core/Message/InputMessage.js"
|
|
7
6
|
import RenderOptions from "./RenderOptions.js"
|
|
7
|
+
import UiMessage from "../core/Message/Message.js"
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @typedef {Object} ComponentFn
|
|
11
11
|
* @property {string} name
|
|
12
|
-
* @property {(input:
|
|
12
|
+
* @property {(input: UiMessage) => Promise<any>} ask
|
|
13
13
|
* @property {Function} bind
|
|
14
14
|
*/
|
|
15
15
|
|
|
@@ -256,8 +256,8 @@ export default class View {
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
/**
|
|
259
|
-
* @param {
|
|
260
|
-
* @returns {Promise<
|
|
259
|
+
* @param {UiMessage} input
|
|
260
|
+
* @returns {Promise<UiMessage | null>}
|
|
261
261
|
*/
|
|
262
262
|
async ask(input) {
|
|
263
263
|
const name = input.constructor.name.replace(/Input$/, "")
|
|
@@ -268,7 +268,7 @@ export default class View {
|
|
|
268
268
|
let result = null
|
|
269
269
|
do {
|
|
270
270
|
const answer = await this.stdin.read()
|
|
271
|
-
result = /** @type {typeof
|
|
271
|
+
result = /** @type {typeof UiMessage} */ (input.constructor).from(answer)
|
|
272
272
|
} while (!result.isValid && !result.escaped)
|
|
273
273
|
return result.escaped ? null : result
|
|
274
274
|
}
|
package/src/core/Form/Form.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Message from "@nan0web/co"
|
|
1
2
|
import FormMessage from "./Message.js"
|
|
2
3
|
import FormInput from "./Input.js"
|
|
3
4
|
|
|
@@ -18,24 +19,24 @@ export default class UIForm extends FormMessage {
|
|
|
18
19
|
/** @type {Object} */ schema = {}
|
|
19
20
|
|
|
20
21
|
/* ------------------------------------------------------------------ */
|
|
21
|
-
/* static
|
|
22
|
+
/* static validation registry */
|
|
22
23
|
/* ------------------------------------------------------------------ */
|
|
23
24
|
|
|
24
25
|
/** @type {Object<string,Function>} */
|
|
25
|
-
static
|
|
26
|
+
static _validations = {}
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
|
-
* Register a custom
|
|
29
|
+
* Register a custom validation that can be referenced by name in a schema.
|
|
29
30
|
*
|
|
30
|
-
* @param {string} name - Identifier used in schema.
|
|
31
|
+
* @param {string} name - Identifier used in schema.validation.
|
|
31
32
|
* @param {(value:any)=>true|string} fn - Function returns true if valid,
|
|
32
33
|
* otherwise returns an error message.
|
|
33
34
|
*/
|
|
34
|
-
static
|
|
35
|
+
static addValidation(name, fn) {
|
|
35
36
|
if (typeof name !== "string" || typeof fn !== "function") {
|
|
36
|
-
throw new Error("
|
|
37
|
+
throw new Error("validation name must be a string and fn must be a function")
|
|
37
38
|
}
|
|
38
|
-
UIForm.
|
|
39
|
+
UIForm._validations[name] = fn
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
/**
|
|
@@ -111,10 +112,10 @@ export default class UIForm extends FormMessage {
|
|
|
111
112
|
/**
|
|
112
113
|
* Validates the entire form.
|
|
113
114
|
*
|
|
114
|
-
* @returns {
|
|
115
|
+
* @returns {Map<string, string>} Map of validation errors, empty if valid.
|
|
115
116
|
*/
|
|
116
117
|
validate() {
|
|
117
|
-
const errors =
|
|
118
|
+
const errors = new Map()
|
|
118
119
|
let isValid = true
|
|
119
120
|
|
|
120
121
|
this.fields.forEach((field) => {
|
|
@@ -122,7 +123,7 @@ export default class UIForm extends FormMessage {
|
|
|
122
123
|
|
|
123
124
|
// Required validation based on field definition or schema
|
|
124
125
|
if (field.required && (fieldValue === '' || fieldValue === null || fieldValue === undefined)) {
|
|
125
|
-
errors
|
|
126
|
+
errors.set(field.name, 'This field is required')
|
|
126
127
|
isValid = false
|
|
127
128
|
return
|
|
128
129
|
}
|
|
@@ -131,12 +132,14 @@ export default class UIForm extends FormMessage {
|
|
|
131
132
|
const { isValid: fieldValid, errors: fieldErrors } = this.validateField(field.name, fieldValue)
|
|
132
133
|
|
|
133
134
|
if (!fieldValid) {
|
|
134
|
-
Object.
|
|
135
|
+
for (const [key, err] of Object.entries(fieldErrors)) {
|
|
136
|
+
errors.set(key, err)
|
|
137
|
+
}
|
|
135
138
|
isValid = false
|
|
136
139
|
}
|
|
137
140
|
})
|
|
138
141
|
|
|
139
|
-
return
|
|
142
|
+
return errors
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
/**
|
|
@@ -208,15 +211,15 @@ export default class UIForm extends FormMessage {
|
|
|
208
211
|
}
|
|
209
212
|
}
|
|
210
213
|
|
|
211
|
-
// Custom
|
|
212
|
-
if (schema.
|
|
214
|
+
// Custom validation – can be a function or a string referencing a static validation
|
|
215
|
+
if (schema.validation) {
|
|
213
216
|
let result
|
|
214
|
-
if (typeof schema.
|
|
215
|
-
result = schema.
|
|
216
|
-
} else if (typeof schema.
|
|
217
|
-
const fn = UIForm.
|
|
217
|
+
if (typeof schema.validation === 'function') {
|
|
218
|
+
result = schema.validation(value)
|
|
219
|
+
} else if (typeof schema.validation === 'string') {
|
|
220
|
+
const fn = UIForm._validations[schema.validation]
|
|
218
221
|
if (!fn) {
|
|
219
|
-
throw new Error(`
|
|
222
|
+
throw new Error(`validation "${schema.validation}" not registered`)
|
|
220
223
|
}
|
|
221
224
|
result = fn(value)
|
|
222
225
|
}
|
|
@@ -236,9 +239,7 @@ export default class UIForm extends FormMessage {
|
|
|
236
239
|
*/
|
|
237
240
|
toJSON() {
|
|
238
241
|
return {
|
|
239
|
-
|
|
240
|
-
type: this.type,
|
|
241
|
-
time: this.time.toISOString(),
|
|
242
|
+
time: new Date(this.time).toISOString(),
|
|
242
243
|
title: this.title,
|
|
243
244
|
fields: this.fields.map(f => f.toJSON ? f.toJSON() : f),
|
|
244
245
|
state: this.state,
|
|
@@ -252,6 +253,25 @@ export default class UIForm extends FormMessage {
|
|
|
252
253
|
*/
|
|
253
254
|
static from(input) {
|
|
254
255
|
if (input instanceof UIForm) return input
|
|
256
|
+
if (input instanceof Message) {
|
|
257
|
+
const Class = input.constructor
|
|
258
|
+
const fields = []
|
|
259
|
+
for (const [name, value] of Object.entries(input)) {
|
|
260
|
+
fields.push(new FormInput({
|
|
261
|
+
name,
|
|
262
|
+
label: Class[name]?.label ?? Class[`${name}Label`] ?? name,
|
|
263
|
+
type: Class[name]?.type ?? Class[`${name}Type`] ?? typeof value,
|
|
264
|
+
required: Class[name]?.required ?? Class[`${name}Required`] ?? false,
|
|
265
|
+
placeholder: Class[name]?.placeholder ?? Class[`${name}Placeholder`] ?? "",
|
|
266
|
+
defaultValue: Class[name]?.defaultValue ?? Class[`${name}Default`] ?? "",
|
|
267
|
+
validation: Class[name]?.validation ?? Class[`${name}Validation`] ?? (() => true),
|
|
268
|
+
}))
|
|
269
|
+
}
|
|
270
|
+
return new UIForm({
|
|
271
|
+
title: Class.name,
|
|
272
|
+
fields
|
|
273
|
+
})
|
|
274
|
+
}
|
|
255
275
|
return new UIForm(input)
|
|
256
276
|
}
|
|
257
277
|
|
|
@@ -280,7 +300,7 @@ export default class UIForm extends FormMessage {
|
|
|
280
300
|
required: !!custom.required,
|
|
281
301
|
placeholder: custom.placeholder ?? "",
|
|
282
302
|
options: custom.options ?? [],
|
|
283
|
-
|
|
303
|
+
validation: custom.validation ?? undefined,
|
|
284
304
|
defaultValue: custom.defaultValue ?? "",
|
|
285
305
|
})
|
|
286
306
|
})
|
package/src/core/Form/Input.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} Filter
|
|
3
|
+
* @property {string} [q=""]
|
|
4
|
+
* @property {number} [offset=0]
|
|
5
|
+
* @property {number} [limit=36]
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** @typedef {Array<string> | ((filter: Filter) => Promise<string[]>)} InputOptions */
|
|
9
|
+
|
|
1
10
|
/**
|
|
2
11
|
* Form input field descriptor.
|
|
3
12
|
*
|
|
@@ -8,7 +17,7 @@
|
|
|
8
17
|
* @property {boolean} required - Whether the field is required.
|
|
9
18
|
* @property {string} placeholder - Placeholder text.
|
|
10
19
|
* @property {Array<string>} options - Select options (if type is 'select').
|
|
11
|
-
* @property {Function|null}
|
|
20
|
+
* @property {Function|null} validation - Custom validation function.
|
|
12
21
|
* @property {*} defaultValue - Default value.
|
|
13
22
|
*/
|
|
14
23
|
export default class FormInput {
|
|
@@ -17,8 +26,8 @@ export default class FormInput {
|
|
|
17
26
|
/** @type {string} */ type = 'text'
|
|
18
27
|
/** @type {boolean} */ required = false
|
|
19
28
|
/** @type {string} */ placeholder = ''
|
|
20
|
-
/** @type {
|
|
21
|
-
/** @type {
|
|
29
|
+
/** @type {InputOptions} */ options = []
|
|
30
|
+
/** @type {import("@nan0web/co").ValidateFn|null} */ validation = null
|
|
22
31
|
/** @type {*} */ defaultValue = null
|
|
23
32
|
|
|
24
33
|
/**
|
|
@@ -42,8 +51,8 @@ export default class FormInput {
|
|
|
42
51
|
* @param {string} [props.type='text'] - Input type.
|
|
43
52
|
* @param {boolean} [props.required=false] - Is required.
|
|
44
53
|
* @param {string} [props.placeholder=''] - Placeholder.
|
|
45
|
-
* @param {
|
|
46
|
-
* @param {Function} [props.
|
|
54
|
+
* @param {InputOptions} [props.options=[]] - Select options or async function to retrieve data with the search and page.
|
|
55
|
+
* @param {Function} [props.validation=null] - Custom validation.
|
|
47
56
|
* @param {*} [props.defaultValue=null] - Default value.
|
|
48
57
|
*/
|
|
49
58
|
constructor(props) {
|
|
@@ -54,7 +63,7 @@ export default class FormInput {
|
|
|
54
63
|
required = this.required,
|
|
55
64
|
placeholder = this.placeholder,
|
|
56
65
|
options = [],
|
|
57
|
-
|
|
66
|
+
validation = this.validation,
|
|
58
67
|
defaultValue = this.defaultValue
|
|
59
68
|
} = props
|
|
60
69
|
|
|
@@ -68,7 +77,7 @@ export default class FormInput {
|
|
|
68
77
|
this.required = Boolean(required)
|
|
69
78
|
this.placeholder = String(placeholder)
|
|
70
79
|
this.options = options
|
|
71
|
-
this.
|
|
80
|
+
this.validation = validation
|
|
72
81
|
this.defaultValue = defaultValue
|
|
73
82
|
|
|
74
83
|
this.requireValidType()
|
package/src/core/Form/Message.js
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import UiMessage from "../Message/Message.js"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* FormMessage – specialized
|
|
4
|
+
* FormMessage – specialized UiMessage for forms.
|
|
5
|
+
* It carries form-specific data and schema for validation.
|
|
5
6
|
*
|
|
6
7
|
* @class FormMessage
|
|
7
|
-
* @extends
|
|
8
|
+
* @extends UiMessage
|
|
8
9
|
*/
|
|
9
|
-
export default class FormMessage extends
|
|
10
|
+
export default class FormMessage extends UiMessage {
|
|
10
11
|
/**
|
|
11
12
|
* Creates a FormMessage.
|
|
12
13
|
*
|
|
13
14
|
* @param {Object} [input={}] - Message properties.
|
|
14
15
|
*/
|
|
15
16
|
constructor(input = {}) {
|
|
16
|
-
super(
|
|
17
|
-
...input,
|
|
18
|
-
type: OutputMessage.TYPES.FORM,
|
|
19
|
-
})
|
|
17
|
+
super(input)
|
|
20
18
|
const {
|
|
21
19
|
data = {},
|
|
22
20
|
schema = {},
|
package/src/core/InputAdapter.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Event from "@nan0web/event/oop"
|
|
2
|
-
import InputMessage from "./Message/InputMessage.js"
|
|
3
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.
|
|
@@ -10,6 +10,10 @@ import CancelError from "./Error/CancelError.js"
|
|
|
10
10
|
*/
|
|
11
11
|
export default class InputAdapter extends Event {
|
|
12
12
|
static CancelError = CancelError
|
|
13
|
+
/** @returns {typeof CancelError} */
|
|
14
|
+
get CancelError() {
|
|
15
|
+
return /** @type {typeof InputAdapter} */ (this.constructor).CancelError
|
|
16
|
+
}
|
|
13
17
|
/**
|
|
14
18
|
* Starts listening for input and emits an `input` event.
|
|
15
19
|
*
|
|
@@ -17,7 +21,7 @@ export default class InputAdapter extends Event {
|
|
|
17
21
|
*/
|
|
18
22
|
start() {
|
|
19
23
|
this.emit('input',
|
|
20
|
-
|
|
24
|
+
UiMessage.from({ body: "Adapter started" })
|
|
21
25
|
)
|
|
22
26
|
}
|
|
23
27
|
|