@nan0web/ui 1.7.0 → 1.9.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 (82) hide show
  1. package/package.json +10 -10
  2. package/src/core/Intent.js +4 -3
  3. package/src/domain/FooterModel.js +57 -0
  4. package/src/domain/HeaderModel.js +50 -0
  5. package/src/domain/HeroModel.js +48 -0
  6. package/src/domain/Navigation.js +60 -0
  7. package/src/domain/SandboxModel.js +66 -115
  8. package/src/domain/ShowcaseAppModel.js +134 -49
  9. package/src/domain/components/AccordionModel.js +38 -0
  10. package/src/domain/components/AutocompleteModel.js +11 -21
  11. package/src/domain/components/BannerModel.js +37 -0
  12. package/src/domain/components/BreadcrumbModel.js +11 -9
  13. package/src/domain/components/ButtonModel.js +31 -58
  14. package/src/domain/components/CommentModel.js +44 -0
  15. package/src/domain/components/ConfirmModel.js +26 -33
  16. package/src/domain/components/EmptyStateModel.js +45 -0
  17. package/src/domain/components/FAQModel.js +32 -0
  18. package/src/domain/components/FooterConfigModel.js +26 -0
  19. package/src/domain/components/FooterVisibilityModel.js +48 -0
  20. package/src/domain/components/GalleryModel.js +36 -0
  21. package/src/domain/components/HeaderConfigModel.js +26 -0
  22. package/src/domain/components/HeaderVisibilityModel.js +54 -0
  23. package/src/domain/components/InputModel.js +21 -41
  24. package/src/domain/components/PriceModel.js +30 -0
  25. package/src/domain/components/PricingModel.js +39 -0
  26. package/src/domain/components/PricingSectionModel.js +32 -0
  27. package/src/domain/components/ProfileDropdownModel.js +45 -0
  28. package/src/domain/components/SelectModel.js +11 -21
  29. package/src/domain/components/SpinnerModel.js +11 -26
  30. package/src/domain/components/StatsItemModel.js +38 -0
  31. package/src/domain/components/StatsModel.js +32 -0
  32. package/src/domain/components/TableModel.js +11 -24
  33. package/src/domain/components/TabsModel.js +30 -0
  34. package/src/domain/components/TestimonialModel.js +24 -0
  35. package/src/domain/components/TimelineItemModel.js +38 -0
  36. package/src/domain/components/TimelineModel.js +32 -0
  37. package/src/domain/components/ToastModel.js +24 -51
  38. package/src/domain/components/TreeModel.js +10 -26
  39. package/src/domain/components/index.js +34 -0
  40. package/src/domain/index.js +25 -0
  41. package/src/index.js +2 -0
  42. package/types/domain/FooterModel.d.ts +52 -0
  43. package/types/domain/HeaderModel.d.ts +45 -0
  44. package/types/domain/HeroModel.d.ts +43 -0
  45. package/types/domain/Navigation.d.ts +51 -0
  46. package/types/domain/SandboxModel.d.ts +16 -40
  47. package/types/domain/ShowcaseAppModel.d.ts +27 -53
  48. package/types/domain/components/AccordionModel.d.ts +33 -0
  49. package/types/domain/components/AutocompleteModel.d.ts +10 -29
  50. package/types/domain/components/BannerModel.d.ts +32 -0
  51. package/types/domain/components/BreadcrumbModel.d.ts +13 -6
  52. package/types/domain/components/ButtonModel.d.ts +18 -54
  53. package/types/domain/components/CommentModel.d.ts +39 -0
  54. package/types/domain/components/ConfirmModel.d.ts +20 -35
  55. package/types/domain/components/EmptyStateModel.d.ts +40 -0
  56. package/types/domain/components/FAQModel.d.ts +27 -0
  57. package/types/domain/components/FooterConfigModel.d.ts +21 -0
  58. package/types/domain/components/FooterVisibilityModel.d.ts +43 -0
  59. package/types/domain/components/GalleryModel.d.ts +35 -0
  60. package/types/domain/components/HeaderConfigModel.d.ts +21 -0
  61. package/types/domain/components/HeaderVisibilityModel.d.ts +49 -0
  62. package/types/domain/components/HeroModel.d.ts +24 -0
  63. package/types/domain/components/InputModel.d.ts +19 -59
  64. package/types/domain/components/PriceModel.d.ts +25 -0
  65. package/types/domain/components/PricingModel.d.ts +34 -0
  66. package/types/domain/components/PricingSectionModel.d.ts +27 -0
  67. package/types/domain/components/ProfileDropdownModel.d.ts +40 -0
  68. package/types/domain/components/SelectModel.d.ts +13 -28
  69. package/types/domain/components/ShowcaseAppModel.d.ts +32 -0
  70. package/types/domain/components/SpinnerModel.d.ts +10 -27
  71. package/types/domain/components/StatsItemModel.d.ts +33 -0
  72. package/types/domain/components/StatsModel.d.ts +27 -0
  73. package/types/domain/components/TableModel.d.ts +10 -26
  74. package/types/domain/components/TabsModel.d.ts +28 -0
  75. package/types/domain/components/TestimonialModel.d.ts +18 -0
  76. package/types/domain/components/TimelineItemModel.d.ts +33 -0
  77. package/types/domain/components/TimelineModel.d.ts +27 -0
  78. package/types/domain/components/ToastModel.d.ts +16 -45
  79. package/types/domain/components/TreeModel.d.ts +13 -36
  80. package/types/domain/components/index.d.ts +20 -0
  81. package/types/domain/index.d.ts +5 -1
  82. package/types/index.d.ts +1 -0
@@ -1,88 +1,173 @@
1
- import { InputModel, ConfirmModel, SpinnerModel, ToastModel, TableModel, ButtonModel, AutocompleteModel, SelectModel } from './components/index.js'
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
+ }
2
54
 
3
55
  /**
4
56
  * Model-as-Schema for the entire UI Sandbox Showcase.
5
57
  * Represents a complete User Journey demonstrating all components.
6
- * Showcases OLMUI Scenario Testing capabilities.
7
58
  */
8
- export class ShowcaseAppModel {
9
- // ==========================================
10
- // 1. MODEL AS SCHEMA (Static Definition)
11
- // ==========================================
59
+ export class ShowcaseAppModel extends Model {
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
+ }
12
75
 
13
- static appName = {
14
- help: 'Name of the showcase application',
15
- default: 'Component Showcase (Zero Hallucination)',
76
+ static appTitle = {
77
+ alias: 'appName',
78
+ help: 'showcase.app_name_help',
79
+ default: ShowcaseAppModel.UI.appName,
16
80
  type: 'string',
17
81
  }
18
82
 
19
- constructor() {
20
- this.appName = ShowcaseAppModel.appName.default
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
21
90
  }
22
91
 
23
92
  // ==========================================
24
93
  // 2. AGNOSTIC LOGIC (Async Generator)
25
94
  // ==========================================
26
95
 
96
+ /**
97
+ * @returns {AsyncGenerator<any, any, any>}
98
+ */
27
99
  async *run() {
28
- // 1. Initial interaction: User clicks "Start Showcase" Button
29
- 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()
30
109
 
31
110
  if (!btnIntent.data.clicked) {
32
111
  return { type: 'result', data: { success: false, reason: 'start_cancelled' } }
33
112
  }
34
113
 
35
- // 2. Ask user for their name via Input
36
- const { data: nameData } = yield* new InputModel({
37
- label: 'Enter your name to begin',
38
- placeholder: 'e.g. Yaroslav',
39
- required: true,
40
- pattern: '.{3,}'
41
- }).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
+ }
42
122
 
43
- 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
+ }
44
131
 
45
- // 3. Ask user to select their role via Select
46
- const { data: roleData } = yield* new SelectModel({
47
- options: ['Developer', 'Designer', 'Manager']
48
- }).run()
49
-
50
- const role = /** @type {string} */ (roleData.selected)
132
+ // Update model data with alias resolution
133
+ profile.setData(profileResult.value)
51
134
 
52
- // 4. Ask for their favorite tool via Autocomplete
53
- const { data: toolData } = yield* new AutocompleteModel({
54
- 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 },
55
140
  }).run()
56
141
 
57
- const tool = /** @type {string} */ (toolData.selected)
58
-
59
- // 5. Ask for confirmation before proceeding to heavy calculation
60
- const confirmIntent = yield* new ConfirmModel({ message: `Ready to generate profile for ${userName} (${role})?` }).run()
61
-
62
142
  if (!confirmIntent.data.confirmed) {
63
- 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()
64
148
  return { type: 'result', data: { success: false, reason: 'user_aborted' } }
65
149
  }
66
150
 
67
- // 6. Demonstrate a long running progress via Spinner
151
+ // 4. Progress Feedback
68
152
  yield* new SpinnerModel({ size: 'md' }).run()
69
153
 
70
- // Simulate business logic processing delay if we were not mocked
71
- // But in generators this just happens immediately between yields
72
-
73
- 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()
74
159
 
75
- // 7. Display the result of the showcase in a Table
160
+ // 5. Result Visualization
76
161
  const { data: tableData } = yield* new TableModel({
77
- columns: ['Property', 'Value'],
162
+ columns: [UI.tableProperty, UI.tableValue],
78
163
  rows: [
79
- ['Name', userName],
80
- ['Role', role],
81
- ['Favorite Tool', tool],
82
- ['Status', 'Active']
83
- ]
164
+ ['profile.name', profile.profileName],
165
+ ['profile.role', profile.role],
166
+ ['profile.tool', profile.tool],
167
+ [UI.tableStatus, UI.tableStatusActive],
168
+ ],
84
169
  }).run()
85
170
 
86
- return { type: 'result', data: { success: true, profile: { userName, role, tool }, rowsDisplayed: tableData.rowsCount } }
171
+ return { type: 'result', data: { success: true, profile, rowsDisplayed: tableData.rowsCount } }
87
172
  }
88
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',
@@ -1,92 +1,65 @@
1
- import { Model } from '@nan0web/core'
2
-
3
- /**
4
- * @typedef {'primary'|'secondary'|'info'|'ok'|'warn'|'err'|'ghost'} ButtonVariant
5
- * @typedef {'sm'|'md'|'lg'} ButtonSize
6
- * @typedef {Object} ButtonData
7
- * @property {string} [content]
8
- * @property {ButtonVariant} [variant]
9
- * @property {ButtonSize} [size]
10
- * @property {boolean} [outline]
11
- * @property {boolean} [disabled]
12
- * @property {boolean} [loading]
13
- */
1
+ import { Model } from '@nan0web/types'
14
2
 
15
3
  /**
16
4
  * Model-as-Schema for Button component.
17
- * Represents the intention and state of a Button interaction.
18
- * Used exclusively for schema definition and editor validation.
19
5
  */
20
6
  export class ButtonModel extends Model {
21
- // ==========================================
22
- // 1. MODEL AS SCHEMA (Static Definition)
23
- // ==========================================
24
-
25
- static content = {
26
- help: 'Text or content inside the button',
27
- default: 'Click Me',
28
- type: 'string',
29
- }
30
-
31
7
  static variant = {
32
- help: 'Visual importance and semantic meaning',
8
+ help: 'Button visual style',
33
9
  default: 'primary',
34
- options: ['primary', 'secondary', 'info', 'ok', 'warn', 'err', 'ghost'],
10
+ options: ['primary', 'secondary', 'danger', 'ghost'],
35
11
  }
36
12
 
37
- static size = {
38
- help: 'Size of the button',
39
- default: 'md',
40
- options: ['sm', 'md', 'lg'],
13
+ static content = {
14
+ help: 'Text displayed inside the button',
15
+ default: 'Click me',
16
+ type: 'string',
41
17
  }
42
18
 
43
- static outline = {
44
- help: 'Whether the button has a transparent background with border',
45
- default: false,
46
- type: 'boolean',
19
+ static href = {
20
+ help: 'Optional link URL',
21
+ default: '',
22
+ type: 'string',
47
23
  }
48
24
 
49
25
  static disabled = {
50
- help: 'Whether the button is disabled and unclickable',
26
+ help: 'Whether the button can be clicked',
51
27
  default: false,
52
28
  type: 'boolean',
53
29
  }
54
30
 
55
- static loading = {
56
- help: 'Whether the button shows a loading spinner instead of content',
31
+ static clicked = {
32
+ help: 'Interal flag: true if clicked',
57
33
  default: false,
58
34
  type: 'boolean',
59
35
  }
60
36
 
61
37
  /**
62
- * @param {ButtonData | any} [data]
38
+ * @param {Partial<ButtonModel> | Record<string, any>} data Model input data.
39
+ * @param {object} [options] Extended options (db, etc.)
63
40
  */
64
- constructor(data = {}) {
65
- super(data)
66
- /** @type {string|undefined} */ this.content
67
- /** @type {ButtonVariant|undefined} */ this.variant
68
- /** @type {ButtonSize|undefined} */ this.size
69
- /** @type {boolean|undefined} */ this.outline
70
- /** @type {boolean|undefined} */ this.disabled
71
- /** @type {boolean|undefined} */ this.loading
41
+ constructor(data = {}, options = {}) {
42
+ super(data, options)
43
+ /** @type {'primary'|'secondary'|'danger'|'ghost'} Button visual style */ this.variant
44
+ /** @type {string} Text displayed inside the button */ this.content
45
+ /** @type {string} Optional link URL */ this.href
46
+ /** @type {boolean} Whether the button can be clicked */ this.disabled
47
+ /** @type {boolean} Reactive flag set to true when user activates the button */ this.clicked
72
48
  }
73
49
 
74
- // ==========================================
75
- // 2. AGNOSTIC LOGIC (Async Generator)
76
- // ==========================================
77
-
50
+ /**
51
+ * @returns {AsyncGenerator<any, any, any>}
52
+ */
78
53
  async *run() {
79
- // A basic button interaction intention:
80
- // We simply yield ourselves as a 'button_click' intent.
81
- // Adaptors will render the button and wait for the click event.
82
54
  const response = yield {
83
55
  type: 'ask',
84
- field: 'action',
85
- schema: { help: 'Click the button to proceed' },
56
+ field: 'clicked',
57
+ schema: { help: 'Click the button' },
86
58
  component: 'Button',
87
- model: /** @type {any} */ (this),
59
+ model: this,
88
60
  }
89
61
 
90
- return { type: 'result', data: { clicked: true, ...response } }
62
+ this.clicked = response.value?.clicked || false
63
+ return { type: 'result', data: { clicked: this.clicked } }
91
64
  }
92
65
  }
@@ -0,0 +1,44 @@
1
+ import { Model } from '@nan0web/types'
2
+
3
+ /**
4
+ * CommentModel — OLMUI Model-as-Schema
5
+ * Base model for user-generated comments / reviews.
6
+ */
7
+ export class CommentModel extends Model {
8
+ static $id = '@nan0web/ui/CommentModel'
9
+
10
+ static author = {
11
+ help: 'Author name',
12
+ placeholder: 'Jane Doe',
13
+ default: '',
14
+ required: true,
15
+ }
16
+ static avatar = {
17
+ help: 'Author avatar image URL',
18
+ placeholder: 'https://...',
19
+ default: '',
20
+ }
21
+ static text = {
22
+ help: 'Comment body text',
23
+ placeholder: 'Great product!',
24
+ default: '',
25
+ required: true,
26
+ }
27
+ static date = {
28
+ help: 'Comment date (ISO 8601)',
29
+ placeholder: '2026-01-01',
30
+ default: '',
31
+ }
32
+
33
+ /**
34
+ * @param {Partial<CommentModel> | Record<string, any>} data Model input data.
35
+ * @param {object} [options] Extended options (db, etc.)
36
+ */
37
+ constructor(data = {}, options = {}) {
38
+ super(data, options)
39
+ /** @type {string} Author name */ this.author
40
+ /** @type {string} Author avatar image URL */ this.avatar
41
+ /** @type {string} Comment body text */ this.text
42
+ /** @type {string} Comment date (ISO 8601) */ this.date
43
+ }
44
+ }
@@ -1,64 +1,57 @@
1
- import { Model } from '@nan0web/core'
1
+ import { Model } from '@nan0web/types'
2
2
 
3
3
  /**
4
- * @typedef {Object} ConfirmData
5
- * @property {string} [message]
6
- * @property {string} [confirmText]
7
- * @property {string} [cancelText]
8
- */
9
-
10
- /**
11
- * Model-as-Schema for Confirm component.
4
+ * Model-as-Schema for Confirmation dialog.
12
5
  */
13
6
  export class ConfirmModel extends Model {
14
- // ==========================================
15
- // 1. MODEL AS SCHEMA (Static Definition)
16
- // ==========================================
7
+ static title = {
8
+ help: 'Short title for the action',
9
+ default: 'Confirm Action',
10
+ type: 'string',
11
+ }
17
12
 
18
13
  static message = {
19
- help: 'Dialog message displayed to the user',
14
+ help: 'The question asked to the user',
20
15
  default: 'Are you sure?',
21
16
  type: 'string',
22
17
  }
23
18
 
24
- static confirmText = {
25
- help: 'Label for the positive confirmation button',
19
+ static okLabel = {
20
+ help: 'Text for the confirm button',
26
21
  default: 'Yes',
27
22
  type: 'string',
28
23
  }
29
24
 
30
- static cancelText = {
31
- help: 'Label for the negative rejection button',
25
+ static cancelLabel = {
26
+ help: 'Text for the cancel button',
32
27
  default: 'No',
33
28
  type: 'string',
34
29
  }
35
30
 
36
31
  /**
37
- * @param {ConfirmData | any} [data]
32
+ * @param {Partial<ConfirmModel> | Record<string, any>} data Model input data.
33
+ * @param {object} [options] Extended options (db, etc.)
38
34
  */
39
- constructor(data = {}) {
40
- super(data)
41
- /** @type {string|undefined} */ this.message
42
- /** @type {string|undefined} */ this.confirmText
43
- /** @type {string|undefined} */ this.cancelText
35
+ constructor(data = {}, options = {}) {
36
+ super(data, options)
37
+ /** @type {string} Short title for the action */ this.title
38
+ /** @type {string} The question asked to the user */ this.message
39
+ /** @type {string} Text for the confirm button */ this.okLabel
40
+ /** @type {string} Text for the cancel button */ this.cancelLabel
44
41
  }
45
42
 
46
- // ==========================================
47
- // 2. AGNOSTIC LOGIC (Async Generator)
48
- // ==========================================
49
-
43
+ /**
44
+ * @returns {AsyncGenerator<any, any, any>}
45
+ */
50
46
  async *run() {
51
47
  const response = yield {
52
48
  type: 'ask',
53
49
  field: 'confirmed',
54
- schema: {
55
- help: this.message,
56
- type: 'boolean',
57
- },
50
+ schema: { type: 'boolean', help: this.message },
58
51
  component: 'Confirm',
59
- model: /** @type {any} */ (this), // Attached for richer UI metadata
52
+ model: this,
60
53
  }
61
54
 
62
- return { type: 'result', data: { confirmed: !!response.value } }
55
+ return { type: 'result', data: { confirmed: response.value === true } }
63
56
  }
64
57
  }