@nan0web/ui 1.0.2 → 1.0.3

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 (44) hide show
  1. package/README.md +2 -35
  2. package/package.json +7 -2
  3. package/src/App/Core/CoreApp.js +14 -19
  4. package/src/App/Core/UI.js +5 -5
  5. package/src/App/User/UserApp.js +4 -19
  6. package/src/App/User/UserUI.js +4 -4
  7. package/src/App/User/index.js +0 -6
  8. package/src/App/index.js +0 -3
  9. package/src/README.md.js +3 -27
  10. package/src/StdIn.js +1 -3
  11. package/src/core/Error/index.js +9 -0
  12. package/src/core/Form/Form.js +36 -18
  13. package/src/core/Form/Input.js +16 -7
  14. package/src/core/Form/Message.js +6 -9
  15. package/src/core/InputAdapter.js +4 -0
  16. package/src/core/Message/InputMessage.js +11 -11
  17. package/src/core/Message/OutputMessage.js +1 -1
  18. package/src/core/index.js +2 -0
  19. package/src/index.js +1 -0
  20. package/types/App/Core/CoreApp.d.ts +9 -9
  21. package/types/App/Core/UI.d.ts +5 -5
  22. package/types/App/Core/Widget.d.ts +1 -1
  23. package/types/App/User/Command/Message.d.ts +2 -3
  24. package/types/App/User/Command/Options.d.ts +9 -2
  25. package/types/App/User/Command/index.d.ts +1 -4
  26. package/types/App/User/UserApp.d.ts +10 -7
  27. package/types/App/User/UserUI.d.ts +0 -9
  28. package/types/App/User/index.d.ts +0 -4
  29. package/types/App/index.d.ts +1 -3
  30. package/types/Frame/Frame.d.ts +5 -5
  31. package/types/View/View.d.ts +3 -3
  32. package/types/core/Error/index.d.ts +6 -0
  33. package/types/core/Form/Form.d.ts +12 -6
  34. package/types/core/Form/Input.d.ts +20 -7
  35. package/types/core/Form/Message.d.ts +10 -4
  36. package/types/core/InputAdapter.d.ts +2 -0
  37. package/types/core/Message/InputMessage.d.ts +5 -5
  38. package/types/core/Message/OutputMessage.d.ts +1 -1
  39. package/types/core/Stream.d.ts +1 -1
  40. package/types/core/StreamEntry.d.ts +1 -1
  41. package/types/core/index.d.ts +1 -0
  42. package/types/index.d.ts +1 -0
  43. package/src/App/Command/Options.js +0 -78
  44. package/src/App/Command/index.js +0 -9
package/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # @nan0web/ui
2
2
 
3
- |[Status](https://github.com/nan0web/monorepo/blob/main/system.md#написання-сценаріїв)|Documentation|Test coverage|Features|Npm version|
4
- |---|---|---|---|---|
5
- |🟢 `96.8%` |🧪 [English 🏴󠁧󠁢󠁥󠁮󠁧󠁿](https://github.com/nan0web/ui/blob/main/README.md)<br />[Українською 🇺🇦](https://github.com/nan0web/ui/blob/main/docs/uk/README.md) |🟡 `81.1%` |✅ d.ts 📜 system.md 🕹️ playground |1.0.1 |
3
+ <!-- %PACKAGE_STATUS% -->
6
4
 
7
5
  A lightweight, agnostic UI framework designed with the **nan0web philosophy**
8
6
  — one application logic, many UI implementations.
@@ -53,10 +51,9 @@ decoupled communication systems between UI components.
53
51
  How to create input and output messages?
54
52
  ```js
55
53
  import { InputMessage, OutputMessage } from '@nan0web/ui'
56
-
57
54
  const input = InputMessage.from({ value: 'Hello User' })
58
55
  const output = OutputMessage.from({ content: ['Welcome to @nan0web/ui'] })
59
- console.info(input.value) // ← Hello User
56
+ console.info(input.value) // ← Message { body: "Hello User", head: {} }
60
57
  console.info(output.content[0]) // ← Welcome to @nan0web/ui
61
58
  ```
62
59
  ### Forms
@@ -76,7 +73,6 @@ Field types include:
76
73
  How to define and validate a UIForm?
77
74
  ```js
78
75
  import { UIForm } from '@nan0web/ui'
79
-
80
76
  const form = new UIForm({
81
77
  title: "Contact Form",
82
78
  fields: [
@@ -88,11 +84,9 @@ const form = new UIForm({
88
84
  message: "Hello!"
89
85
  }
90
86
  })
91
-
92
87
  const result = form.validate()
93
88
  console.info(result.isValid) // ← false
94
89
  console.info(result.errors.email) // ← Invalid email format
95
-
96
90
  ```
97
91
  ### Components
98
92
 
@@ -104,7 +98,6 @@ Components render data as frame-ready output.
104
98
  How to render the Welcome component?
105
99
  ```js
106
100
  import { Welcome } from '@nan0web/ui'
107
-
108
101
  const frame = Welcome({ user: { name: "Alice" } })
109
102
  const firstLine = frame[0].join("")
110
103
  console.info(firstLine) // ← Welcome Alice!
@@ -122,7 +115,6 @@ Every view has:
122
115
  How to render frame with View?
123
116
  ```js
124
117
  import { View } from '@nan0web/ui'
125
-
126
118
  const view = new View()
127
119
  view.render(1)(["Hello, world"])
128
120
  console.info(String(view.frame)) // ← "\rHello, world"
@@ -141,38 +133,15 @@ Render methods:
141
133
  How to create a Frame with fixed size?
142
134
  ```js
143
135
  import { Frame } from '@nan0web/ui'
144
-
145
136
  const frame = new Frame({
146
137
  value: [["Frame content"]],
147
138
  width: 20,
148
139
  height: 5,
149
140
  renderMethod: Frame.RenderMethod.APPEND,
150
141
  })
151
-
152
142
  const rendered = frame.render()
153
143
  console.info(rendered.includes("Frame content")) // ← true
154
144
  ```
155
- ### App Architecture
156
-
157
- `App` provides the main application logic.
158
-
159
- - Core – minimal UI layer
160
- - User – user-specific UI commands
161
-
162
- Each app registers commands and binds them to UI actions.
163
-
164
- How to create a basic user app that greets?
165
- ```js
166
- import { App, View } from '@nan0web/ui'
167
-
168
- const app = new App.User.App({ name: "GreetApp" })
169
- const view = new View()
170
- view.register("Welcome", Welcome)
171
-
172
- const cmd = App.Command.Message.parse("welcome --user Bob")
173
- const result = await app.processCommand(cmd, new App.User.UI(app, view))
174
- console.info(String(result)) // ← Welcome Bob!
175
- ```
176
145
  ### Models
177
146
 
178
147
  UI models are plain data objects managed by `Model` classes.
@@ -182,7 +151,6 @@ UI models are plain data objects managed by `Model` classes.
182
151
  How to use a User model?
183
152
  ```js
184
153
  import { Model } from '@nan0web/ui'
185
-
186
154
  const user = new Model.User({ name: "Charlie", email: "charlie@example.com" })
187
155
  console.info(user.name) // ← Charlie
188
156
  console.info(user.email) // ← charlie@example.com
@@ -197,7 +165,6 @@ with minimal setup.
197
165
  How to test UI components with assertions?
198
166
  ```js
199
167
  import { Welcome, InputMessage } from '@nan0web/ui'
200
-
201
168
  const output = Welcome({ user: { name: "Test" } })
202
169
  const input = InputMessage.from({ value: "test" })
203
170
  console.log(output[0].join("")) // ← Welcome Test!
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nan0web/ui",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "NaN•Web UI. One application logic (algorithm) and many UI.",
5
5
  "main": "src/index.js",
6
6
  "types": "types/index.d.ts",
@@ -13,7 +13,8 @@
13
13
  "scripts": {
14
14
  "build": "tsc",
15
15
  "playground": "node playground/main.js",
16
- "test": "node --test --test-timeout=3333 \"src/**/*.test.js\" | nan0test parse --fail",
16
+ "test": "node --test --test-timeout=3333 \"src/**/*.test.js\"",
17
+ "test:nan0test": "node --test --test-timeout=3333 \"src/**/*.test.js\" | nan0test parse --fail",
17
18
  "test:coverage": "node --experimental-test-coverage --test-coverage-include=\"src/**/*.js\" --test-coverage-exclude=\"src/**/*.test.js\" --test \"src/**/*.test.js\"",
18
19
  "test:coverage:collect": "nan0test coverage",
19
20
  "test:docs": "node --test src/README.md.js",
@@ -35,6 +36,10 @@
35
36
  "import": "./src/Component/index.js",
36
37
  "types": "./types/Component/index.d.ts"
37
38
  },
39
+ "./core": {
40
+ "import": "./src/core/index.js",
41
+ "types": "./types/core/index.d.ts"
42
+ },
38
43
  "./cli-app": "./apps/cli/src/index.js",
39
44
  "./mobile-app": "./apps/mobile/src/index.js",
40
45
  "./web-app": "./apps/web/src/App.jsx"
@@ -1,5 +1,5 @@
1
+ import { Message } from "@nan0web/co"
1
2
  import { typeOf } from "@nan0web/types"
2
- import { CommandMessage } from "../Command/index.js"
3
3
  import UI from "./UI.js"
4
4
 
5
5
  /** @typedef {Function} CommandFn */
@@ -18,7 +18,7 @@ export default class CoreApp {
18
18
  /** @type {object} App state */
19
19
  state
20
20
 
21
- /** @type {CommandMessage} Starting command parsed from argv */
21
+ /** @type {Message} Starting command parsed from argv */
22
22
  startCommand
23
23
 
24
24
  /**
@@ -26,18 +26,19 @@ export default class CoreApp {
26
26
  * @param {object} props - CoreApp properties
27
27
  * @param {string} [props.name="CoreApp"] - App name
28
28
  * @param {object} [props.state={}] - Initial state object
29
- * @param {string[]} [props.argv=[]] - Command line arguments to parse
29
+ * @param {Message} [props.startCommand=new Message()] - Command line arguments to parse
30
30
  */
31
31
  constructor(props = {}) {
32
32
  const {
33
33
  name = "CoreApp",
34
34
  state = {},
35
- argv = [],
35
+ startCommand = new Message(),
36
36
  } = props
37
37
  this.name = String(name)
38
38
  this.state = state
39
39
  this.commands = new Map()
40
- this.startCommand = CommandMessage.parse(argv)
40
+ // @deprecated @todo fix the argv by moving to ui-cli.
41
+ this.startCommand = Message.from(startCommand ?? {})
41
42
  }
42
43
 
43
44
  /**
@@ -78,39 +79,33 @@ export default class CoreApp {
78
79
 
79
80
  /**
80
81
  * Process a command message.
81
- * @param {CommandMessage} commandMessage - Command to process
82
+ * @param {Message} msg - Command to process
82
83
  * @param {UI} ui - UI instance to use for rendering
83
84
  * @returns {Promise<any>} Output of the command
84
85
  * @throws {Error} If the command is not registered
85
86
  */
86
- async processCommand(commandMessage, ui) {
87
- const cmd = commandMessage.args[0]
88
- const handler = this.commands.get(cmd)
87
+ async processCommand(msg, ui) {
88
+ const handler = this.commands.get(msg.constructor.name)
89
89
  if (!handler) {
90
90
  throw new Error([
91
91
  "Unknown command", ": ",
92
- cmd, "\n",
92
+ msg.constructor.name, "\n",
93
93
  "Available commands", ": ",
94
94
  [...this.commands.keys()].join(", "),
95
95
  ].join(""))
96
96
  }
97
- const Class = /** @type {typeof CommandMessage} */ (commandMessage.constructor)
98
- const command = Class.from({
99
- args: commandMessage.args.slice(1),
100
- opts: commandMessage.opts,
101
- })
102
- return await handler.apply(this, [command, ui])
97
+ return await handler.apply(this, [msg, ui])
103
98
  }
104
99
 
105
100
  /**
106
101
  * Process an array of command messages sequentially.
107
- * @param {CommandMessage[]} commandMessages - Array of commands to process
102
+ * @param {Message[]} Messages - Array of commands to process
108
103
  * @param {UI} ui - UI instance to use for rendering
109
104
  * @returns {Promise<any[]>} Array of command outputs
110
105
  */
111
- async processCommands(commandMessages, ui) {
106
+ async processCommands(Messages, ui) {
112
107
  const results = []
113
- for (const cmdMsg of commandMessages) {
108
+ for (const cmdMsg of Messages) {
114
109
  const result = await this.processCommand(cmdMsg, ui)
115
110
  results.push(result)
116
111
  }
@@ -1,8 +1,8 @@
1
+ import { Message } from "@nan0web/co"
2
+ import { notEmpty } from "@nan0web/types"
1
3
  import View from "../../View/View.js"
2
4
  import CoreApp from "./CoreApp.js"
3
5
  import Widget from "./Widget.js"
4
- import { notEmpty } from "@nan0web/types"
5
- import { CommandMessage } from "../Command/index.js"
6
6
 
7
7
  /** @typedef {import("../../View/View.js").ComponentFn} ComponentFn */
8
8
 
@@ -25,10 +25,10 @@ class UI extends Widget {
25
25
  }
26
26
 
27
27
  /**
28
- * Convert raw input to CommandMessage array.
28
+ * Convert raw input to Message array.
29
29
  * Must be implemented by subclasses.
30
30
  * @param {any} rawInput - Raw input to convert
31
- * @returns {CommandMessage[]} Array of command messages
31
+ * @returns {Message[]} Array of command messages
32
32
  * @throws {Error} Always thrown as this method must be implemented by subclasses
33
33
  */
34
34
  convertInput(rawInput) {
@@ -58,7 +58,7 @@ class UI extends Widget {
58
58
  const answer = await this.app.selectCommand(this)
59
59
  if (answer) {
60
60
  const [input] = this.convertInput(rawInput)
61
- const cmd = new CommandMessage({
61
+ const cmd = new Message({
62
62
  argv: [answer],
63
63
  opts: input?.opts ?? {},
64
64
  })
@@ -1,6 +1,6 @@
1
+ import { Message } from "@nan0web/co"
1
2
  import { notEmpty } from "@nan0web/types"
2
3
  import CoreApp from "../Core/CoreApp.js"
3
- import Command, { CommandMessage } from "./Command/index.js"
4
4
  import User from "../../Model/User/User.js"
5
5
  import UserUI from "./UserUI.js"
6
6
  import InputMessage from "../../core/Message/InputMessage.js"
@@ -11,34 +11,19 @@ import InputMessage from "../../core/Message/InputMessage.js"
11
11
  * User can change user data to see another Welcome view.
12
12
  */
13
13
  class UserApp extends CoreApp {
14
- /** @type {CommandMessage} Starting command parsed from argv */
15
- startCommand
16
-
17
- /** @type {object} App state */
18
- state
19
-
20
14
  /**
21
15
  * Creates a new UserApp instance.
22
- * @param {object} props - UserApp properties
23
- * @param {string} [props.name="UserApp"] - App name
24
- * @param {object} [props.state={}] - Initial state object
25
- * @param {string[]} [props.argv=[]] - Command line arguments to parse
16
+ * @param {Partial<CoreApp>} [props={}] - UserApp properties
26
17
  */
27
18
  constructor(props = {}) {
28
19
  super(props)
29
- const {
30
- argv = [],
31
- state = {},
32
- } = props
33
- this.state = state
34
- this.startCommand = CommandMessage.parse(argv)
35
20
  this.registerCommand("setUser", this.setUser.bind(this))
36
21
  this.registerCommand("welcome", this.welcome.bind(this))
37
22
  }
38
23
 
39
24
  /**
40
25
  * Set user data from params.
41
- * @param {CommandMessage} cmd - Command message with user data
26
+ * @param {Message} cmd - Command message with user data
42
27
  * @param {UserUI} ui - UI instance
43
28
  * @returns {Promise<{ message: string }>} Welcome message
44
29
  */
@@ -52,7 +37,7 @@ class UserApp extends CoreApp {
52
37
 
53
38
  /**
54
39
  * Show welcome message for current user.
55
- * @param {CommandMessage} cmd - Command message
40
+ * @param {Message} cmd - Command message
56
41
  * @param {UserUI} ui - UI instance
57
42
  * @returns {Promise<string[][]>} Welcome view output
58
43
  */
@@ -1,5 +1,5 @@
1
+ import { Message } from "@nan0web/co"
1
2
  import App from "../Core/index.js"
2
- import { CommandMessage } from "./Command/index.js"
3
3
 
4
4
  /**
5
5
  * UserUI connects UserApp and View.
@@ -8,13 +8,13 @@ import { CommandMessage } from "./Command/index.js"
8
8
  */
9
9
  export default class UserUI extends App.UI {
10
10
  /**
11
- * Convert raw input to CommandMessage array.
11
+ * Convert raw input to Message array.
12
12
  * If user.name provided in rawInput, use it directly.
13
13
  * Otherwise ask user for name.
14
14
  * @param {any} rawInput - Raw input to convert
15
- * @returns {CommandMessage[]} Array of command messages
15
+ * @returns {Message[]} Array of command messages
16
16
  */
17
17
  convertInput(rawInput) {
18
- return [CommandMessage.parse(rawInput)]
18
+ return [new Message({ body: rawInput })]
19
19
  }
20
20
  }
@@ -1,15 +1,9 @@
1
1
  import UserApp from "./UserApp.js"
2
2
  import UserUI from "./UserUI.js"
3
- import UserCommand from "./Command/index.js"
4
- import Command from "../Command/index.js"
5
3
 
6
4
  export { UserApp, UserUI }
7
5
 
8
6
  export default {
9
7
  App: UserApp,
10
8
  UI: UserUI,
11
- Command: {
12
- ...Command,
13
- ...UserCommand,
14
- },
15
9
  }
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
@@ -120,10 +120,10 @@ function testRender() {
120
120
 
121
121
  const input = InputMessage.from({ value: 'Hello User' })
122
122
  const output = OutputMessage.from({ content: ['Welcome to @nan0web/ui'] })
123
- console.info(input.value) // ← Hello User
123
+ console.info(input.value) // ← Message { body: "Hello User", head: {} }
124
124
  console.info(output.content[0]) // ← Welcome to @nan0web/ui
125
- assert.equal(input.value, 'Hello User')
126
- assert.equal(output.content[0], 'Welcome to @nan0web/ui')
125
+ assert.deepStrictEqual({ ...console.output()[0][1] }, { body: "Hello User", head: {} })
126
+ assert.equal(console.output()[1][1], 'Welcome to @nan0web/ui')
127
127
  })
128
128
 
129
129
  /**
@@ -249,30 +249,6 @@ function testRender() {
249
249
  assert.ok(renderedVisible.includes("Frame content"))
250
250
  })
251
251
 
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
252
  /**
277
253
  * @docs
278
254
  * ### Models
package/src/StdIn.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import EventProcessor from "@nan0web/event/oop"
2
- import { CommandMessage } from "@nan0web/co"
3
2
  import { typeOf } from "@nan0web/types"
4
3
  import InputMessage from "./core/Message/InputMessage.js"
5
4
 
@@ -91,8 +90,7 @@ class StdIn extends EventProcessor {
91
90
  decode(message) {
92
91
  if (message instanceof InputMessage) return message
93
92
  if (Array.isArray(message) && message.every(typeOf(String))) {
94
- const parsed = CommandMessage.parse(message)
95
- return new InputMessage({ value: parsed })
93
+ return new InputMessage({ value: message })
96
94
  }
97
95
  return new InputMessage(message)
98
96
  }
@@ -0,0 +1,9 @@
1
+ import CancelError from "./CancelError.js"
2
+
3
+ export {
4
+ CancelError
5
+ }
6
+
7
+ export default {
8
+ CancelError
9
+ }
@@ -1,5 +1,6 @@
1
1
  import FormMessage from "./Message.js"
2
2
  import FormInput from "./Input.js"
3
+ import Message from "@nan0web/co"
3
4
 
4
5
  /**
5
6
  * Abstract form for data entry.
@@ -18,24 +19,24 @@ export default class UIForm extends FormMessage {
18
19
  /** @type {Object} */ schema = {}
19
20
 
20
21
  /* ------------------------------------------------------------------ */
21
- /* static validator registry */
22
+ /* static validation registry */
22
23
  /* ------------------------------------------------------------------ */
23
24
 
24
25
  /** @type {Object<string,Function>} */
25
- static _validators = {}
26
+ static _validations = {}
26
27
 
27
28
  /**
28
- * Register a custom validator that can be referenced by name in a schema.
29
+ * Register a custom validation that can be referenced by name in a schema.
29
30
  *
30
- * @param {string} name - Identifier used in schema.validator.
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 addValidator(name, fn) {
35
+ static addValidation(name, fn) {
35
36
  if (typeof name !== "string" || typeof fn !== "function") {
36
- throw new Error("Validator name must be a string and fn must be a function")
37
+ throw new Error("validation name must be a string and fn must be a function")
37
38
  }
38
- UIForm._validators[name] = fn
39
+ UIForm._validations[name] = fn
39
40
  }
40
41
 
41
42
  /**
@@ -208,15 +209,15 @@ export default class UIForm extends FormMessage {
208
209
  }
209
210
  }
210
211
 
211
- // Custom validator – can be a function or a string referencing a static validator
212
- if (schema.validator) {
212
+ // Custom validation – can be a function or a string referencing a static validation
213
+ if (schema.validation) {
213
214
  let result
214
- if (typeof schema.validator === 'function') {
215
- result = schema.validator(value)
216
- } else if (typeof schema.validator === 'string') {
217
- const fn = UIForm._validators[schema.validator]
215
+ if (typeof schema.validation === 'function') {
216
+ result = schema.validation(value)
217
+ } else if (typeof schema.validation === 'string') {
218
+ const fn = UIForm._validations[schema.validation]
218
219
  if (!fn) {
219
- throw new Error(`Validator "${schema.validator}" not registered`)
220
+ throw new Error(`validation "${schema.validation}" not registered`)
220
221
  }
221
222
  result = fn(value)
222
223
  }
@@ -236,9 +237,7 @@ export default class UIForm extends FormMessage {
236
237
  */
237
238
  toJSON() {
238
239
  return {
239
- id: this.id,
240
- type: this.type,
241
- time: this.time.toISOString(),
240
+ time: new Date(this.time).toISOString(),
242
241
  title: this.title,
243
242
  fields: this.fields.map(f => f.toJSON ? f.toJSON() : f),
244
243
  state: this.state,
@@ -252,6 +251,25 @@ export default class UIForm extends FormMessage {
252
251
  */
253
252
  static from(input) {
254
253
  if (input instanceof UIForm) return input
254
+ if (input instanceof Message) {
255
+ const Class = input.constructor
256
+ const fields = []
257
+ for (const [name, value] of Object.entries(input)) {
258
+ fields.push(new FormInput({
259
+ name,
260
+ label: Class[name]?.label ?? Class[`${name}Label`] ?? name,
261
+ type: Class[name]?.type ?? Class[`${name}Type`] ?? typeof value,
262
+ required: Class[name]?.required ?? Class[`${name}Required`] ?? false,
263
+ placeholder: Class[name]?.placeholder ?? Class[`${name}Placeholder`] ?? "",
264
+ defaultValue: Class[name]?.defaultValue ?? Class[`${name}Default`] ?? "",
265
+ validation: Class[name]?.validation ?? Class[`${name}Validation`] ?? (() => true),
266
+ }))
267
+ }
268
+ return new UIForm({
269
+ title: Class.name,
270
+ fields
271
+ })
272
+ }
255
273
  return new UIForm(input)
256
274
  }
257
275
 
@@ -280,7 +298,7 @@ export default class UIForm extends FormMessage {
280
298
  required: !!custom.required,
281
299
  placeholder: custom.placeholder ?? "",
282
300
  options: custom.options ?? [],
283
- validator: custom.validator ?? undefined,
301
+ validation: custom.validation ?? undefined,
284
302
  defaultValue: custom.defaultValue ?? "",
285
303
  })
286
304
  })
@@ -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} validator - Custom validation function.
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 {Array<string>} */ options = []
21
- /** @type {Function|null} */ validator = null
29
+ /** @type {InputOptions} */ options = []
30
+ /** @type {Function|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 {Array<string>} [props.options=[]] - Select options.
46
- * @param {Function} [props.validator=null] - Custom validator.
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
- validator = this.validator,
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.validator = validator
80
+ this.validation = validation
72
81
  this.defaultValue = defaultValue
73
82
 
74
83
  this.requireValidType()
@@ -1,22 +1,19 @@
1
- import OutputMessage from "../Message/OutputMessage.js"
1
+ import InputMessage from "../Message/InputMessage.js"
2
2
 
3
3
  /**
4
- * FormMessage – specialized OutputMessage for forms.
4
+ * FormMessage – specialized InputMessage for forms.
5
5
  *
6
6
  * @class FormMessage
7
- * @extends OutputMessage
7
+ * @extends InputMessage
8
8
  */
9
- export default class FormMessage extends OutputMessage {
9
+ export default class FormMessage extends InputMessage {
10
10
  /**
11
11
  * Creates a FormMessage.
12
12
  *
13
13
  * @param {Object} [input={}] - Message properties.
14
14
  */
15
15
  constructor(input = {}) {
16
- super({
17
- ...input,
18
- type: OutputMessage.TYPES.FORM,
19
- })
16
+ super(input)
20
17
  const {
21
18
  data = {},
22
19
  schema = {},
@@ -83,4 +80,4 @@ export default class FormMessage extends OutputMessage {
83
80
 
84
81
  return { isValid: Object.keys(errors).length === 0, errors }
85
82
  }
86
- }
83
+ }
@@ -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
  *