@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.
- package/README.md +29 -10
- package/package.json +18 -22
- package/src/Model/index.js +32 -3
- package/src/core/Form/Form.js +8 -7
- package/src/core/Form/Message.js +1 -1
- package/src/core/GeneratorRunner.js +10 -0
- package/src/core/Intent.js +21 -5
- package/src/core/IntentErrorModel.js +6 -1
- package/src/core/Stream.js +16 -5
- package/src/core/index.js +1 -1
- package/src/domain/FooterModel.js +57 -0
- package/src/domain/HeaderModel.js +50 -0
- package/src/domain/HeroModel.js +48 -0
- package/src/domain/Navigation.js +11 -10
- package/src/domain/SandboxModel.js +66 -115
- package/src/domain/ShowcaseAppModel.js +133 -50
- package/src/domain/components/AccordionModel.js +38 -0
- package/src/domain/components/AutocompleteModel.js +11 -21
- package/src/domain/components/BannerModel.js +37 -0
- package/src/domain/components/BreadcrumbModel.js +11 -9
- package/src/domain/components/ButtonModel.js +31 -58
- package/src/domain/components/CommentModel.js +44 -0
- package/src/domain/components/ConfirmModel.js +26 -33
- package/src/domain/components/EmptyStateModel.js +45 -0
- package/src/domain/components/FAQModel.js +32 -0
- package/src/domain/components/FooterConfigModel.js +26 -0
- package/src/domain/components/FooterVisibilityModel.js +48 -0
- package/src/domain/components/GalleryModel.js +36 -0
- package/src/domain/components/HeaderConfigModel.js +26 -0
- package/src/domain/components/HeaderVisibilityModel.js +54 -0
- package/src/domain/components/InputModel.js +21 -41
- package/src/domain/components/PriceModel.js +30 -0
- package/src/domain/components/PricingModel.js +39 -0
- package/src/domain/components/PricingSectionModel.js +32 -0
- package/src/domain/components/ProfileDropdownModel.js +45 -0
- package/src/domain/components/SelectModel.js +11 -21
- package/src/domain/components/SpinnerModel.js +11 -26
- package/src/domain/components/StatsItemModel.js +38 -0
- package/src/domain/components/StatsModel.js +32 -0
- package/src/domain/components/TableModel.js +11 -24
- package/src/domain/components/TabsModel.js +30 -0
- package/src/domain/components/TestimonialModel.js +24 -0
- package/src/domain/components/TimelineItemModel.js +38 -0
- package/src/domain/components/TimelineModel.js +32 -0
- package/src/domain/components/ToastModel.js +24 -51
- package/src/domain/components/TreeModel.js +10 -26
- package/src/domain/components/index.js +34 -0
- package/src/domain/index.js +24 -0
- package/src/index.js +2 -0
- package/src/testing/GalleryGenerator.js +85 -0
- package/src/testing/LogicInspector.js +55 -0
- package/src/testing/SnapshotInspector.js +84 -0
- package/src/testing/VisualAdapter.js +41 -0
- package/src/testing/index.js +3 -0
- package/types/Model/index.d.ts +62 -4
- package/types/core/Form/Form.d.ts +2 -2
- package/types/core/GeneratorRunner.d.ts +4 -0
- package/types/core/Intent.d.ts +31 -3
- package/types/core/IntentErrorModel.d.ts +4 -0
- package/types/core/index.d.ts +1 -1
- package/types/domain/FooterModel.d.ts +52 -0
- package/types/domain/HeaderModel.d.ts +45 -0
- package/types/domain/HeroModel.d.ts +43 -0
- package/types/domain/Navigation.d.ts +10 -9
- package/types/domain/SandboxModel.d.ts +16 -40
- package/types/domain/ShowcaseAppModel.d.ts +26 -54
- package/types/domain/components/AccordionModel.d.ts +33 -0
- package/types/domain/components/AutocompleteModel.d.ts +10 -29
- package/types/domain/components/BannerModel.d.ts +32 -0
- package/types/domain/components/BreadcrumbModel.d.ts +13 -6
- package/types/domain/components/ButtonModel.d.ts +18 -54
- package/types/domain/components/CommentModel.d.ts +39 -0
- package/types/domain/components/ConfirmModel.d.ts +20 -35
- package/types/domain/components/EmptyStateModel.d.ts +40 -0
- package/types/domain/components/FAQModel.d.ts +27 -0
- package/types/domain/components/FooterConfigModel.d.ts +21 -0
- package/types/domain/components/FooterVisibilityModel.d.ts +43 -0
- package/types/domain/components/GalleryModel.d.ts +35 -0
- package/types/domain/components/HeaderConfigModel.d.ts +21 -0
- package/types/domain/components/HeaderVisibilityModel.d.ts +49 -0
- package/types/domain/components/HeroModel.d.ts +24 -0
- package/types/domain/components/InputModel.d.ts +19 -59
- package/types/domain/components/PriceModel.d.ts +25 -0
- package/types/domain/components/PricingModel.d.ts +34 -0
- package/types/domain/components/PricingSectionModel.d.ts +27 -0
- package/types/domain/components/ProfileDropdownModel.d.ts +40 -0
- package/types/domain/components/SelectModel.d.ts +13 -28
- package/types/domain/components/ShowcaseAppModel.d.ts +32 -0
- package/types/domain/components/SpinnerModel.d.ts +10 -27
- package/types/domain/components/StatsItemModel.d.ts +33 -0
- package/types/domain/components/StatsModel.d.ts +27 -0
- package/types/domain/components/TableModel.d.ts +10 -26
- package/types/domain/components/TabsModel.d.ts +28 -0
- package/types/domain/components/TestimonialModel.d.ts +18 -0
- package/types/domain/components/TimelineItemModel.d.ts +33 -0
- package/types/domain/components/TimelineModel.d.ts +27 -0
- package/types/domain/components/ToastModel.d.ts +16 -45
- package/types/domain/components/TreeModel.d.ts +13 -36
- package/types/domain/components/index.d.ts +20 -0
- package/types/domain/index.d.ts +4 -1
- package/types/index.d.ts +1 -0
- package/types/testing/GalleryGenerator.d.ts +1 -0
- package/types/testing/LogicInspector.d.ts +22 -0
- package/types/testing/SnapshotInspector.d.ts +17 -0
- package/types/testing/VisualAdapter.d.ts +15 -0
- package/types/testing/index.d.ts +3 -0
- package/src/README.md.js +0 -436
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// Exports for Component Models
|
|
2
|
+
|
|
3
|
+
// Interactive Components
|
|
2
4
|
export { BreadcrumbModel } from './BreadcrumbModel.js'
|
|
3
5
|
export { ButtonModel } from './ButtonModel.js'
|
|
4
6
|
export { ConfirmModel } from './ConfirmModel.js'
|
|
@@ -9,3 +11,35 @@ export { ToastModel } from './ToastModel.js'
|
|
|
9
11
|
export { SelectModel } from './SelectModel.js'
|
|
10
12
|
export { AutocompleteModel } from './AutocompleteModel.js'
|
|
11
13
|
export { TreeModel } from './TreeModel.js'
|
|
14
|
+
export { TabsModel } from './TabsModel.js'
|
|
15
|
+
export { AccordionModel } from './AccordionModel.js'
|
|
16
|
+
export { FAQModel } from './FAQModel.js'
|
|
17
|
+
export { GalleryModel } from './GalleryModel.js'
|
|
18
|
+
|
|
19
|
+
// Pricing
|
|
20
|
+
export { PriceModel } from './PriceModel.js'
|
|
21
|
+
export { PricingModel } from './PricingModel.js'
|
|
22
|
+
export { PricingSectionModel } from './PricingSectionModel.js'
|
|
23
|
+
|
|
24
|
+
// Hero
|
|
25
|
+
|
|
26
|
+
// Social / Feedback
|
|
27
|
+
export { CommentModel } from './CommentModel.js'
|
|
28
|
+
export { TestimonialModel } from './TestimonialModel.js'
|
|
29
|
+
|
|
30
|
+
// Data Display
|
|
31
|
+
export { StatsItemModel } from './StatsItemModel.js'
|
|
32
|
+
export { StatsModel } from './StatsModel.js'
|
|
33
|
+
export { TimelineItemModel } from './TimelineItemModel.js'
|
|
34
|
+
export { TimelineModel } from './TimelineModel.js'
|
|
35
|
+
|
|
36
|
+
// Visibility Configs
|
|
37
|
+
export { HeaderVisibilityModel } from './HeaderVisibilityModel.js'
|
|
38
|
+
export { HeaderConfigModel } from './HeaderConfigModel.js'
|
|
39
|
+
export { FooterVisibilityModel } from './FooterVisibilityModel.js'
|
|
40
|
+
export { FooterConfigModel } from './FooterConfigModel.js'
|
|
41
|
+
|
|
42
|
+
// Business Critical
|
|
43
|
+
export { EmptyStateModel } from './EmptyStateModel.js'
|
|
44
|
+
export { BannerModel } from './BannerModel.js'
|
|
45
|
+
export { ProfileDropdownModel } from './ProfileDropdownModel.js'
|
package/src/domain/index.js
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
export { SandboxModel } from './SandboxModel.js'
|
|
3
3
|
export { ShowcaseAppModel } from './ShowcaseAppModel.js'
|
|
4
4
|
export { default as Navigation } from './Navigation.js'
|
|
5
|
+
export { Language } from '@nan0web/i18n/src/domain/Language.js'
|
|
6
|
+
|
|
7
|
+
// Layout Models (Phase 1)
|
|
8
|
+
export { default as HeaderModel } from './HeaderModel.js'
|
|
9
|
+
export { default as FooterModel } from './FooterModel.js'
|
|
10
|
+
export { default as HeroModel } from './HeroModel.js'
|
|
5
11
|
|
|
6
12
|
// Component Models
|
|
7
13
|
export {
|
|
@@ -14,4 +20,22 @@ export {
|
|
|
14
20
|
SelectModel,
|
|
15
21
|
AutocompleteModel,
|
|
16
22
|
TreeModel,
|
|
23
|
+
TabsModel,
|
|
24
|
+
AccordionModel,
|
|
25
|
+
GalleryModel,
|
|
26
|
+
PriceModel,
|
|
27
|
+
PricingModel,
|
|
28
|
+
CommentModel,
|
|
29
|
+
TestimonialModel,
|
|
30
|
+
StatsItemModel,
|
|
31
|
+
StatsModel,
|
|
32
|
+
TimelineItemModel,
|
|
33
|
+
TimelineModel,
|
|
34
|
+
HeaderVisibilityModel,
|
|
35
|
+
HeaderConfigModel,
|
|
36
|
+
FooterVisibilityModel,
|
|
37
|
+
FooterConfigModel,
|
|
38
|
+
EmptyStateModel,
|
|
39
|
+
BannerModel,
|
|
40
|
+
ProfileDropdownModel,
|
|
17
41
|
} from './components/index.js'
|
package/src/index.js
CHANGED
|
@@ -11,6 +11,8 @@ import App from './App/index.js'
|
|
|
11
11
|
|
|
12
12
|
export { Frame, FrameProps, Locale, StdIn, StdOut, View, RenderOptions, Model, Component, App }
|
|
13
13
|
export { format } from './format.js'
|
|
14
|
+
export { default as Navigation } from './domain/Navigation.js'
|
|
15
|
+
export { Language } from '@nan0web/i18n/src/domain/Language.js'
|
|
14
16
|
|
|
15
17
|
// export default App
|
|
16
18
|
export { default as FormMessage } from './core/Form/Message.js'
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
import yaml from 'js-yaml'
|
|
5
|
+
import { LogicInspector } from './LogicInspector.js'
|
|
6
|
+
import { VisualAdapter } from './VisualAdapter.js'
|
|
7
|
+
import * as Models from '../domain/index.js'
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
10
|
+
const rootDir = path.resolve(__dirname, '../../')
|
|
11
|
+
const dataDir = path.resolve(rootDir, 'docs/data')
|
|
12
|
+
const snapshotsDir = path.resolve(rootDir, 'snapshots/core')
|
|
13
|
+
|
|
14
|
+
// Clean before generation
|
|
15
|
+
if (fs.existsSync(snapshotsDir)) fs.rmSync(snapshotsDir, { recursive: true, force: true })
|
|
16
|
+
fs.mkdirSync(snapshotsDir, { recursive: true })
|
|
17
|
+
|
|
18
|
+
const groups = {
|
|
19
|
+
Actions: ['Button', 'Toggle'],
|
|
20
|
+
Forms: ['Input', 'Select', 'Slider', 'Autocomplete', 'Color', 'Shadow'],
|
|
21
|
+
Data: ['Accordion', 'Card', 'Sortable', 'Table', 'Tree', 'CodeBlock', 'Markdown', 'Badge'],
|
|
22
|
+
Feedback: ['Alert', 'Confirm', 'Modal', 'ProgressBar', 'Spinner', 'Toast'],
|
|
23
|
+
System: ['LangSelect', 'ThemeToggle'],
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getCategory(comp) {
|
|
27
|
+
for (const [cat, comps] of Object.entries(groups)) {
|
|
28
|
+
if (comps.includes(comp)) return cat
|
|
29
|
+
}
|
|
30
|
+
return 'Other'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function generate() {
|
|
34
|
+
const langs = fs.readdirSync(dataDir).filter(d => fs.statSync(path.join(dataDir, d)).isDirectory() && d !== '_')
|
|
35
|
+
|
|
36
|
+
for (const lang of langs) {
|
|
37
|
+
const langDir = path.join(dataDir, lang)
|
|
38
|
+
const components = fs.readdirSync(langDir).filter(f => f.endsWith('.yaml'))
|
|
39
|
+
|
|
40
|
+
for (const file of components) {
|
|
41
|
+
const compName = file.replace('.yaml', '')
|
|
42
|
+
const category = getCategory(compName)
|
|
43
|
+
const text = fs.readFileSync(path.join(langDir, file), 'utf-8')
|
|
44
|
+
const data = yaml.load(text)
|
|
45
|
+
|
|
46
|
+
// Variations are in data.content
|
|
47
|
+
const variations = data.content || []
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < variations.length; i++) {
|
|
50
|
+
const varData = variations[i][compName] || variations[i]
|
|
51
|
+
|
|
52
|
+
// Get variation name from 'content', 'title', 'message', or fallback to index
|
|
53
|
+
let varName = varData.content || varData.title || varData.message || `var${i + 1}`
|
|
54
|
+
if (typeof varName !== 'string') varName = `var${i + 1}`
|
|
55
|
+
|
|
56
|
+
// Clean filename: allow Ukrainian, but replace spaces/special chars with single underscore
|
|
57
|
+
const safeVarName = varName
|
|
58
|
+
.trim()
|
|
59
|
+
.toLowerCase()
|
|
60
|
+
.replace(/[./\\:]/g, '_') // Replace paths/dots
|
|
61
|
+
.replace(/\s+/g, '_') // Replace spaces
|
|
62
|
+
.replace(/_{2,}/g, '_') // No double underscores
|
|
63
|
+
.slice(0, 50) // Max length
|
|
64
|
+
|
|
65
|
+
// Logic Capture
|
|
66
|
+
/** @type {() => AsyncGenerator<import('../core/Intent.js').Intent, import('../core/Intent.js').ResultIntent, any>} */
|
|
67
|
+
const modelStream = async function* () {
|
|
68
|
+
yield { type: 'render', component: `ui-${compName.toLowerCase()}`, props: varData }
|
|
69
|
+
return { type: 'result', data: { ok: true } }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const intents = await LogicInspector.capture(modelStream())
|
|
73
|
+
const snapshot = intents.map(it => VisualAdapter.render(it)).join('\n')
|
|
74
|
+
|
|
75
|
+
const outPath = path.join(snapshotsDir, lang, category, compName)
|
|
76
|
+
if (!fs.existsSync(outPath)) fs.mkdirSync(outPath, { recursive: true })
|
|
77
|
+
|
|
78
|
+
fs.writeFileSync(path.join(outPath, `${safeVarName}.txt`), snapshot)
|
|
79
|
+
console.log(`📸 Generated snapshot for ${lang}/${category}/${compName}/${safeVarName}`)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
generate().catch(console.error)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { runGenerator } from '../core/GeneratorRunner.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LogicInspector
|
|
5
|
+
*
|
|
6
|
+
* Базовий клас для захоплення "Логічних зліпків" (Intent Stream) будь-яких моделей OLMUI.
|
|
7
|
+
* Дозволяє виконувати чисто-логічне тестування без прив'язки до рендерингу.
|
|
8
|
+
*/
|
|
9
|
+
export class LogicInspector {
|
|
10
|
+
/**
|
|
11
|
+
* Виконує генератор моделі та записує послідовність усіх інтенцій.
|
|
12
|
+
* @param {AsyncGenerator<import('../core/Intent.js').Intent, import('../core/Intent.js').ResultIntent, import('../core/Intent.js').IntentResponse>} modelStream - результат виклику model.run()
|
|
13
|
+
* @param {object} options
|
|
14
|
+
* @param {Array<any> | ((locale: string) => Array<any>)} [options.inputs] - черга вхідних значень для askIntent
|
|
15
|
+
* @param {string} [options.locale] - локаль для тестів
|
|
16
|
+
* @param {function} [options.t] - функція перекладу
|
|
17
|
+
* @returns {Promise<Array<any>>} Intent Stream Log
|
|
18
|
+
*/
|
|
19
|
+
static async capture(modelStream, { inputs = [], locale = 'uk', t = (k) => k } = {}) {
|
|
20
|
+
const intents = []
|
|
21
|
+
let inputIdx = 0
|
|
22
|
+
const resolvedInputs = typeof inputs === 'function' ? inputs(locale) : inputs
|
|
23
|
+
|
|
24
|
+
const recordingAdapter = {
|
|
25
|
+
/** @param {import('../core/Intent.js').AskIntent} i */
|
|
26
|
+
ask: async (i) => {
|
|
27
|
+
const value = resolvedInputs[inputIdx++]
|
|
28
|
+
const entry = { type: 'ask', field: i.field, schema: i.schema, input: value }
|
|
29
|
+
intents.push(entry)
|
|
30
|
+
return { value }
|
|
31
|
+
},
|
|
32
|
+
/** @param {import('../core/Intent.js').LogIntent} i */
|
|
33
|
+
log: async (i) => {
|
|
34
|
+
intents.push({ type: 'log', level: i.level || 'info', message: i.message })
|
|
35
|
+
},
|
|
36
|
+
/** @param {import('../core/Intent.js').ProgressIntent} i */
|
|
37
|
+
progress: async (i) => {
|
|
38
|
+
intents.push({ type: 'progress', message: i.message })
|
|
39
|
+
},
|
|
40
|
+
/** @param {import('../core/Intent.js').RenderIntent} i */
|
|
41
|
+
render: async (i) => {
|
|
42
|
+
intents.push({ type: 'render', component: i.component, props: i.props })
|
|
43
|
+
},
|
|
44
|
+
/** @param {import('../core/Intent.js').ResultIntent} i */
|
|
45
|
+
result: async (i) => {
|
|
46
|
+
intents.push({ type: 'result', data: i.data })
|
|
47
|
+
},
|
|
48
|
+
t
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Викликаємо базовий раннер з нашим записуючим адаптером
|
|
52
|
+
await runGenerator(modelStream, recordingAdapter)
|
|
53
|
+
return intents
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SnapshotInspector
|
|
6
|
+
*
|
|
7
|
+
* Рев'ювер для автоматичної перевірки Snapshot-зліпків на наявність артефактів,
|
|
8
|
+
* неперекладених ключів та структурних помилок.
|
|
9
|
+
* Реалізує правила "Zero-Hallucination Snapshot Validation".
|
|
10
|
+
*/
|
|
11
|
+
export class SnapshotInspector {
|
|
12
|
+
/**
|
|
13
|
+
* Перевіряє вміст одного снепшоту.
|
|
14
|
+
* @param {string} content - Текстовий вміст .txt файлу галереї
|
|
15
|
+
* @param {string} locale - Локаль (uk, en)
|
|
16
|
+
* @param {string} [filename] - Ім'я файлу для перевірки на "глюки" (підкреслення)
|
|
17
|
+
* @returns {object} { score, errors }
|
|
18
|
+
*/
|
|
19
|
+
static inspect(content, locale = 'uk', filename = '') {
|
|
20
|
+
const errors = []
|
|
21
|
+
const lines = content.split('\n')
|
|
22
|
+
|
|
23
|
+
// 0. Перевірка імені файлу (на прохання архітектора)
|
|
24
|
+
if (filename) {
|
|
25
|
+
if (/__|--/.test(filename)) {
|
|
26
|
+
errors.push(`Filename "${filename}" has multiple consecutive separators (glitch detected).`)
|
|
27
|
+
}
|
|
28
|
+
if (filename.length < 3) {
|
|
29
|
+
errors.push(`Filename "${filename}" is too short.`)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
lines.forEach((line, index) => {
|
|
34
|
+
const lineNum = index + 1
|
|
35
|
+
const trimmed = line.trim()
|
|
36
|
+
|
|
37
|
+
// 1. Неперекладені i18n ключі (крапки в словах)
|
|
38
|
+
// Виключаємо імена компонентів, атрибути RENDER-тегів, та числа з крапкою (версії, координати)
|
|
39
|
+
const isAttribute = trimmed.includes('="')
|
|
40
|
+
const isDotNumber = /^-?\d+\.\d+$/.test(trimmed)
|
|
41
|
+
|
|
42
|
+
if (/\w+\.\w+/.test(line) && !line.includes('ui-') && !line.includes('http') && !isAttribute && !isDotNumber) {
|
|
43
|
+
errors.push(`Line ${lineNum}: Possible untranslated key found: "${trimmed}"`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 2. Технічні артефакти
|
|
47
|
+
if (line.includes('[object Object]')) {
|
|
48
|
+
errors.push(`Line ${lineNum}: Critical artifact "[object Object]" found.`)
|
|
49
|
+
}
|
|
50
|
+
if (line.includes('undefined')) {
|
|
51
|
+
errors.push(`Line ${lineNum}: Critical artifact "undefined" found.`)
|
|
52
|
+
}
|
|
53
|
+
if (line.includes('NaN')) {
|
|
54
|
+
errors.push(`Line ${lineNum}: Critical artifact "NaN" found.`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 3. Англійські слова в українській локалі (базові)
|
|
58
|
+
if (locale === 'uk') {
|
|
59
|
+
const enWords = ['Back', 'Select', 'Cancel', 'Submit', 'Confirm', 'Delete', 'Information', 'Success', 'Warning', 'Error']
|
|
60
|
+
enWords.forEach(word => {
|
|
61
|
+
const regex = new RegExp(`\\b${word}\\b`, 'i')
|
|
62
|
+
// Перевіряємо тільки якщо це не частина тегу <ui-...> або атрибут
|
|
63
|
+
if (regex.test(line)) {
|
|
64
|
+
const isTag = line.includes(`<ui-${word.toLowerCase()}`) || line.includes(`[RENDER] <ui-`)
|
|
65
|
+
const isAsk = line.includes(`[ASK] ${word}`)
|
|
66
|
+
if (!isTag && !isAsk && !isAttribute) {
|
|
67
|
+
errors.push(`Line ${lineNum}: English word "${word}" found in "uk" locale.`)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 4. Помилки роутингу
|
|
74
|
+
if (line.includes('🚨 Path not found')) {
|
|
75
|
+
errors.push(`Line ${lineNum}: Routing error "Path not found".`)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
score: errors.length === 0 ? 100 : Math.max(0, 100 - errors.length * 10),
|
|
81
|
+
errors
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VisualAdapter (Base)
|
|
3
|
+
*
|
|
4
|
+
* Базовий клас для візуальної трансформації інтенцій OLMUI.
|
|
5
|
+
* Використовувана у @nan0web/ui як фундамент для спеціалізованих рендерерів.
|
|
6
|
+
*/
|
|
7
|
+
export class VisualAdapter {
|
|
8
|
+
/**
|
|
9
|
+
* Конвертує одну інтенцію у просте текстове представлення.
|
|
10
|
+
* @param {object} intent - Intent entry from LogicInspector
|
|
11
|
+
* @param {function} [t] - i18n translate function
|
|
12
|
+
* @returns {string} Raw description
|
|
13
|
+
*/
|
|
14
|
+
static render(intent, t = (k) => k) {
|
|
15
|
+
switch (intent.type) {
|
|
16
|
+
case 'ask':
|
|
17
|
+
return `[ASK] ${intent.field}: ${intent.input !== undefined ? intent.input : '...'}`
|
|
18
|
+
case 'progress':
|
|
19
|
+
return `[PROGRESS] ${intent.message || ''}`
|
|
20
|
+
case 'log':
|
|
21
|
+
return `[LOG ${intent.level?.toUpperCase() || 'INFO'}] ${typeof intent.message === 'object' ? JSON.stringify(intent.message) : intent.message}`
|
|
22
|
+
case 'render': {
|
|
23
|
+
const { content, ...propsData } = intent.props || {}
|
|
24
|
+
const props = Object.entries(propsData)
|
|
25
|
+
.map(([k, v]) => ` ${k}="${typeof v === 'object' ? JSON.stringify(v) : v}"`)
|
|
26
|
+
.join('\n')
|
|
27
|
+
|
|
28
|
+
if (content) {
|
|
29
|
+
const attrs = props ? `\n${props}\n` : ' '
|
|
30
|
+
return `[RENDER] <${intent.component}${attrs}>${content}</${intent.component}>`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return `[RENDER] <${intent.component}${props ? '\n' + props + '\n' : ''}>`
|
|
34
|
+
}
|
|
35
|
+
case 'result':
|
|
36
|
+
return `[RESULT] ${JSON.stringify(intent.data)}`
|
|
37
|
+
default:
|
|
38
|
+
return `[UNKNOWN: ${intent.type}] ${JSON.stringify(intent)}`
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
package/types/Model/index.d.ts
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
export { User };
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export
|
|
2
|
+
export const HeaderModel: typeof DomainModels.HeaderModel;
|
|
3
|
+
export const FooterModel: typeof DomainModels.FooterModel;
|
|
4
|
+
export const HeroModel: typeof DomainModels.HeroModel;
|
|
5
|
+
export const ButtonModel: typeof DomainModels.ButtonModel;
|
|
6
|
+
export const ConfirmModel: typeof DomainModels.ConfirmModel;
|
|
7
|
+
export const InputModel: typeof DomainModels.InputModel;
|
|
8
|
+
export const SpinnerModel: typeof DomainModels.SpinnerModel;
|
|
9
|
+
export const TableModel: typeof DomainModels.TableModel;
|
|
10
|
+
export const ToastModel: typeof DomainModels.ToastModel;
|
|
11
|
+
export const SelectModel: typeof DomainModels.SelectModel;
|
|
12
|
+
export const AutocompleteModel: typeof DomainModels.AutocompleteModel;
|
|
13
|
+
export const TreeModel: typeof DomainModels.TreeModel;
|
|
14
|
+
export const TabsModel: typeof DomainModels.TabsModel;
|
|
15
|
+
export const AccordionModel: typeof DomainModels.AccordionModel;
|
|
16
|
+
export const GalleryModel: typeof DomainModels.GalleryModel;
|
|
17
|
+
export const PriceModel: typeof DomainModels.PriceModel;
|
|
18
|
+
export const PricingModel: typeof DomainModels.PricingModel;
|
|
19
|
+
export const CommentModel: typeof DomainModels.CommentModel;
|
|
20
|
+
export const TestimonialModel: typeof DomainModels.TestimonialModel;
|
|
21
|
+
export const StatsItemModel: typeof DomainModels.StatsItemModel;
|
|
22
|
+
export const StatsModel: typeof DomainModels.StatsModel;
|
|
23
|
+
export const TimelineItemModel: typeof DomainModels.TimelineItemModel;
|
|
24
|
+
export const TimelineModel: typeof DomainModels.TimelineModel;
|
|
25
|
+
export default Model;
|
|
6
26
|
import User from './User/User.js';
|
|
27
|
+
import * as DomainModels from '../domain/index.js';
|
|
28
|
+
declare const Model: {
|
|
29
|
+
SandboxModel: typeof DomainModels.SandboxModel;
|
|
30
|
+
ShowcaseAppModel: typeof DomainModels.ShowcaseAppModel;
|
|
31
|
+
Navigation: typeof DomainModels.Navigation;
|
|
32
|
+
Language: any;
|
|
33
|
+
HeaderModel: typeof DomainModels.HeaderModel;
|
|
34
|
+
FooterModel: typeof DomainModels.FooterModel;
|
|
35
|
+
HeroModel: typeof DomainModels.HeroModel;
|
|
36
|
+
ButtonModel: typeof DomainModels.ButtonModel;
|
|
37
|
+
ConfirmModel: typeof DomainModels.ConfirmModel;
|
|
38
|
+
InputModel: typeof DomainModels.InputModel;
|
|
39
|
+
SpinnerModel: typeof DomainModels.SpinnerModel;
|
|
40
|
+
TableModel: typeof DomainModels.TableModel;
|
|
41
|
+
ToastModel: typeof DomainModels.ToastModel;
|
|
42
|
+
SelectModel: typeof DomainModels.SelectModel;
|
|
43
|
+
AutocompleteModel: typeof DomainModels.AutocompleteModel;
|
|
44
|
+
TreeModel: typeof DomainModels.TreeModel;
|
|
45
|
+
TabsModel: typeof DomainModels.TabsModel;
|
|
46
|
+
AccordionModel: typeof DomainModels.AccordionModel;
|
|
47
|
+
GalleryModel: typeof DomainModels.GalleryModel;
|
|
48
|
+
PriceModel: typeof DomainModels.PriceModel;
|
|
49
|
+
PricingModel: typeof DomainModels.PricingModel;
|
|
50
|
+
CommentModel: typeof DomainModels.CommentModel;
|
|
51
|
+
TestimonialModel: typeof DomainModels.TestimonialModel;
|
|
52
|
+
StatsItemModel: typeof DomainModels.StatsItemModel;
|
|
53
|
+
StatsModel: typeof DomainModels.StatsModel;
|
|
54
|
+
TimelineItemModel: typeof DomainModels.TimelineItemModel;
|
|
55
|
+
TimelineModel: typeof DomainModels.TimelineModel;
|
|
56
|
+
HeaderVisibilityModel: typeof DomainModels.HeaderVisibilityModel;
|
|
57
|
+
HeaderConfigModel: typeof DomainModels.HeaderConfigModel;
|
|
58
|
+
FooterVisibilityModel: typeof DomainModels.FooterVisibilityModel;
|
|
59
|
+
FooterConfigModel: typeof DomainModels.FooterConfigModel;
|
|
60
|
+
EmptyStateModel: typeof DomainModels.EmptyStateModel;
|
|
61
|
+
BannerModel: typeof DomainModels.BannerModel;
|
|
62
|
+
ProfileDropdownModel: typeof DomainModels.ProfileDropdownModel;
|
|
63
|
+
User: typeof User;
|
|
64
|
+
};
|
|
@@ -89,9 +89,9 @@ export default class UIForm extends FormMessage {
|
|
|
89
89
|
/**
|
|
90
90
|
* Validates the entire form.
|
|
91
91
|
*
|
|
92
|
-
* @returns {
|
|
92
|
+
* @returns {any}
|
|
93
93
|
*/
|
|
94
|
-
validate():
|
|
94
|
+
validate(): any;
|
|
95
95
|
/**
|
|
96
96
|
* Validates a single field.
|
|
97
97
|
*
|
|
@@ -32,6 +32,10 @@ export type AdapterHandlers = {
|
|
|
32
32
|
* Handler for 'log' intents. Optional (defaults to no-op).
|
|
33
33
|
*/
|
|
34
34
|
log?: ((intent: import("./Intent.js").LogIntent) => void | Promise<void>) | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Handler for 'render' intents (visual component injection). Optional.
|
|
37
|
+
*/
|
|
38
|
+
render?: ((intent: import("./Intent.js").RenderIntent) => void | Promise<void>) | undefined;
|
|
35
39
|
/**
|
|
36
40
|
* Handler for the final 'result'. Optional (defaults to no-op).
|
|
37
41
|
*/
|
package/types/core/Intent.d.ts
CHANGED
|
@@ -53,9 +53,17 @@ export function validateIntent(intent: any): intent is Intent;
|
|
|
53
53
|
* @property {'result'} type
|
|
54
54
|
* @property {*} data - The raw result data (JSON-serializable).
|
|
55
55
|
*/
|
|
56
|
+
/**
|
|
57
|
+
* Model requests rendering of a pure UI component (Header, Footer, Static Map).
|
|
58
|
+
* No response expected from the logic loop.
|
|
59
|
+
* @typedef {Object} RenderIntent
|
|
60
|
+
* @property {'render'} type
|
|
61
|
+
* @property {string} component - Component name (e.g. 'App.Layout.Header').
|
|
62
|
+
* @property {object} props - Static props for the component.
|
|
63
|
+
*/
|
|
56
64
|
/**
|
|
57
65
|
* Union of all possible yielded intents.
|
|
58
|
-
* @typedef {AskIntent | ProgressIntent | LogIntent} Intent
|
|
66
|
+
* @typedef {AskIntent | ProgressIntent | LogIntent | RenderIntent} Intent
|
|
59
67
|
*/
|
|
60
68
|
/**
|
|
61
69
|
* Response to an AskIntent. Adapter provides the collected value.
|
|
@@ -82,7 +90,7 @@ export function validateIntent(intent: any): intent is Intent;
|
|
|
82
90
|
* Union of all possible responses an Adapter can send back via iterator.next().
|
|
83
91
|
* @typedef {AskResponse | AbortResponse | undefined} IntentResponse
|
|
84
92
|
*/
|
|
85
|
-
export const INTENT_TYPES: readonly ["ask", "progress", "log"];
|
|
93
|
+
export const INTENT_TYPES: readonly ["ask", "progress", "log", "render"];
|
|
86
94
|
export function ask(field: string, schema: object | Function): AskIntent;
|
|
87
95
|
export function progress(message: any): {
|
|
88
96
|
type: string;
|
|
@@ -93,6 +101,11 @@ export function log(level: any, message: any, data?: {}): {
|
|
|
93
101
|
level: any;
|
|
94
102
|
message: any;
|
|
95
103
|
};
|
|
104
|
+
export function render(component: any, props?: {}): {
|
|
105
|
+
type: string;
|
|
106
|
+
component: any;
|
|
107
|
+
props: {};
|
|
108
|
+
};
|
|
96
109
|
export function result(data: any): {
|
|
97
110
|
type: string;
|
|
98
111
|
data: any;
|
|
@@ -186,10 +199,25 @@ export type ResultIntent = {
|
|
|
186
199
|
*/
|
|
187
200
|
data: any;
|
|
188
201
|
};
|
|
202
|
+
/**
|
|
203
|
+
* Model requests rendering of a pure UI component (Header, Footer, Static Map).
|
|
204
|
+
* No response expected from the logic loop.
|
|
205
|
+
*/
|
|
206
|
+
export type RenderIntent = {
|
|
207
|
+
type: "render";
|
|
208
|
+
/**
|
|
209
|
+
* - Component name (e.g. 'App.Layout.Header').
|
|
210
|
+
*/
|
|
211
|
+
component: string;
|
|
212
|
+
/**
|
|
213
|
+
* - Static props for the component.
|
|
214
|
+
*/
|
|
215
|
+
props: object;
|
|
216
|
+
};
|
|
189
217
|
/**
|
|
190
218
|
* Union of all possible yielded intents.
|
|
191
219
|
*/
|
|
192
|
-
export type Intent = AskIntent | ProgressIntent | LogIntent;
|
|
220
|
+
export type Intent = AskIntent | ProgressIntent | LogIntent | RenderIntent;
|
|
193
221
|
/**
|
|
194
222
|
* Response to an AskIntent. Adapter provides the collected value.
|
|
195
223
|
* The value MUST conform to the type described in the requested FieldSchema.
|
package/types/core/index.d.ts
CHANGED
|
@@ -13,4 +13,4 @@ import UIForm from './Form/Form.js';
|
|
|
13
13
|
export { UIStream, UIStream as UiStream, StreamEntry, StreamEntry as UiStreamEntry, UIForm, UIForm as UiForm };
|
|
14
14
|
export { default as Error, CancelError } from "./Error/index.js";
|
|
15
15
|
export { runFlow, flow, View, Prompt, Stream, Alert, Toast, Badge, Text, Table, Input, Select, Confirm, Multiselect, Mask, Password, Spinner, Progress, default as Flow } from "./Flow.js";
|
|
16
|
-
export { validateIntent, ask, progress, log, result, INTENT_TYPES, isModelSchema } from "./Intent.js";
|
|
16
|
+
export { validateIntent, ask, progress, log, render, result, INTENT_TYPES, isModelSchema } from "./Intent.js";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FooterModel — OLMUI Model-as-Schema
|
|
3
|
+
* Universal footer structure: copyright, version, license, navigation, sharing, languages.
|
|
4
|
+
*/
|
|
5
|
+
export default class FooterModel extends Model {
|
|
6
|
+
static $id: string;
|
|
7
|
+
static copyright: {
|
|
8
|
+
help: string;
|
|
9
|
+
placeholder: string;
|
|
10
|
+
default: string;
|
|
11
|
+
};
|
|
12
|
+
static version: {
|
|
13
|
+
help: string;
|
|
14
|
+
placeholder: string;
|
|
15
|
+
default: string;
|
|
16
|
+
};
|
|
17
|
+
static license: {
|
|
18
|
+
help: string;
|
|
19
|
+
placeholder: string;
|
|
20
|
+
default: string;
|
|
21
|
+
};
|
|
22
|
+
static nav: {
|
|
23
|
+
help: string;
|
|
24
|
+
type: string;
|
|
25
|
+
hint: typeof Navigation;
|
|
26
|
+
default: never[];
|
|
27
|
+
};
|
|
28
|
+
static share: {
|
|
29
|
+
help: string;
|
|
30
|
+
type: string;
|
|
31
|
+
hint: typeof Navigation;
|
|
32
|
+
default: never[];
|
|
33
|
+
};
|
|
34
|
+
static langs: {
|
|
35
|
+
help: string;
|
|
36
|
+
type: string;
|
|
37
|
+
default: never[];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* @param {Partial<FooterModel> | Record<string, any>} data Model input data.
|
|
41
|
+
* @param {object} [options] Extended options (db, etc.)
|
|
42
|
+
*/
|
|
43
|
+
constructor(data?: Partial<FooterModel> | Record<string, any>, options?: object);
|
|
44
|
+
/** @type {string} Copyright text */ copyright: string;
|
|
45
|
+
/** @type {string} Application version string */ version: string;
|
|
46
|
+
/** @type {string} License type */ license: string;
|
|
47
|
+
/** @type {Navigation[]} Footer navigation links */ nav: Navigation[];
|
|
48
|
+
/** @type {Navigation[]} Social sharing links */ share: Navigation[];
|
|
49
|
+
/** @type {any[]} Available languages for switcher */ langs: any[];
|
|
50
|
+
}
|
|
51
|
+
import { Model } from '@nan0web/types';
|
|
52
|
+
import Navigation from './Navigation.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HeaderModel — OLMUI Model-as-Schema
|
|
3
|
+
* Universal header structure: logo, navigation, language switcher, actions.
|
|
4
|
+
*/
|
|
5
|
+
export default class HeaderModel extends Model {
|
|
6
|
+
static $id: string;
|
|
7
|
+
static title: {
|
|
8
|
+
help: string;
|
|
9
|
+
placeholder: string;
|
|
10
|
+
default: string;
|
|
11
|
+
};
|
|
12
|
+
static logo: {
|
|
13
|
+
help: string;
|
|
14
|
+
placeholder: string;
|
|
15
|
+
default: string;
|
|
16
|
+
};
|
|
17
|
+
static actions: {
|
|
18
|
+
help: string;
|
|
19
|
+
type: string;
|
|
20
|
+
hint: typeof Navigation;
|
|
21
|
+
default: never[];
|
|
22
|
+
};
|
|
23
|
+
static lang: {
|
|
24
|
+
help: string;
|
|
25
|
+
type: string;
|
|
26
|
+
default: null;
|
|
27
|
+
};
|
|
28
|
+
static langs: {
|
|
29
|
+
help: string;
|
|
30
|
+
type: string;
|
|
31
|
+
default: never[];
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* @param {Partial<HeaderModel> | Record<string, any>} data Model input data.
|
|
35
|
+
* @param {object} [options] Extended options (db, etc.)
|
|
36
|
+
*/
|
|
37
|
+
constructor(data?: Partial<HeaderModel> | Record<string, any>, options?: object);
|
|
38
|
+
/** @type {string} Site or app title displayed in the header */ title: string;
|
|
39
|
+
/** @type {string} Logo image URL or icon name */ logo: string;
|
|
40
|
+
/** @type {Navigation[]} Header action links (CTA, Sign In, etc.) */ actions: Navigation[];
|
|
41
|
+
/** @type {any|null} Currently active language */ lang: any | null;
|
|
42
|
+
/** @type {any[]} Available languages for switcher */ langs: any[];
|
|
43
|
+
}
|
|
44
|
+
import { Model } from '@nan0web/types';
|
|
45
|
+
import Navigation from './Navigation.js';
|