@nan0web/ui 1.8.0 → 1.10.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 (107) hide show
  1. package/README.md +29 -10
  2. package/package.json +18 -22
  3. package/src/Model/index.js +32 -3
  4. package/src/core/Form/Form.js +8 -7
  5. package/src/core/Form/Message.js +1 -1
  6. package/src/core/GeneratorRunner.js +10 -0
  7. package/src/core/Intent.js +21 -5
  8. package/src/core/IntentErrorModel.js +6 -1
  9. package/src/core/Stream.js +16 -5
  10. package/src/core/index.js +1 -1
  11. package/src/domain/FooterModel.js +57 -0
  12. package/src/domain/HeaderModel.js +50 -0
  13. package/src/domain/HeroModel.js +48 -0
  14. package/src/domain/Navigation.js +11 -10
  15. package/src/domain/SandboxModel.js +66 -115
  16. package/src/domain/ShowcaseAppModel.js +133 -50
  17. package/src/domain/components/AccordionModel.js +38 -0
  18. package/src/domain/components/AutocompleteModel.js +11 -21
  19. package/src/domain/components/BannerModel.js +37 -0
  20. package/src/domain/components/BreadcrumbModel.js +11 -9
  21. package/src/domain/components/ButtonModel.js +31 -58
  22. package/src/domain/components/CommentModel.js +44 -0
  23. package/src/domain/components/ConfirmModel.js +26 -33
  24. package/src/domain/components/EmptyStateModel.js +45 -0
  25. package/src/domain/components/FAQModel.js +32 -0
  26. package/src/domain/components/FooterConfigModel.js +26 -0
  27. package/src/domain/components/FooterVisibilityModel.js +48 -0
  28. package/src/domain/components/GalleryModel.js +36 -0
  29. package/src/domain/components/HeaderConfigModel.js +26 -0
  30. package/src/domain/components/HeaderVisibilityModel.js +54 -0
  31. package/src/domain/components/InputModel.js +21 -41
  32. package/src/domain/components/PriceModel.js +30 -0
  33. package/src/domain/components/PricingModel.js +39 -0
  34. package/src/domain/components/PricingSectionModel.js +32 -0
  35. package/src/domain/components/ProfileDropdownModel.js +45 -0
  36. package/src/domain/components/SelectModel.js +11 -21
  37. package/src/domain/components/SpinnerModel.js +11 -26
  38. package/src/domain/components/StatsItemModel.js +38 -0
  39. package/src/domain/components/StatsModel.js +32 -0
  40. package/src/domain/components/TableModel.js +11 -24
  41. package/src/domain/components/TabsModel.js +30 -0
  42. package/src/domain/components/TestimonialModel.js +24 -0
  43. package/src/domain/components/TimelineItemModel.js +38 -0
  44. package/src/domain/components/TimelineModel.js +32 -0
  45. package/src/domain/components/ToastModel.js +24 -51
  46. package/src/domain/components/TreeModel.js +10 -26
  47. package/src/domain/components/index.js +34 -0
  48. package/src/domain/index.js +24 -0
  49. package/src/index.js +2 -0
  50. package/src/testing/GalleryGenerator.js +85 -0
  51. package/src/testing/LogicInspector.js +55 -0
  52. package/src/testing/SnapshotInspector.js +84 -0
  53. package/src/testing/VisualAdapter.js +41 -0
  54. package/src/testing/index.js +3 -0
  55. package/types/Model/index.d.ts +62 -4
  56. package/types/core/Form/Form.d.ts +2 -2
  57. package/types/core/GeneratorRunner.d.ts +4 -0
  58. package/types/core/Intent.d.ts +31 -3
  59. package/types/core/IntentErrorModel.d.ts +4 -0
  60. package/types/core/index.d.ts +1 -1
  61. package/types/domain/FooterModel.d.ts +52 -0
  62. package/types/domain/HeaderModel.d.ts +45 -0
  63. package/types/domain/HeroModel.d.ts +43 -0
  64. package/types/domain/Navigation.d.ts +10 -9
  65. package/types/domain/SandboxModel.d.ts +16 -40
  66. package/types/domain/ShowcaseAppModel.d.ts +26 -54
  67. package/types/domain/components/AccordionModel.d.ts +33 -0
  68. package/types/domain/components/AutocompleteModel.d.ts +10 -29
  69. package/types/domain/components/BannerModel.d.ts +32 -0
  70. package/types/domain/components/BreadcrumbModel.d.ts +13 -6
  71. package/types/domain/components/ButtonModel.d.ts +18 -54
  72. package/types/domain/components/CommentModel.d.ts +39 -0
  73. package/types/domain/components/ConfirmModel.d.ts +20 -35
  74. package/types/domain/components/EmptyStateModel.d.ts +40 -0
  75. package/types/domain/components/FAQModel.d.ts +27 -0
  76. package/types/domain/components/FooterConfigModel.d.ts +21 -0
  77. package/types/domain/components/FooterVisibilityModel.d.ts +43 -0
  78. package/types/domain/components/GalleryModel.d.ts +35 -0
  79. package/types/domain/components/HeaderConfigModel.d.ts +21 -0
  80. package/types/domain/components/HeaderVisibilityModel.d.ts +49 -0
  81. package/types/domain/components/HeroModel.d.ts +24 -0
  82. package/types/domain/components/InputModel.d.ts +19 -59
  83. package/types/domain/components/PriceModel.d.ts +25 -0
  84. package/types/domain/components/PricingModel.d.ts +34 -0
  85. package/types/domain/components/PricingSectionModel.d.ts +27 -0
  86. package/types/domain/components/ProfileDropdownModel.d.ts +40 -0
  87. package/types/domain/components/SelectModel.d.ts +13 -28
  88. package/types/domain/components/ShowcaseAppModel.d.ts +32 -0
  89. package/types/domain/components/SpinnerModel.d.ts +10 -27
  90. package/types/domain/components/StatsItemModel.d.ts +33 -0
  91. package/types/domain/components/StatsModel.d.ts +27 -0
  92. package/types/domain/components/TableModel.d.ts +10 -26
  93. package/types/domain/components/TabsModel.d.ts +28 -0
  94. package/types/domain/components/TestimonialModel.d.ts +18 -0
  95. package/types/domain/components/TimelineItemModel.d.ts +33 -0
  96. package/types/domain/components/TimelineModel.d.ts +27 -0
  97. package/types/domain/components/ToastModel.d.ts +16 -45
  98. package/types/domain/components/TreeModel.d.ts +13 -36
  99. package/types/domain/components/index.d.ts +20 -0
  100. package/types/domain/index.d.ts +4 -1
  101. package/types/index.d.ts +1 -0
  102. package/types/testing/GalleryGenerator.d.ts +1 -0
  103. package/types/testing/LogicInspector.d.ts +22 -0
  104. package/types/testing/SnapshotInspector.d.ts +17 -0
  105. package/types/testing/VisualAdapter.d.ts +15 -0
  106. package/types/testing/index.d.ts +3 -0
  107. package/src/README.md.js +0 -436
@@ -1,192 +1,143 @@
1
- import { Model } from '@nan0web/core'
2
- import * as ComponentModels from './components/index.js'
1
+ import { Model } from '@nan0web/types'
3
2
  import { BreadcrumbModel } from './components/BreadcrumbModel.js'
4
3
 
5
4
  /**
6
- * @typedef {Object} SandboxData
7
- * @property {string[]} [components]
8
- * @property {string} [selectedComponent]
9
- * @property {string} [themeFormat]
10
- */
11
-
12
- /**
13
- * Model-as-Schema for the UI Sandbox environment.
14
- * Represents a tool wrapping standard OLMUI components, allowing
15
- * users to inspect their models, tweak variables interactively,
16
- * and export the configuration as themes for the Marketplace.
17
- *
18
- * Navigation uses BreadcrumbModel:
19
- * ESC = pop one level (if stack has no parent → exit app)
20
- * Ctrl+C = always exit (handled by prompts.js wrapper)
21
- *
22
- * URL mapping:
23
- * /sandbox → Select Component
24
- * /sandbox/button → Edit Button properties
25
- * /sandbox/button/export → Choose export format
5
+ * SandboxModel OLMUI Model-as-Schema
6
+ * Environment for testing and previewing UI components with dynamic property editing.
26
7
  */
27
8
  export class SandboxModel extends Model {
28
- // ==========================================
29
- // 1. MODEL AS SCHEMA (Static Definition)
30
- // ==========================================
9
+ static $id = '@nan0web/ui/SandboxModel'
31
10
 
32
11
  static components = {
33
12
  help: 'List of registered UI components available for inspection',
34
13
  type: 'string[]',
35
- default: []
14
+ default: [],
36
15
  }
37
16
 
38
17
  static selectedComponent = {
39
- help: 'The specific component chosen by the user for theming',
40
- type: 'string',
18
+ help: 'The component currently being inspected in the sandbox',
19
+ placeholder: 'Button',
20
+ default: '',
41
21
  }
42
22
 
43
23
  static themeFormat = {
44
24
  help: 'The file format chosen to export the custom theme configuration',
45
25
  options: ['yaml', 'css', 'json'],
46
- default: 'yaml'
26
+ default: 'yaml',
47
27
  }
48
28
 
49
29
  /**
50
- * @param {SandboxData | any} [data]
30
+ * @param {Partial<SandboxModel> | Record<string, any>} data Model input data.
31
+ * @param {object} [options] Extended options (db, etc.)
51
32
  */
52
- constructor(data = {}) {
53
- super(data)
54
- /** @type {string[]|undefined} */ this.components
55
- /** @type {string|undefined} */ this.selectedComponent
56
- /** @type {string|undefined} */ this.themeFormat
33
+ constructor(data = {}, options = {}) {
34
+ super(data, options)
35
+ /** @type {string[]} List of registered UI components available for inspection */ this
36
+ .components
37
+ /** @type {string} The component currently being inspected in the sandbox */ this
38
+ .selectedComponent
39
+ /** @type {'yaml'|'css'|'json'} The file format chosen to export the custom theme configuration */ this
40
+ .themeFormat
57
41
  }
58
42
 
59
- // ==========================================
60
- // 2. AGNOSTIC LOGIC (Async Generator)
61
- // ==========================================
62
-
43
+ /**
44
+ * @returns {AsyncGenerator<any, any, any>}
45
+ */
63
46
  async *run() {
64
47
  /** @type {any} */
65
- let targetInstance = null
66
-
67
- // ── BreadcrumbModel as navigation stack ──
68
- const nav = new BreadcrumbModel()
69
- nav.push('🏖 Sandbox', 'sandbox')
48
+ let lastResponse = null
70
49
 
71
50
  while (true) {
72
- // ── Level 1: Select Component ──
51
+ const nav = new BreadcrumbModel({ items: ['Sandbox'] })
52
+
73
53
  if (!this.selectedComponent) {
74
54
  // Show breadcrumb
75
- yield /** @type {any} */ ({
76
- type: 'log', level: 'info',
55
+ yield {
56
+ type: 'log',
57
+ level: 'info',
77
58
  message: `\n${nav}`,
78
59
  component: 'Breadcrumbs',
79
- model: /** @type {any} */ (nav),
80
- })
60
+ model: nav,
61
+ }
81
62
 
82
- // ESC here = CancelError not caught → bubbles out → app exits
83
- const listResponse = yield {
63
+ const response = yield {
84
64
  type: 'ask',
85
65
  field: 'selectedComponent',
86
66
  schema: {
87
- help: 'Select a component to inspect and theme',
67
+ help: 'Select a component to inspect',
88
68
  options: this.components || [],
89
- validate: (/** @type {string} */ val) =>
90
- (this.components || []).includes(val) || 'Component not found in sandbox registry',
91
69
  },
92
70
  component: 'Select',
93
- model: /** @type {any} */ (this),
71
+ model: this,
94
72
  }
95
- this.selectedComponent = listResponse.value
96
- nav.push(/** @type {string} */ (this.selectedComponent))
97
-
98
- // Instantiate the selected model class
99
- const Ctor = ComponentModels[`${this.selectedComponent}Model`]
100
- targetInstance = Ctor ? new Ctor() : this
73
+ this.selectedComponent = response.value
74
+ if (!this.selectedComponent) break
101
75
  }
102
76
 
103
- // ── Level 2: Edit Component Properties ──
104
- // URL: /sandbox/button Data: data/sandbox/button/index.yaml
105
- /** @type {any} */
77
+ nav.push(this.selectedComponent, this.selectedComponent)
78
+
79
+ // 2. Component Configuration Mode
106
80
  let configResponse
107
81
  try {
108
- yield /** @type {any} */ ({
109
- type: 'log', level: 'info',
82
+ yield {
83
+ type: 'log',
84
+ level: 'info',
110
85
  message: `\n${nav}`,
111
86
  component: 'Breadcrumbs',
112
- model: /** @type {any} */ (nav),
113
- })
87
+ model: nav,
88
+ }
114
89
 
115
90
  configResponse = yield {
116
91
  type: 'ask',
117
- field: 'componentThemeConfig',
118
- schema: ComponentModels[`${this.selectedComponent}Model`] || {
119
- help: `Configure properties for ${this.selectedComponent} to create a theme variation`,
120
- },
121
- component: 'SandboxWrapper',
122
- model: true,
123
- instance: /** @type {any} */ (targetInstance),
92
+ field: 'config',
93
+ schema: { help: `Configure ${this.selectedComponent} properties` },
94
+ component: 'PropertyEditor',
95
+ model: this, // Passes the whole Sandbox context
124
96
  }
125
97
  } catch (e) {
126
- const err = /** @type {Error} */ (e)
127
- if (err.name === 'CancelError') {
128
- // Pop: Level 2 → Level 1
129
- nav.pop()
130
- this.selectedComponent = undefined
131
- continue
132
- }
133
- throw e
98
+ this.selectedComponent = ''
99
+ continue
134
100
  }
135
101
 
136
- // Persist edits for potential back-navigation
137
- targetInstance = configResponse.value
102
+ if (configResponse.cancelled) {
103
+ this.selectedComponent = ''
104
+ continue
105
+ }
138
106
 
139
- // ── Level 3: Choose Export Format ──
140
- // URL: /sandbox/button/export Data: data/sandbox/button/export/index.yaml
141
- /** @type {any} */
142
- let formatResponse
107
+ // 3. Theme Export Mode
143
108
  try {
144
109
  nav.push('Export', 'export')
145
- yield /** @type {any} */ ({
146
- type: 'log', level: 'info',
110
+ yield {
111
+ type: 'log',
112
+ level: 'info',
147
113
  message: `\n${nav}`,
148
114
  component: 'Breadcrumbs',
149
- model: /** @type {any} */ (nav),
150
- })
115
+ model: nav,
116
+ }
151
117
 
152
- formatResponse = yield {
118
+ const themeResponse = yield {
153
119
  type: 'ask',
154
120
  field: 'themeFormat',
155
121
  schema: {
156
- help: 'Choose how to export the theme configuration',
122
+ help: 'Choose export format',
157
123
  options: SandboxModel.themeFormat.options,
158
124
  },
159
125
  component: 'Select',
160
- model: /** @type {any} */ (this),
126
+ model: this,
161
127
  }
128
+ this.themeFormat = themeResponse.value
162
129
  } catch (e) {
163
- const err = /** @type {Error} */ (e)
164
- if (err.name === 'CancelError') {
165
- // Pop: Level 3 → Level 2 (same targetInstance preserved)
166
- nav.pop()
167
- continue
168
- }
169
- throw e
130
+ // stay on results
170
131
  }
171
132
 
172
- this.themeFormat = formatResponse.value
173
-
174
- // 4. Success notification
175
- yield /** @type {any} */ ({
176
- type: 'log',
177
- level: 'success',
178
- message: `Theme exported as ${(this.themeFormat || 'json').toUpperCase()}! Path: ${nav.path}`,
179
- })
180
-
181
- // 5. Return result with navigation context
182
133
  return {
183
134
  type: 'result',
184
135
  data: {
185
- targetComponent: this.selectedComponent,
136
+ component: this.selectedComponent,
186
137
  themeConfig: configResponse.value,
187
138
  exportFormat: this.themeFormat,
188
139
  breadcrumb: nav.path,
189
- }
140
+ },
190
141
  }
191
142
  }
192
143
  }
@@ -1,90 +1,173 @@
1
- import { InputModel, ConfirmModel, SpinnerModel, ToastModel, TableModel, ButtonModel, AutocompleteModel, SelectModel } from './components/index.js'
2
- import { Model } from '@nan0web/core'
1
+ import { Model } from '@nan0web/types'
2
+ import {
3
+ ConfirmModel,
4
+ SpinnerModel,
5
+ ToastModel,
6
+ TableModel,
7
+ ButtonModel,
8
+ } from './components/index.js'
9
+ import { resolveDefaults } from '@nan0web/types'
10
+
11
+ /**
12
+ * Sub-model representing the user's profile for the showcase journey.
13
+ * Following strictly Model-as-Schema (0HCnAI) pattern.
14
+ */
15
+ class ProfileModel extends Model {
16
+ static $id = '@nan0web/ui/ProfileModel'
17
+
18
+ static UI = {
19
+ title: 'Profile',
20
+ }
21
+
22
+ // Use alias 'name' to avoid conflict with JS static name property
23
+ static profileName = {
24
+ alias: 'name',
25
+ help: 'Your name',
26
+ placeholder: 'John Doe',
27
+ required: true,
28
+ pattern: '.{3,}',
29
+ }
30
+
31
+ static role = {
32
+ help: 'Your role',
33
+ options: ['Developer', 'Designer', 'Manager'],
34
+ default: 'Developer',
35
+ }
36
+
37
+ static tool = {
38
+ help: 'Your tool',
39
+ options: ['React', 'Lit', 'Node.js', 'Playwright', 'Vitest', 'Vite'],
40
+ hint: 'autocomplete',
41
+ }
42
+
43
+ /**
44
+ * @param {Partial<ProfileModel> | Record<string, any>} data Model input data.
45
+ * @param {object} [options] Extended options (db, etc.)
46
+ */
47
+ constructor(data = {}, options = {}) {
48
+ super(data, options)
49
+ /** @type {string} Your name */ this.profileName
50
+ /** @type {'Developer'|'Designer'|'Manager'} Your role */ this.role
51
+ /** @type {'React'|'Lit'|'Node.js'|'Playwright'|'Vitest'|'Vite'} Your tool */ this.tool
52
+ }
53
+ }
3
54
 
4
55
  /**
5
56
  * Model-as-Schema for the entire UI Sandbox Showcase.
6
57
  * Represents a complete User Journey demonstrating all components.
7
- * Showcases OLMUI Scenario Testing capabilities.
8
58
  */
9
59
  export class ShowcaseAppModel extends Model {
10
- // ==========================================
11
- // 1. MODEL AS SCHEMA (Static Definition)
12
- // ==========================================
60
+ static $id = '@nan0web/ui/ShowcaseAppModel'
61
+
62
+ // Canonical UI Texts and Messages
63
+ static UI = {
64
+ appName: 'showcase.app_name',
65
+ startBtn: 'showcase.start_btn',
66
+ generateConfirm: 'showcase.msg_generate_confirm',
67
+ aborted: 'showcase.msg_aborted',
68
+ success: 'showcase.msg_success',
69
+ cancelled: 'showcase.msg_cancelled',
70
+ tableProperty: 'table.property',
71
+ tableValue: 'table.value',
72
+ tableStatus: 'table.status',
73
+ tableStatusActive: 'table.status_active',
74
+ }
13
75
 
14
- static appName = {
15
- help: 'Name of the showcase application',
16
- default: 'Component Showcase (Zero Hallucination)',
76
+ static appTitle = {
77
+ alias: 'appName',
78
+ help: 'showcase.app_name_help',
79
+ default: ShowcaseAppModel.UI.appName,
17
80
  type: 'string',
18
81
  }
19
82
 
20
- constructor(data = {}) {
21
- super(data)
22
- /** @type {string|undefined} */ this.appName
83
+ /**
84
+ * @param {Partial<ShowcaseAppModel> | Record<string, any>} data Model input data.
85
+ * @param {object} [options] Extended options (db, etc.)
86
+ */
87
+ constructor(data = {}, options = {}) {
88
+ super(data, options)
89
+ /** @type {string} App name help */ this.appTitle
23
90
  }
24
91
 
25
92
  // ==========================================
26
93
  // 2. AGNOSTIC LOGIC (Async Generator)
27
94
  // ==========================================
28
95
 
96
+ /**
97
+ * @returns {AsyncGenerator<any, any, any>}
98
+ */
29
99
  async *run() {
30
- // 1. Initial interaction: User clicks "Start Showcase" Button
31
- const btnIntent = yield* new ButtonModel({ content: 'Start Showcase', variant: 'primary', size: 'lg' }).run()
100
+ const { UI } = ShowcaseAppModel
101
+
102
+ // 1. Initial interaction: Start Button
103
+ const startBtn = new ButtonModel({
104
+ content: UI.startBtn,
105
+ variant: 'primary',
106
+ size: 'lg',
107
+ })
108
+ const btnIntent = yield* startBtn.run()
32
109
 
33
110
  if (!btnIntent.data.clicked) {
34
111
  return { type: 'result', data: { success: false, reason: 'start_cancelled' } }
35
112
  }
36
113
 
37
- // 2. Ask user for their name via Input
38
- const { data: nameData } = yield* new InputModel({
39
- label: 'Enter your name to begin',
40
- placeholder: 'e.g. Yaroslav',
41
- required: true,
42
- pattern: '.{3,}'
43
- }).run()
114
+ // 2. Journey Form: Pure Model-as-Schema
115
+ const profile = new ProfileModel()
116
+ const profileResult = yield {
117
+ type: 'ask',
118
+ model: profile,
119
+ schema: ProfileModel,
120
+ field: 'profile_form',
121
+ }
44
122
 
45
- const userName = /** @type {string} */ (nameData.value)
123
+ if (profileResult.cancelled) {
124
+ yield* new ToastModel({
125
+ message: UI.aborted,
126
+ variant: 'warn',
127
+ duration: 0,
128
+ }).run()
129
+ return { type: 'result', data: { success: false, reason: 'user_aborted' } }
130
+ }
46
131
 
47
- // 3. Ask user to select their role via Select
48
- const { data: roleData } = yield* new SelectModel({
49
- options: ['Developer', 'Designer', 'Manager']
50
- }).run()
51
-
52
- const role = /** @type {string} */ (roleData.selected)
132
+ // Update model data with alias resolution
133
+ profile.setData(profileResult.value)
53
134
 
54
- // 4. Ask for their favorite tool via Autocomplete
55
- const { data: toolData } = yield* new AutocompleteModel({
56
- options: ['React', 'Lit', 'Node.js', 'Playwright', 'Vitest', 'Vite']
135
+ // 3. Confirmation - key is used, adapter should handle translation/interpolation
136
+ const confirmIntent = yield* new ConfirmModel({
137
+ message: UI.generateConfirm,
138
+ // Pass interpolation data
139
+ data: { name: profile.profileName, role: profile.role },
57
140
  }).run()
58
141
 
59
- const tool = /** @type {string} */ (toolData.selected)
60
-
61
- // 5. Ask for confirmation before proceeding to heavy calculation
62
- const confirmIntent = yield* new ConfirmModel({ message: `Ready to generate profile for ${userName} (${role})?` }).run()
63
-
64
142
  if (!confirmIntent.data.confirmed) {
65
- yield* new ToastModel({ message: 'Operation aborted.', variant: 'warning', duration: 0 }).run()
143
+ yield* new ToastModel({
144
+ message: UI.cancelled,
145
+ variant: 'info',
146
+ duration: 0,
147
+ }).run()
66
148
  return { type: 'result', data: { success: false, reason: 'user_aborted' } }
67
149
  }
68
150
 
69
- // 6. Demonstrate a long running progress via Spinner
151
+ // 4. Progress Feedback
70
152
  yield* new SpinnerModel({ size: 'md' }).run()
71
153
 
72
- // Simulate business logic processing delay if we were not mocked
73
- // But in generators this just happens immediately between yields
74
-
75
- yield* new ToastModel({ message: 'Profile generated successfully!', variant: 'success', duration: 0 }).run()
154
+ yield* new ToastModel({
155
+ message: UI.success,
156
+ variant: 'success',
157
+ duration: 0,
158
+ }).run()
76
159
 
77
- // 7. Display the result of the showcase in a Table
160
+ // 5. Result Visualization
78
161
  const { data: tableData } = yield* new TableModel({
79
- columns: ['Property', 'Value'],
162
+ columns: [UI.tableProperty, UI.tableValue],
80
163
  rows: [
81
- ['Name', userName],
82
- ['Role', role],
83
- ['Favorite Tool', tool],
84
- ['Status', 'Active']
85
- ]
164
+ ['profile.name', profile.profileName],
165
+ ['profile.role', profile.role],
166
+ ['profile.tool', profile.tool],
167
+ [UI.tableStatus, UI.tableStatusActive],
168
+ ],
86
169
  }).run()
87
170
 
88
- return { type: 'result', data: { success: true, profile: { userName, role, tool }, rowsDisplayed: tableData.rowsCount } }
171
+ return { type: 'result', data: { success: true, profile, rowsDisplayed: tableData.rowsCount } }
89
172
  }
90
173
  }
@@ -0,0 +1,38 @@
1
+ import { Model } from '@nan0web/types'
2
+
3
+ /**
4
+ * AccordionModel — OLMUI Model-as-Schema
5
+ * Collapsible FAQ / accordion item with title + content.
6
+ */
7
+ export class AccordionModel extends Model {
8
+ static $id = '@nan0web/ui/AccordionModel'
9
+
10
+ static title = {
11
+ help: 'Accordion item header / question',
12
+ placeholder: 'How does it work?',
13
+ default: '',
14
+ required: true,
15
+ }
16
+ static content = {
17
+ help: 'Accordion item body / answer (supports markdown)',
18
+ placeholder: 'It works by...',
19
+ default: '',
20
+ required: true,
21
+ }
22
+ static open = {
23
+ help: 'Whether the item is expanded by default',
24
+ default: false,
25
+ type: 'boolean',
26
+ }
27
+
28
+ /**
29
+ * @param {Partial<AccordionModel> | Record<string, any>} data Model input data.
30
+ * @param {object} [options] Extended options (db, etc.)
31
+ */
32
+ constructor(data = {}, options = {}) {
33
+ super(data, options)
34
+ /** @type {string} Accordion item header / question */ this.title
35
+ /** @type {string} Accordion item body / answer (supports markdown) */ this.content
36
+ /** @type {boolean} Whether the item is expanded by default */ this.open
37
+ }
38
+ }
@@ -1,20 +1,10 @@
1
- import { Model } from '@nan0web/core'
2
-
3
- /**
4
- * @typedef {Object} AutocompleteData
5
- * @property {string} [content]
6
- * @property {string[]} [options]
7
- */
1
+ import { Model } from '@nan0web/types'
8
2
 
9
3
  /**
10
4
  * Model-as-Schema for Autocomplete component.
11
5
  * Represents a text input with search suggestions.
12
6
  */
13
7
  export class AutocompleteModel extends Model {
14
- // ==========================================
15
- // 1. MODEL AS SCHEMA (Static Definition)
16
- // ==========================================
17
-
18
8
  static content = {
19
9
  help: 'Current search text',
20
10
  default: '',
@@ -28,18 +18,18 @@ export class AutocompleteModel extends Model {
28
18
  }
29
19
 
30
20
  /**
31
- * @param {AutocompleteData | any} [data]
21
+ * @param {Partial<AutocompleteModel> | Record<string, any>} data Model input data.
22
+ * @param {object} [options] Extended options (db, etc.)
32
23
  */
33
- constructor(data = {}) {
34
- super(data)
35
- /** @type {string|undefined} */ this.content
36
- /** @type {string[]|undefined} */ this.options
24
+ constructor(data = {}, options = {}) {
25
+ super(data, options)
26
+ /** @type {string} Current search text */ this.content
27
+ /** @type {string[]} List of suggestions based on input */ this.options
37
28
  }
38
29
 
39
- // ==========================================
40
- // 2. AGNOSTIC LOGIC (Async Generator)
41
- // ==========================================
42
-
30
+ /**
31
+ * @returns {AsyncGenerator<any, any, any>}
32
+ */
43
33
  async *run() {
44
34
  const response = yield {
45
35
  type: 'ask',
@@ -49,7 +39,7 @@ export class AutocompleteModel extends Model {
49
39
  options: this.options,
50
40
  },
51
41
  component: 'Autocomplete',
52
- model: /** @type {any} */ (this),
42
+ model: this,
53
43
  }
54
44
 
55
45
  this.content = response.value
@@ -0,0 +1,37 @@
1
+ import { Model } from '@nan0web/types'
2
+
3
+ /**
4
+ * BannerModel — OLMUI Model-as-Schema
5
+ * Global notification bar (cookies, maintenance, announcements).
6
+ */
7
+ export class BannerModel extends Model {
8
+ static $id = '@nan0web/ui/BannerModel'
9
+
10
+ static text = {
11
+ help: 'Banner message text',
12
+ placeholder: 'We use cookies to improve your experience',
13
+ default: '',
14
+ required: true,
15
+ }
16
+ static href = {
17
+ help: 'Optional link for "Learn more" or action URL',
18
+ placeholder: 'https://...',
19
+ default: '',
20
+ }
21
+ static closable = {
22
+ help: 'Whether the user can dismiss the banner',
23
+ default: true,
24
+ type: 'boolean',
25
+ }
26
+
27
+ /**
28
+ * @param {Partial<BannerModel> | Record<string, any>} data Model input data.
29
+ * @param {object} [options] Extended options (db, etc.)
30
+ */
31
+ constructor(data = {}, options = {}) {
32
+ super(data, options)
33
+ /** @type {string} Banner message text */ this.text
34
+ /** @type {string} Optional link for "Learn more" or action URL */ this.href
35
+ /** @type {boolean} Whether the user can dismiss the banner */ this.closable
36
+ }
37
+ }
@@ -1,4 +1,4 @@
1
- import { Model } from '@nan0web/core'
1
+ import { Model } from '@nan0web/types'
2
2
 
3
3
  /**
4
4
  * @typedef {Object} BreadcrumbItem
@@ -45,12 +45,14 @@ export class BreadcrumbModel extends Model {
45
45
  }
46
46
 
47
47
  /**
48
- * @param {BreadcrumbData | any} [data]
48
+ * @param {BreadcrumbData | Record<string, any>} data Model input data.
49
+ * @param {object} [options] Extended options (db, etc.)
49
50
  */
50
- constructor(data = {}) {
51
- super(data)
52
- /** @type {BreadcrumbItem[]} */ this.items
53
- /** @type {string} */ this.separator
51
+ constructor(data = {}, options = {}) {
52
+ super(data, options)
53
+ /** @type {BreadcrumbItem[]} Navigation stack */ this.items
54
+ /** @type {string} Visual separator between breadcrumb segments */ this.separator
55
+
54
56
  // Normalize: if items were passed as plain strings, convert to {label, path}
55
57
  if (Array.isArray(this.items)) {
56
58
  this.items = this.items.map((item) =>
@@ -245,13 +247,13 @@ export class BreadcrumbModel extends Model {
245
247
  * This is a "display-only" run — it shows the navigation state.
246
248
  */
247
249
  async *run() {
248
- yield /** @type {any} */ ({
250
+ yield {
249
251
  type: 'log',
250
252
  level: 'info',
251
253
  message: this.toString(),
252
254
  component: 'Breadcrumbs',
253
- model: /** @type {any} */ (this),
254
- })
255
+ model: /** @type {BreadcrumbModel} */ (this),
256
+ }
255
257
 
256
258
  return {
257
259
  type: 'result',