@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nan0web/ui",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
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",
@@ -37,11 +37,12 @@
37
37
  "author": "YaRaSLove (ЯRаСлав) <support@yaro.page>",
38
38
  "license": "ISC",
39
39
  "devDependencies": {
40
- "@nan0web/event": "*",
40
+ "@nan0web/event": "^1.0.1",
41
41
  "@nan0web/i18n": "^1.1.0",
42
42
  "@nan0web/release": "^1.0.2",
43
43
  "@nan0web/test": "^1.1.3",
44
- "@nan0web/ui-cli": "^2.3.0",
44
+ "@nan0web/ui-cli": "^2.9.0",
45
+ "@nan0web/ui-lit": "^1.1.0",
45
46
  "@playwright/test": "^1.58.2",
46
47
  "@rollup/plugin-yaml": "^4.1.2",
47
48
  "@vitest/coverage-v8": "^3.2.4",
@@ -50,15 +51,14 @@
50
51
  "knip": "^5.86.0",
51
52
  "lit": "^3.3.2",
52
53
  "vite": "^6.4.1",
53
- "vitest": "^3.2.4",
54
- "@nan0web/ui-lit": "1.1.0"
54
+ "vitest": "^3.2.4"
55
55
  },
56
56
  "dependencies": {
57
57
  "@nan0web/co": "^2.0.0",
58
- "@nan0web/event": "^1.0.1",
59
58
  "@nan0web/log": "^1.1.1",
60
59
  "@nan0web/types": "^1.4.0",
61
- "string-width": "^7.2.0"
60
+ "string-width": "^7.2.0",
61
+ "@nan0web/core": "1.1.2"
62
62
  },
63
63
  "scripts": {
64
64
  "build": "tsc",
@@ -73,9 +73,9 @@
73
73
  "test:release": "node --test \"releases/**/*.test.js\"",
74
74
  "test:status": "nan0test status --hide-name",
75
75
  "test:play": "node --test --test-timeout=3333 \"play/**/*.test.js\"",
76
- "test:ssg": "node --test --test-timeout=5000 \"ssg/ssg.test.js\"",
77
- "test:e2e": "playwright test --ignore-snapshots e2e/components.spec.js e2e/debug-label.spec.js",
78
- "test:e2e:slow": "E2E_SLOW=1 playwright test e2e/visual.spec.js",
76
+ "test:ssg": "node --test --test-timeout=5000 \"src/test/ssg.test.js\"",
77
+ "test:e2e": "playwright test --ignore-snapshots test/e2e/components.spec.js test/e2e/debug-label.spec.js",
78
+ "test:e2e:slow": "E2E_SLOW=1 playwright test test/e2e/visual.spec.js",
79
79
  "test:all": "npm run test && npm run test:docs && npm run test:play && npm run test:e2e && npm run knip",
80
80
  "knip": "knip --production",
81
81
  "precommit": "npm test",
@@ -131,14 +131,15 @@ export function validateIntent(intent) {
131
131
  if (typeof intent.field !== 'string' || !intent.field) {
132
132
  throw IntentErrorModel.error('ask_missing_field')
133
133
  }
134
- // Accept both: plain schema {help: '...'} and Model-as-Schema class
135
- const isModel = intent.model === true
134
+ // Accept both: plain schema {help: '...'} and Model-as-Schema class/instance
135
+ const isModel = !!intent.model
136
136
  if (!isModel && (!intent.schema || typeof intent.schema !== 'object' || !('help' in intent.schema))) {
137
137
  throw IntentErrorModel.error('ask_missing_schema_help')
138
138
  }
139
139
  }
140
140
  if (intent.type === 'progress' || intent.type === 'log') {
141
- if (typeof intent.message !== 'string') {
141
+ const isComponentLog = intent.type === 'log' && intent.component
142
+ if (!isComponentLog && typeof intent.message !== 'string') {
142
143
  throw IntentErrorModel.error('intent_missing_message', { type: intent.type })
143
144
  }
144
145
  }
@@ -0,0 +1,57 @@
1
+ import { Model } from '@nan0web/types'
2
+ import Navigation from './Navigation.js'
3
+
4
+ /**
5
+ * FooterModel — OLMUI Model-as-Schema
6
+ * Universal footer structure: copyright, version, license, navigation, sharing, languages.
7
+ */
8
+ export default class FooterModel extends Model {
9
+ static $id = '@nan0web/ui/FooterModel'
10
+
11
+ static copyright = {
12
+ help: 'Copyright text',
13
+ placeholder: '© 2026 Company',
14
+ default: '',
15
+ }
16
+ static version = {
17
+ help: 'Application version string',
18
+ placeholder: '1.0.0',
19
+ default: '',
20
+ }
21
+ static license = {
22
+ help: 'License type',
23
+ placeholder: 'ISC',
24
+ default: '',
25
+ }
26
+ static nav = {
27
+ help: 'Footer navigation links',
28
+ type: 'Navigation[]',
29
+ hint: Navigation,
30
+ default: [],
31
+ }
32
+ static share = {
33
+ help: 'Social sharing links',
34
+ type: 'Navigation[]',
35
+ hint: Navigation,
36
+ default: [],
37
+ }
38
+ static langs = {
39
+ help: 'Available languages for switcher',
40
+ type: 'Language[]',
41
+ default: [],
42
+ }
43
+
44
+ /**
45
+ * @param {Partial<FooterModel> | Record<string, any>} data Model input data.
46
+ * @param {object} [options] Extended options (db, etc.)
47
+ */
48
+ constructor(data = {}, options = {}) {
49
+ super(data, options)
50
+ /** @type {string} Copyright text */ this.copyright
51
+ /** @type {string} Application version string */ this.version
52
+ /** @type {string} License type */ this.license
53
+ /** @type {Navigation[]} Footer navigation links */ this.nav
54
+ /** @type {Navigation[]} Social sharing links */ this.share
55
+ /** @type {any[]} Available languages for switcher */ this.langs
56
+ }
57
+ }
@@ -0,0 +1,50 @@
1
+ import { Model } from '@nan0web/types'
2
+ import Navigation from './Navigation.js'
3
+
4
+ /**
5
+ * HeaderModel — OLMUI Model-as-Schema
6
+ * Universal header structure: logo, navigation, language switcher, actions.
7
+ */
8
+ export default class HeaderModel extends Model {
9
+ static $id = '@nan0web/ui/HeaderModel'
10
+
11
+ static title = {
12
+ help: 'Site or app title displayed in the header',
13
+ placeholder: 'My App',
14
+ default: '',
15
+ }
16
+ static logo = {
17
+ help: 'Logo image URL or icon name',
18
+ placeholder: 'https://...',
19
+ default: '',
20
+ }
21
+ static actions = {
22
+ help: 'Header action links (CTA, Sign In, etc.)',
23
+ type: 'Navigation[]',
24
+ hint: Navigation,
25
+ default: [],
26
+ }
27
+ static lang = {
28
+ help: 'Currently active language',
29
+ type: 'Language',
30
+ default: null,
31
+ }
32
+ static langs = {
33
+ help: 'Available languages for switcher',
34
+ type: 'Language[]',
35
+ default: [],
36
+ }
37
+
38
+ /**
39
+ * @param {Partial<HeaderModel> | Record<string, any>} data Model input data.
40
+ * @param {object} [options] Extended options (db, etc.)
41
+ */
42
+ constructor(data = {}, options = {}) {
43
+ super(data, options)
44
+ /** @type {string} Site or app title displayed in the header */ this.title
45
+ /** @type {string} Logo image URL or icon name */ this.logo
46
+ /** @type {Navigation[]} Header action links (CTA, Sign In, etc.) */ this.actions
47
+ /** @type {any|null} Currently active language */ this.lang
48
+ /** @type {any[]} Available languages for switcher */ this.langs
49
+ }
50
+ }
@@ -0,0 +1,48 @@
1
+ import { Model } from '@nan0web/types'
2
+ import Navigation from './Navigation.js'
3
+
4
+ /**
5
+ * HeroModel — OLMUI Model-as-Schema
6
+ * Universal hero/banner section for landing pages.
7
+ * Uses Navigation[] for actions instead of a single CTA.
8
+ */
9
+ export default class HeroModel extends Model {
10
+ static $id = '@nan0web/ui/HeroModel'
11
+
12
+ static title = {
13
+ help: 'Hero main headline',
14
+ placeholder: 'Welcome to Our Platform',
15
+ default: '',
16
+ required: true,
17
+ }
18
+ static description = {
19
+ help: 'Hero sub-headline or description text',
20
+ placeholder: 'Build something amazing...',
21
+ default: '',
22
+ }
23
+ static image = {
24
+ help: 'Hero background or feature image URL',
25
+ placeholder: 'https://...',
26
+ hint: 'image',
27
+ upload: true,
28
+ default: '',
29
+ }
30
+ static actions = {
31
+ help: 'Call-to-action buttons (multiple CTA support)',
32
+ type: 'Navigation[]',
33
+ hint: Navigation,
34
+ default: [],
35
+ }
36
+
37
+ /**
38
+ * @param {Partial<HeroModel> | Record<string, any>} data Model input data.
39
+ * @param {object} [options] Extended options (db, etc.)
40
+ */
41
+ constructor(data = {}, options = {}) {
42
+ super(data, options)
43
+ /** @type {string} Hero main headline */ this.title
44
+ /** @type {string} Hero sub-headline or description text */ this.description
45
+ /** @type {string} Hero background or feature image URL */ this.image
46
+ /** @type {Navigation[]} Call-to-action buttons (multiple CTA support) */ this.actions
47
+ }
48
+ }
@@ -0,0 +1,60 @@
1
+ import { Model } from '@nan0web/types'
2
+
3
+ /**
4
+ * Navigation Model — OLMUI Model-as-Schema
5
+ * Generic recursive navigation structure for all UI platforms (CLI, Web, Mobile).
6
+ */
7
+ export default class Navigation extends Model {
8
+ static $id = '@nan0web/ui/Navigation'
9
+
10
+ static title = {
11
+ help: 'Label for the menu item',
12
+ placeholder: 'Home',
13
+ default: '',
14
+ required: true,
15
+ }
16
+ static href = {
17
+ help: 'URL or internal app route',
18
+ placeholder: '/',
19
+ default: '#',
20
+ }
21
+ static icon = {
22
+ help: 'Icon name/ID',
23
+ placeholder: 'home',
24
+ default: '',
25
+ }
26
+ static image = {
27
+ help: 'Display image or thumbnail',
28
+ placeholder: 'https://...',
29
+ default: '',
30
+ }
31
+ static children = {
32
+ help: 'Nested sub-menu navigation',
33
+ type: 'Navigation[]',
34
+ hint: Navigation,
35
+ default: [],
36
+ }
37
+ static hidden = {
38
+ help: 'Hide from lists/menus',
39
+ type: 'boolean',
40
+ default: false,
41
+ }
42
+
43
+ /**
44
+ * @param {Partial<Navigation> | 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} Label for the menu item */ this.title
50
+ /** @type {string} URL or internal app route */ this.href
51
+ /** @type {string} Icon name/ID */ this.icon
52
+ /** @type {string} Display image or thumbnail */ this.image
53
+ /** @type {Navigation[]} Nested sub-menu navigation */ this.children
54
+ /** @type {boolean} Hide from lists/menus */ this.hidden
55
+
56
+ if (this.children) {
57
+ this.children = this.children.map((item) => new Navigation(item))
58
+ }
59
+ }
60
+ }
@@ -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
  }