@nan0web/ui 1.10.0 → 1.11.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 (101) hide show
  1. package/README.md +69 -3
  2. package/package.json +61 -29
  3. package/src/App/Command/DepsCommand.js +3 -4
  4. package/src/Frame/Props.js +12 -18
  5. package/src/InterfaceTemplate/InterfaceTemplate.js +9 -7
  6. package/src/Model/index.js +61 -6
  7. package/src/StdIn.js +2 -6
  8. package/src/cli.js +1 -0
  9. package/src/core/GeneratorRunner.js +67 -7
  10. package/src/core/InputAdapter.js +3 -1
  11. package/src/core/Intent.js +200 -17
  12. package/src/core/Message/Message.js +4 -7
  13. package/src/core/Message/OutputMessage.js +4 -9
  14. package/src/core/StreamEntry.js +20 -28
  15. package/src/core/index.js +1 -0
  16. package/src/domain/Content.js +196 -0
  17. package/src/domain/Document.js +17 -0
  18. package/src/domain/FooterModel.js +37 -19
  19. package/src/domain/HeaderModel.js +47 -21
  20. package/src/domain/HeroModel.js +24 -22
  21. package/src/domain/LayoutModel.js +43 -0
  22. package/src/domain/ModelAsApp.js +46 -0
  23. package/src/domain/SandboxModel.js +19 -16
  24. package/src/domain/app/GalleryCommand.js +53 -0
  25. package/src/domain/app/GalleryRenderIntent.js +77 -0
  26. package/src/domain/app/SnapshotAuditor.js +401 -0
  27. package/src/domain/app/SnapshotRunner.js +264 -0
  28. package/src/domain/app/UIApp.js +78 -0
  29. package/src/domain/components/BreadcrumbModel.js +10 -6
  30. package/src/domain/components/FeatureGridModel.js +62 -0
  31. package/src/domain/components/MarkdownModel.js +24 -0
  32. package/src/domain/components/ShellModel.js +243 -0
  33. package/src/domain/components/TableModel.js +10 -6
  34. package/src/domain/components/ToastModel.js +10 -6
  35. package/src/domain/components/index.js +3 -1
  36. package/src/domain/index.js +14 -4
  37. package/src/index.js +21 -2
  38. package/src/inspect.js +2 -0
  39. package/src/test/ScenarioAdapter.js +59 -0
  40. package/src/test/ScenarioTest.js +51 -0
  41. package/src/test/ScenarioTest.story.js +56 -0
  42. package/src/testing/CrashReporter.js +56 -0
  43. package/src/testing/GalleryGenerator.js +15 -71
  44. package/src/testing/LogicInspector.js +3 -3
  45. package/src/testing/SnapshotRunner.js +22 -0
  46. package/src/testing/SpecAdapter.js +115 -0
  47. package/src/testing/SpecRunner.js +121 -0
  48. package/src/testing/VisualAdapter.js +24 -19
  49. package/src/testing/index.js +5 -1
  50. package/src/testing/verifySnapshot.js +17 -0
  51. package/types/App/Command/DepsCommand.d.ts +0 -2
  52. package/types/Model/index.d.ts +56 -62
  53. package/types/StdIn.d.ts +3 -3
  54. package/types/cli.d.ts +1 -0
  55. package/types/core/GeneratorRunner.d.ts +14 -1
  56. package/types/core/InputAdapter.d.ts +2 -1
  57. package/types/core/Intent.d.ts +209 -31
  58. package/types/core/Message/Message.d.ts +2 -2
  59. package/types/core/Message/OutputMessage.d.ts +0 -2
  60. package/types/core/index.d.ts +1 -0
  61. package/types/domain/Content.d.ts +340 -0
  62. package/types/domain/Document.d.ts +21 -0
  63. package/types/domain/FooterModel.d.ts +22 -12
  64. package/types/domain/HeaderModel.d.ts +36 -13
  65. package/types/domain/HeroModel.d.ts +19 -17
  66. package/types/domain/LayoutModel.d.ts +34 -0
  67. package/types/domain/ModelAsApp.d.ts +23 -0
  68. package/types/domain/SandboxModel.d.ts +10 -0
  69. package/types/domain/app/GalleryCommand.d.ts +55 -0
  70. package/types/domain/app/GalleryRenderIntent.d.ts +31 -0
  71. package/types/domain/app/SnapshotAuditor.d.ts +99 -0
  72. package/types/domain/app/SnapshotRunner.d.ts +45 -0
  73. package/types/domain/app/UIApp.d.ts +60 -0
  74. package/types/domain/components/BreadcrumbModel.d.ts +6 -8
  75. package/types/domain/components/FeatureGridModel.d.ts +50 -0
  76. package/types/domain/components/MarkdownModel.d.ts +19 -0
  77. package/types/domain/components/ShellModel.d.ts +56 -0
  78. package/types/domain/components/TableModel.d.ts +4 -0
  79. package/types/domain/components/ToastModel.d.ts +4 -0
  80. package/types/domain/components/index.d.ts +3 -0
  81. package/types/domain/index.d.ts +10 -4
  82. package/types/index.d.ts +19 -1
  83. package/types/inspect.d.ts +2 -0
  84. package/types/test/ScenarioAdapter.d.ts +43 -0
  85. package/types/test/ScenarioTest.d.ts +24 -0
  86. package/types/test/ScenarioTest.story.d.ts +1 -0
  87. package/types/testing/CrashReporter.d.ts +13 -0
  88. package/types/testing/SnapshotRunner.d.ts +7 -0
  89. package/types/testing/SpecAdapter.d.ts +57 -0
  90. package/types/testing/SpecRunner.d.ts +41 -0
  91. package/types/testing/VisualAdapter.d.ts +0 -6
  92. package/types/testing/index.d.ts +5 -1
  93. package/types/testing/verifySnapshot.d.ts +14 -0
  94. package/src/testing/SnapshotInspector.js +0 -84
  95. package/types/App/Command/Options.d.ts +0 -43
  96. package/types/App/Command/index.d.ts +0 -8
  97. package/types/App/User/Command/Options.d.ts +0 -34
  98. package/types/core/Message/InputMessage.d.ts +0 -71
  99. package/types/domain/components/HeroModel.d.ts +0 -24
  100. package/types/domain/components/ShowcaseAppModel.d.ts +0 -32
  101. package/types/testing/SnapshotInspector.d.ts +0 -17
@@ -38,17 +38,32 @@ import { IntentErrorModel } from './IntentErrorModel.js'
38
38
  * @typedef {Object} ProgressIntent
39
39
  * @property {'progress'} type
40
40
  * @property {number} [value] - Progress value (0-1).
41
- * @property {string} [id] - Progress ID for tracking multiple parallel operations.
41
+ * @property {number} [total] - Absolute total (if value is absolute).
42
+ * @property {string} [id] - Progress ID for tracking by Adapter to calculate speed/eta.
42
43
  * @property {string} message - Status message from Model (i18n static field value).
43
44
  */
44
45
 
45
46
  /**
46
- * Model emits a log message. No response expected.
47
+ * @typedef {'info' | 'warn' | 'error' | 'success'} ShowLevel
48
+ */
49
+
50
+ /** @typedef {ShowLevel} LogLevel */
51
+
52
+ /**
53
+ * Model emits a show message. No response expected.
47
54
  * Message MUST come from the Model (i18n static field value).
55
+ * @typedef {Object} ShowIntent
56
+ * @property {'show'} type
57
+ * @property {ShowLevel} level
58
+ * @property {string} message - Show message from Model (i18n static field value).
59
+ */
60
+
61
+ /**
62
+ * Model emits a log message intended for debugging/developer (Not UI).
48
63
  * @typedef {Object} LogIntent
49
64
  * @property {'log'} type
50
- * @property {'info' | 'warn' | 'error' | 'success'} level
51
- * @property {string} message - Log message from Model (i18n static field value).
65
+ * @property {LogLevel} level
66
+ * @property {string} message - Internal log message.
52
67
  */
53
68
 
54
69
  /**
@@ -67,9 +82,33 @@ import { IntentErrorModel } from './IntentErrorModel.js'
67
82
  * @property {object} props - Static props for the component.
68
83
  */
69
84
 
85
+ /**
86
+ * Contextual data and attachments for the AI subagent.
87
+ * @typedef {Object} AgentContext
88
+ * @property {string[]} [instructions] - List of instructions or guidelines (e.g. ['Use 1-char emojis only']).
89
+ * @property {Record<string, string>} [files] - Hash map of file paths to their string contents.
90
+ * @property {Record<string, any>} [data] - Any arbitrary JSON data (e.g. parsed errors, ASTs, metadata) useful for the task.
91
+ */
92
+
93
+ /**
94
+ * Model delegates a task to an AI subagent. The Adapter should launch the agent
95
+ * with the provided task and context, and return the result. If the agent is skipped,
96
+ * it returns { success: false } but allows user to generate a prompt.
97
+ * @typedef {Object} AgentIntent
98
+ * @property {'agent'} type
99
+ * @property {string} task - The instructional task for the AI agent.
100
+ * @property {AgentContext} context - Contextual data, files, and instructions for the task.
101
+ * @property {() => string} toPrompt - Helper to format task and context as an LLM prompt.
102
+ */
103
+
70
104
  /**
71
105
  * Union of all possible yielded intents.
72
- * @typedef {AskIntent | ProgressIntent | LogIntent | RenderIntent} Intent
106
+ * @typedef {(AskIntent | ProgressIntent | LogIntent | ShowIntent | RenderIntent | AgentIntent | ResultIntent) & {
107
+ * $value?: any;
108
+ * $success?: boolean;
109
+ * $files?: Record<string, string>;
110
+ * $message?: string;
111
+ * }} Intent
73
112
  */
74
113
 
75
114
  // ─── Response Types (Adapter → Model) ───
@@ -82,6 +121,21 @@ import { IntentErrorModel } from './IntentErrorModel.js'
82
121
  * @property {boolean} [cancelled] - Whether the user cancelled this interaction (e.g. pressed ESC).
83
122
  */
84
123
 
124
+ /**
125
+ * Response to an AgentIntent.
126
+ * The underlying Adapter (Orchestrator) is responsible for communicating with the LLM,
127
+ * enforcing output formats (e.g. Unified Diff or Tool Calls like `updateFile`),
128
+ * and resolving common LLM hallucinations (like Grok truncating code with `// ...`).
129
+ *
130
+ * The Model (e.g. IconsAuditor) receives this clean, resolved response and does not
131
+ * need to parse Markdown or interpret diffs itself.
132
+ *
133
+ * @typedef {Object} AgentResponse
134
+ * @property {boolean} success - True if the agent successfully processed the task.
135
+ * @property {Record<string, string>} [files] - Hash map of fully resolved, updated file contents.
136
+ * @property {string} [message] - Optional summary or explanation returned by the AI.
137
+ */
138
+
85
139
  // ─── Abort Support ───
86
140
 
87
141
  /**
@@ -101,10 +155,14 @@ import { IntentErrorModel } from './IntentErrorModel.js'
101
155
 
102
156
  /**
103
157
  * Union of all possible responses an Adapter can send back via iterator.next().
104
- * @typedef {AskResponse | AbortResponse | undefined} IntentResponse
158
+ * @typedef {AskResponse | AgentResponse | AbortResponse | undefined} IntentResponse
159
+ */
160
+
161
+ /**
162
+ * @typedef {'ask' | 'show' | 'progress' | 'render' | 'agent'} IntentType
105
163
  */
106
164
 
107
- export const INTENT_TYPES = /** @type {const} */ (['ask', 'progress', 'log', 'render'])
165
+ export const INTENT_TYPES = /** @type {const} */ (['ask', 'progress', 'show', 'log', 'render', 'agent'])
108
166
 
109
167
  /**
110
168
  * Detects if a value is a Model-as-Schema class (has static fields with `help`).
@@ -113,7 +171,7 @@ export const INTENT_TYPES = /** @type {const} */ (['ask', 'progress', 'log', 're
113
171
  */
114
172
  export function isModelSchema(schema) {
115
173
  if (typeof schema !== 'function') return false
116
- return Object.keys(schema).some((key) => {
174
+ return Object.getOwnPropertyNames(schema).some((key) => {
117
175
  const meta = schema[key]
118
176
  return meta && typeof meta === 'object' && 'help' in meta
119
177
  })
@@ -142,13 +200,16 @@ export function validateIntent(intent) {
142
200
  }
143
201
  // Accept both: plain schema {help: '...'} and Model-as-Schema class/instance
144
202
  const isModel = !!intent.model
145
- if (!isModel && (!intent.schema || typeof intent.schema !== 'object' || !('help' in intent.schema))) {
203
+ if (
204
+ !isModel &&
205
+ (!intent.schema || typeof intent.schema !== 'object' || !('help' in intent.schema))
206
+ ) {
146
207
  throw IntentErrorModel.error('ask_missing_schema_help')
147
208
  }
148
209
  }
149
- if (intent.type === 'progress' || intent.type === 'log') {
150
- const isComponentLog = intent.type === 'log' && intent.component
151
- if (!isComponentLog && typeof intent.message !== 'string') {
210
+ if (intent.type === 'progress' || intent.type === 'show' || intent.type === 'log') {
211
+ const isComponentShow = (intent.type === 'show' || intent.type === 'log') && intent.component
212
+ if (!isComponentShow && typeof intent.message !== 'string') {
152
213
  throw IntentErrorModel.error('intent_missing_message', { type: intent.type })
153
214
  }
154
215
  }
@@ -157,6 +218,11 @@ export function validateIntent(intent) {
157
218
  throw IntentErrorModel.error('render_missing_component')
158
219
  }
159
220
  }
221
+ if (intent.type === 'agent') {
222
+ if (typeof intent.task !== 'string' || !intent.task) {
223
+ throw IntentErrorModel.error('agent_missing_task')
224
+ }
225
+ }
160
226
  return true
161
227
  }
162
228
 
@@ -171,14 +237,131 @@ export function validateIntent(intent) {
171
237
  * @param {object | Function} schema - Field descriptor or Model-as-Schema class.
172
238
  * @returns {AskIntent}
173
239
  */
174
- export const ask = (field, schema) => {
240
+ export function ask(field, schema) {
175
241
  if (isModelSchema(schema)) {
176
242
  return { type: 'ask', field, schema, model: true }
177
243
  }
178
244
  return { type: 'ask', field, schema }
179
245
  }
180
246
 
181
- export const progress = (message) => ({ type: 'progress', message })
182
- export const log = (level, message, data = {}) => ({ type: 'log', level, message, ...data })
183
- export const render = (component, props = {}) => ({ type: 'render', component, props })
184
- export const result = (data) => ({ type: 'result', data })
247
+ /**
248
+ * Create a progress intent.
249
+ * @param {string} message - Status message from Model (i18n static field value).
250
+ * @param {number} [value=0] - Progress value (current step or percentage).
251
+ * @param {number|string} [totalOrId] - Absolute total steps (number) OR progress tracking ID (string).
252
+ * @param {string} [id='default'] - Progress ID (if total is provided).
253
+ * @returns {ProgressIntent}
254
+ */
255
+ export function progress(message, value = 0, totalOrId, id) {
256
+ let total = undefined
257
+ let progressId = 'default'
258
+
259
+ if (typeof totalOrId === 'number') {
260
+ total = totalOrId
261
+ if (typeof id === 'string') progressId = id
262
+ } else if (typeof totalOrId === 'string') {
263
+ progressId = totalOrId
264
+ }
265
+
266
+ return { type: 'progress', message, value, total, id: progressId }
267
+ }
268
+
269
+ export function log(level, message, data = {}) {
270
+ return { type: 'log', level, message, ...data }
271
+ }
272
+
273
+ /**
274
+ * Create a render intent.
275
+ * @param {string} component - Component name (e.g. 'App.Layout.Header').
276
+ * @param {object} [props] - Static props for the component.
277
+ * @returns {RenderIntent}
278
+ */
279
+ export function render(component, props = {}) {
280
+ return { type: 'render', component, props }
281
+ }
282
+
283
+ /**
284
+ * Create a result intent.
285
+ * @param {*} data - The raw result data.
286
+ * @returns {ResultIntent}
287
+ */
288
+ export function result(data) {
289
+ return { type: 'result', data }
290
+ }
291
+
292
+ /**
293
+ * @typedef {Object} ShowData
294
+ * @property {any} [component]
295
+ * @property {import('@nan0web/types').Model} [model]
296
+ */
297
+ /**
298
+ * Create a show intent.
299
+ * @param {string | any} message Message to display.
300
+ * @param {ShowLevel|ShowData} [level='info'] Level of message or additional data then `level = 'info'`.
301
+ * @param {ShowData} [data={}] Additional data to display.
302
+ * @returns {ShowIntent}
303
+ */
304
+ export function show(message, level = 'info', data = {}) {
305
+ if ('string' === typeof level) {
306
+ return { type: 'show', level, message, ...data }
307
+ }
308
+ return { type: 'show', level: 'info', message, ...level, ...data }
309
+ }
310
+
311
+ /**
312
+ * Create an agent intent to delegate a task to an AI subagent.
313
+ * @param {string} task - The instructional task for the AI agent.
314
+ * @param {AgentContext} [context={}] - Contextual data (files, errors, docs).
315
+ * @returns {AgentIntent}
316
+ */
317
+ export function agent(task, context = {}) {
318
+ return {
319
+ type: 'agent',
320
+ task,
321
+ context,
322
+ toPrompt() {
323
+ let ctxStr = ''
324
+ if (this.context.data) {
325
+ try {
326
+ ctxStr = JSON.stringify(this.context.data, null, 2)
327
+ } catch (e) {
328
+ ctxStr = String(this.context.data)
329
+ }
330
+ }
331
+
332
+ // Format input files for the LLM using the boundary concept
333
+ const filesStr = Object.entries(this.context.files || {})
334
+ .map(([path, content]) => `---boundary:${path}---\n${content}\n---boundary---`)
335
+ .join('\n\n')
336
+
337
+ const outputRules = `
338
+ [Output Format Rules]
339
+ You must return your code modifications using the following strictly parsable boundary format. Do NOT use markdown code blocks (\`\`\`) or JSON for your code outputs.
340
+
341
+ To replace an ENTIRE file:
342
+ ---boundary:path/to/file.js---
343
+ <full new file content here>
344
+ ---boundary---
345
+
346
+ To replace a SPECIFIC SNIPPET (e.g. replacing 3 lines starting at line 33):
347
+ ---boundary:path/to/file.js:33:3---
348
+ <new snippet content here>
349
+ ---boundary---
350
+ `
351
+ const inst = Array.isArray(this.context.instructions)
352
+ ? this.context.instructions.join('\n')
353
+ : this.context.instructions
354
+
355
+ return [
356
+ `[Subagent Task]`,
357
+ this.task,
358
+ inst ? `\n[Instructions]\n${inst}` : '',
359
+ ctxStr ? `\n[Context]\n${ctxStr}` : '',
360
+ filesStr ? `\n[Files]\n${filesStr}` : '',
361
+ outputRules,
362
+ ]
363
+ .filter(Boolean)
364
+ .join('\n')
365
+ },
366
+ }
367
+ }
@@ -43,11 +43,6 @@ export default class UiMessage extends Message {
43
43
  NAVIGATION: 'navigation',
44
44
  }
45
45
 
46
- /** @type {string} */
47
- type = ''
48
- /** @type {string} */
49
- id = ''
50
-
51
46
  /**
52
47
  * Creates a UiMessage.
53
48
  *
@@ -56,9 +51,11 @@ export default class UiMessage extends Message {
56
51
  constructor(input = {}) {
57
52
  super(input)
58
53
 
59
- const { type = this.type, id = this.id } = input
54
+ const { type, id } = input
55
+ /** @type {string} */
60
56
  this.id = id || `ui-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
61
- this.type = String(type)
57
+ /** @type {string} */
58
+ this.type = type ? String(type) : ''
62
59
 
63
60
  if (!('body' in input) && 'content' in input) {
64
61
  this.body = Array.isArray(input.content) ? input.content : [input.content]
@@ -14,15 +14,6 @@ export default class OutputMessage extends UiMessage {
14
14
  CRITICAL: 3,
15
15
  }
16
16
 
17
- /** @type {string[]} */
18
- body
19
- /** @type {Object} */
20
- meta = {}
21
- /** @type {Error|null} */
22
- error = null
23
- /** @type {number} */
24
- priority = OutputMessage.PRIORITY.NORMAL
25
-
26
17
  /**
27
18
  * Creates an OutputMessage.
28
19
  *
@@ -41,14 +32,18 @@ export default class OutputMessage extends UiMessage {
41
32
 
42
33
  const contentSource = 'body' in input ? body : 'content' in input ? content : []
43
34
 
35
+ /** @type {string[]} */
44
36
  this.body = Array.isArray(contentSource)
45
37
  ? contentSource
46
38
  : contentSource
47
39
  ? [String(contentSource)]
48
40
  : []
49
41
 
42
+ /** @type {Object} */
50
43
  this.meta = meta
44
+ /** @type {Error|null} */
51
45
  this.error = error instanceof Error ? error : error ? new Error(String(error)) : null
46
+ /** @type {number} */
52
47
  this.priority = Number(priority)
53
48
 
54
49
  if (!this.type && this.error) {
@@ -2,30 +2,6 @@
2
2
  * Represents an entry in a stream with value, completion status, cancellation status, and error message.
3
3
  */
4
4
  export default class StreamEntry {
5
- /**
6
- * The value of the stream entry.
7
- * @type {any}
8
- */
9
- value = undefined
10
-
11
- /**
12
- * Indicates if the stream entry is done (completed).
13
- * @type {boolean}
14
- */
15
- done = false
16
-
17
- /**
18
- * Indicates if the stream entry has been cancelled.
19
- * @type {boolean}
20
- */
21
- cancelled = false
22
-
23
- /**
24
- * Error message associated with the stream entry.
25
- * @type {string}
26
- */
27
- error = ''
28
-
29
5
  /**
30
6
  * Creates a new StreamEntry instance.
31
7
  * @param {Object} [input={}] - Input object to initialize the stream entry.
@@ -36,14 +12,30 @@ export default class StreamEntry {
36
12
  */
37
13
  constructor(input = {}) {
38
14
  const {
39
- value = this.value,
40
- done = this.done,
41
- cancelled = this.cancelled,
42
- error = this.error,
15
+ value = undefined,
16
+ done = false,
17
+ cancelled = false,
18
+ error = '',
43
19
  } = input
20
+ /**
21
+ * The value of the stream entry.
22
+ * @type {any}
23
+ */
44
24
  this.value = value
25
+ /**
26
+ * Indicates if the stream entry is done (completed).
27
+ * @type {boolean}
28
+ */
45
29
  this.done = Boolean(done)
30
+ /**
31
+ * Indicates if the stream entry has been cancelled.
32
+ * @type {boolean}
33
+ */
46
34
  this.cancelled = Boolean(cancelled)
35
+ /**
36
+ * Error message associated with the stream entry.
37
+ * @type {string}
38
+ */
47
39
  this.error = String(error)
48
40
  }
49
41
 
package/src/core/index.js CHANGED
@@ -46,3 +46,4 @@ export { IntentErrorModel } from './IntentErrorModel.js'
46
46
  export { runGenerator } from './GeneratorRunner.js'
47
47
 
48
48
  export { MaskHandler } from './MaskHandler.js'
49
+ export { LayoutModel } from '../domain/LayoutModel.js'
@@ -0,0 +1,196 @@
1
+ import { Model } from '@nan0web/types'
2
+
3
+ /**
4
+ * @typedef {Object} HTML5Elements
5
+ * @property {string|ContentData[]} [a]
6
+ * @property {string|ContentData[]} [abbr]
7
+ * @property {string|ContentData[]} [address]
8
+ * @property {string|ContentData[]} [area]
9
+ * @property {string|ContentData[]} [article]
10
+ * @property {string|ContentData[]} [aside]
11
+ * @property {string|ContentData[]} [audio]
12
+ * @property {string|ContentData[]} [b]
13
+ * @property {string|ContentData[]} [base]
14
+ * @property {string|ContentData[]} [bdi]
15
+ * @property {string|ContentData[]} [bdo]
16
+ * @property {string|ContentData[]} [blockquote]
17
+ * @property {string|ContentData[]} [body]
18
+ * @property {boolean|object} [br]
19
+ * @property {string|ContentData[]} [canvas]
20
+ * @property {string|ContentData[]} [caption]
21
+ * @property {string|ContentData[]} [cite]
22
+ * @property {string|ContentData[]} [code]
23
+ * @property {string|ContentData[]} [col]
24
+ * @property {string|ContentData[]} [colgroup]
25
+ * @property {string|ContentData[]} [data]
26
+ * @property {string|ContentData[]} [datalist]
27
+ * @property {string|ContentData[]} [dd]
28
+ * @property {string|ContentData[]} [del]
29
+ * @property {string|ContentData[]} [details]
30
+ * @property {string|ContentData[]} [dfn]
31
+ * @property {string|ContentData[]} [dialog]
32
+ * @property {string|ContentData[]} [div]
33
+ * @property {string|ContentData[]} [dl]
34
+ * @property {string|ContentData[]} [dt]
35
+ * @property {string|ContentData[]} [em]
36
+ * @property {string|ContentData[]} [embed]
37
+ * @property {string|ContentData[]} [fieldset]
38
+ * @property {string|ContentData[]} [figcaption]
39
+ * @property {string|ContentData[]} [figure]
40
+ * @property {string|ContentData[]} [footer]
41
+ * @property {string|ContentData[]} [form]
42
+ * @property {string|ContentData[]} [h1]
43
+ * @property {string|ContentData[]} [h2]
44
+ * @property {string|ContentData[]} [h3]
45
+ * @property {string|ContentData[]} [h4]
46
+ * @property {string|ContentData[]} [h5]
47
+ * @property {string|ContentData[]} [h6]
48
+ * @property {string|ContentData[]} [head]
49
+ * @property {string|ContentData[]} [header]
50
+ * @property {string|ContentData[]} [hgroup]
51
+ * @property {boolean|object} [hr]
52
+ * @property {string|ContentData[]} [html]
53
+ * @property {string|ContentData[]} [i]
54
+ * @property {string|ContentData[]} [iframe]
55
+ * @property {string|ContentData[]} [img]
56
+ * @property {string|ContentData[]} [ins]
57
+ * @property {string|ContentData[]} [kbd]
58
+ * @property {string|ContentData[]} [label]
59
+ * @property {string|ContentData[]} [legend]
60
+ * @property {string|ContentData[]} [li]
61
+ * @property {string|ContentData[]} [link]
62
+ * @property {string|ContentData[]} [main]
63
+ * @property {string|ContentData[]} [map]
64
+ * @property {string|ContentData[]} [mark]
65
+ * @property {string|ContentData[]} [meta]
66
+ * @property {string|ContentData[]} [meter]
67
+ * @property {boolean|any} [input]
68
+ * @property {boolean|any} [button]
69
+ * @property {boolean|any} [select]
70
+ * @property {string|ContentData[]} [nav]
71
+ * @property {string|ContentData[]} [noscript]
72
+ * @property {string|ContentData[]} [object]
73
+ * @property {string|ContentData[]} [ol]
74
+ * @property {string|ContentData[]} [optgroup]
75
+ * @property {string|ContentData[]} [option]
76
+ * @property {string|ContentData[]} [output]
77
+ * @property {string|ContentData[]} [p]
78
+ * @property {string|ContentData[]} [picture]
79
+ * @property {string|ContentData[]} [pre]
80
+ * @property {string|ContentData[]} [progress]
81
+ * @property {string|ContentData[]} [q]
82
+ * @property {string|ContentData[]} [rp]
83
+ * @property {string|ContentData[]} [rt]
84
+ * @property {string|ContentData[]} [ruby]
85
+ * @property {string|ContentData[]} [s]
86
+ * @property {string|ContentData[]} [samp]
87
+ * @property {string|ContentData[]} [script]
88
+ * @property {string|ContentData[]} [section]
89
+ * @property {string|ContentData[]} [slot]
90
+ * @property {string|ContentData[]} [small]
91
+ * @property {string|ContentData[]} [source]
92
+ * @property {string|ContentData[]} [span]
93
+ * @property {string|ContentData[]} [strong]
94
+ * @property {string|ContentData[]} [style]
95
+ * @property {string|ContentData[]} [sub]
96
+ * @property {string|ContentData[]} [summary]
97
+ * @property {string|ContentData[]} [sup]
98
+ * @property {string|ContentData[]} [table]
99
+ * @property {string|ContentData[]} [tbody]
100
+ * @property {string|ContentData[]} [td]
101
+ * @property {string|ContentData[]} [template]
102
+ * @property {string|ContentData[]} [textarea]
103
+ * @property {string|ContentData[]} [tfoot]
104
+ * @property {string|ContentData[]} [th]
105
+ * @property {string|ContentData[]} [thead]
106
+ * @property {string|ContentData[]} [time]
107
+ * @property {string|ContentData[]} [title]
108
+ * @property {string|ContentData[]} [tr]
109
+ * @property {string|ContentData[]} [track]
110
+ * @property {string|ContentData[]} [u]
111
+ * @property {string|ContentData[]} [ul]
112
+ * @property {string|ContentData[]} [var]
113
+ * @property {string|ContentData[]} [video]
114
+ * @property {string|ContentData[]} [wbr]
115
+ * @property {string|ContentData[]} [svg]
116
+ * @property {string|ContentData[]} [path]
117
+ * @property {string|ContentData[]} [circle]
118
+ * @property {string|ContentData[]} [rect]
119
+ * @property {string|ContentData[]} [line]
120
+ * @property {string|ContentData[]} [polyline]
121
+ * @property {string|ContentData[]} [polygon]
122
+ * @property {string|ContentData[]} [g]
123
+ * @property {string|ContentData[]} [defs]
124
+ * @property {string|ContentData[]} [symbol]
125
+ * @property {string|ContentData[]} [use]
126
+ * @property {string|ContentData[]} [text]
127
+ */
128
+
129
+ /**
130
+ * @typedef {Object} CoreUIElements
131
+ * @property {import('./components/AccordionModel.js').AccordionModel} [accordion]
132
+ * @property {import('./components/AutocompleteModel.js').AutocompleteModel} [autocomplete]
133
+ * @property {import('./components/BannerModel.js').BannerModel} [banner]
134
+ * @property {import('./components/BreadcrumbModel.js').BreadcrumbModel} [breadcrumb]
135
+ * @property {import('./components/ButtonModel.js').ButtonModel} [button]
136
+ * @property {import('./components/CommentModel.js').CommentModel} [comment]
137
+ * @property {import('./components/ConfirmModel.js').ConfirmModel} [confirm]
138
+ * @property {import('./components/EmptyStateModel.js').EmptyStateModel} [emptyState]
139
+ * @property {import('./components/FAQModel.js').FAQModel} [faq]
140
+ * @property {import('./components/FeatureGridModel.js').FeatureGridModel} [featureGrid]
141
+ * @property {import('./components/GalleryModel.js').GalleryModel} [gallery]
142
+ * @property {import('./components/InputModel.js').InputModel} [input]
143
+ * @property {import('./components/MarkdownModel.js').MarkdownModel} [markdown]
144
+ * @property {import('./components/PriceModel.js').PriceModel} [price]
145
+ * @property {import('./components/PricingModel.js').PricingModel} [pricing]
146
+ * @property {import('./components/PricingSectionModel.js').PricingSectionModel} [pricingSection]
147
+ * @property {import('./components/ProfileDropdownModel.js').ProfileDropdownModel} [profileDropdown]
148
+ * @property {import('./components/SelectModel.js').SelectModel} [select]
149
+ * @property {import('./components/ShellModel.js').ShellModel} [shell]
150
+ * @property {import('./components/SpinnerModel.js').SpinnerModel} [spinner]
151
+ * @property {import('./components/StatsItemModel.js').StatsItemModel} [statsItem]
152
+ * @property {import('./components/StatsModel.js').StatsModel} [stats]
153
+ * @property {import('./components/TableModel.js').TableModel} [tableUI]
154
+ * @property {import('./components/TabsModel.js').TabsModel} [tabs]
155
+ * @property {import('./components/TestimonialModel.js').TestimonialModel} [testimonial]
156
+ * @property {import('./components/TimelineItemModel.js').TimelineItemModel} [timelineItem]
157
+ * @property {import('./components/TimelineModel.js').TimelineModel} [timeline]
158
+ * @property {import('./components/ToastModel.js').ToastModel} [toast]
159
+ * @property {import('./components/TreeModel.js').TreeModel} [tree]
160
+ * @property {ContentData[]} [sortable] - Інтерактивний Drag-n-Drop контейнер
161
+ */
162
+
163
+ /**
164
+ * @typedef {Partial<Content & HTML5Elements & CoreUIElements> & Record<string, any>} ContentData
165
+ */
166
+
167
+ export class Content extends Model {
168
+ static content = { type: 'string', help: 'Content' }
169
+ static children = { type: 'array', model: Content, help: 'Children' }
170
+
171
+ /**
172
+ * @param {ContentData | string} [data={}]
173
+ * @param {import('@nan0web/types').ModelOptions} [options={}]
174
+ */
175
+ constructor(data = {}, options = {}) {
176
+ if ('string' === typeof data) {
177
+ data = { content: data }
178
+ }
179
+ super(data, options)
180
+
181
+ // ── Base Fields ──
182
+ /** @type {string|undefined} Content */ this.content
183
+ /** @type {Array<Content>|undefined} Children */ this.children
184
+
185
+ // ── Hydration ──
186
+ if (Array.isArray(this.children)) {
187
+ this.children = this.children.map((child) => new Content(child, options))
188
+ }
189
+
190
+ const { content, children, ...rest } = /** @type {any} */ (data)
191
+
192
+ for (const [key, value] of Object.entries(rest)) {
193
+ /** @type {any} */ (this)[key] = value
194
+ }
195
+ }
196
+ }
@@ -0,0 +1,17 @@
1
+ import { Model } from '@nan0web/types'
2
+ import { Content } from './Content.js'
3
+
4
+ export class Document extends Model {
5
+ static title = { type: 'string', help: 'Title' }
6
+ static content = { type: 'array', model: Content, help: 'Content' }
7
+ /**
8
+ *
9
+ * @param {Partial<Document>} [data]
10
+ * @param {import('@nan0web/types').ModelOptions} [options]
11
+ */
12
+ constructor(data = {}, options = {}) {
13
+ super(data, options)
14
+ /** @type {string} Title */ this.title
15
+ /** @type {Array<Content>} Content */ this.content
16
+ }
17
+ }