@nan0web/ui 1.1.0 → 1.5.2
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 +48 -14
- package/package.json +25 -13
- package/src/App/Command/DepsCommand.js +5 -9
- package/src/App/Core/CoreApp.js +18 -17
- package/src/App/Core/UI.js +11 -15
- package/src/App/Core/Widget.js +6 -10
- package/src/App/Core/index.js +3 -3
- package/src/App/Scenario.js +4 -4
- package/src/App/User/Command/Message.js +1 -1
- package/src/App/User/Command/index.js +1 -1
- package/src/App/User/UserApp.js +28 -21
- package/src/App/User/UserUI.js +2 -2
- package/src/App/User/index.js +2 -2
- package/src/App/index.js +5 -10
- package/src/Component/Process/Input.js +10 -17
- package/src/Component/Process/Process.js +3 -5
- package/src/Component/Process/index.js +2 -2
- package/src/Component/SortableList/SortableList.js +100 -0
- package/src/Component/SortableList/index.js +3 -0
- package/src/Component/Welcome/Input.js +2 -4
- package/src/Component/Welcome/Welcome.js +5 -9
- package/src/Component/Welcome/index.js +2 -2
- package/src/Component/index.js +5 -3
- package/src/Frame/Frame.js +163 -146
- package/src/Frame/Props.js +20 -20
- package/src/Locale.js +17 -18
- package/src/Model/User/User.js +3 -6
- package/src/Model/index.js +1 -1
- package/src/README.md.js +119 -94
- package/src/StdIn.js +8 -12
- package/src/StdOut.js +23 -27
- package/src/View/RenderOptions.js +1 -1
- package/src/View/View.js +40 -36
- package/src/core/Error/CancelError.js +2 -2
- package/src/core/Error/index.js +3 -5
- package/src/core/Flow.js +347 -0
- package/src/core/Form/Form.js +35 -33
- package/src/core/Form/Input.js +21 -6
- package/src/core/Form/Message.js +3 -6
- package/src/core/Form/index.js +4 -8
- package/src/core/InputAdapter.js +4 -6
- package/src/core/Message/Message.js +9 -12
- package/src/core/Message/OutputMessage.js +19 -17
- package/src/core/Message/index.js +2 -2
- package/src/core/OutputAdapter.js +12 -10
- package/src/core/Stream.js +1 -1
- package/src/core/StreamEntry.js +2 -2
- package/src/core/UiAdapter.js +31 -30
- package/src/core/index.js +33 -10
- package/src/functions.js +8 -15
- package/src/index.js +21 -32
- package/types/App/Command/DepsCommand.d.ts +1 -1
- package/types/App/Command/Options.d.ts +37 -40
- package/types/App/Command/index.d.ts +6 -6
- package/types/App/Core/CoreApp.d.ts +2 -2
- package/types/App/Core/UI.d.ts +4 -4
- package/types/App/Core/Widget.d.ts +4 -4
- package/types/App/Core/index.d.ts +3 -3
- package/types/App/Scenario.d.ts +1 -1
- package/types/App/User/Command/Message.d.ts +1 -1
- package/types/App/User/Command/Options.d.ts +29 -29
- package/types/App/User/Command/index.d.ts +1 -1
- package/types/App/User/UserApp.d.ts +5 -5
- package/types/App/User/index.d.ts +2 -2
- package/types/App/index.d.ts +4 -4
- package/types/Component/Process/Process.d.ts +2 -2
- package/types/Component/Process/index.d.ts +2 -2
- package/types/Component/SortableList/SortableList.d.ts +58 -0
- package/types/Component/SortableList/index.d.ts +2 -0
- package/types/Component/Welcome/Input.d.ts +1 -1
- package/types/Component/Welcome/Welcome.d.ts +1 -1
- package/types/Component/Welcome/index.d.ts +2 -2
- package/types/Component/index.d.ts +5 -3
- package/types/Frame/Frame.d.ts +1 -1
- package/types/Frame/Props.d.ts +1 -1
- package/types/Model/index.d.ts +1 -1
- package/types/StdIn.d.ts +2 -2
- package/types/StdOut.d.ts +1 -1
- package/types/View/View.d.ts +7 -7
- package/types/core/Error/index.d.ts +1 -1
- package/types/core/Flow.d.ts +320 -0
- package/types/core/Form/Form.d.ts +2 -2
- package/types/core/Form/Input.d.ts +11 -0
- package/types/core/Form/Message.d.ts +1 -1
- package/types/core/Form/index.d.ts +3 -3
- package/types/core/InputAdapter.d.ts +2 -2
- package/types/core/Intent.d.ts +65 -68
- package/types/core/Message/InputMessage.d.ts +65 -65
- package/types/core/Message/Message.d.ts +1 -1
- package/types/core/Message/OutputMessage.d.ts +1 -1
- package/types/core/Message/index.d.ts +2 -2
- package/types/core/Stream.d.ts +1 -1
- package/types/core/UiAdapter.d.ts +5 -5
- package/types/core/index.d.ts +4 -3
- package/types/index.d.ts +10 -10
package/src/Locale.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { typeOf, notEmpty } from
|
|
1
|
+
import { typeOf, notEmpty } from '@nan0web/types'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Handles locale-specific formatting for different data types.
|
|
@@ -42,14 +42,14 @@ export default class Locale {
|
|
|
42
42
|
*/
|
|
43
43
|
constructor(props = {}) {
|
|
44
44
|
const {
|
|
45
|
-
lang =
|
|
46
|
-
collate =
|
|
47
|
-
ctype =
|
|
48
|
-
messages =
|
|
49
|
-
monetary =
|
|
50
|
-
numeric =
|
|
51
|
-
time =
|
|
52
|
-
all =
|
|
45
|
+
lang = '',
|
|
46
|
+
collate = '',
|
|
47
|
+
ctype = '',
|
|
48
|
+
messages = '',
|
|
49
|
+
monetary = '',
|
|
50
|
+
numeric = '',
|
|
51
|
+
time = '',
|
|
52
|
+
all = 'uk_UA.UTF-8',
|
|
53
53
|
} = props
|
|
54
54
|
this.lang = lang
|
|
55
55
|
this.collate = collate
|
|
@@ -70,9 +70,9 @@ export default class Locale {
|
|
|
70
70
|
format(type, options) {
|
|
71
71
|
if (Number === type || typeOf(Number)(type)) {
|
|
72
72
|
/**
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
* new (locales?: LocalesArgument, options?: NumberFormatOptions): NumberFormat;
|
|
74
|
+
* (locales?: LocalesArgument, options?: NumberFormatOptions): NumberFormat;
|
|
75
|
+
* supportedLocalesOf(locales: LocalesArgument, options?: NumberFormatOptions): string[];
|
|
76
76
|
*/
|
|
77
77
|
/**
|
|
78
78
|
* localeMatcher?: "lookup" | "best fit" | undefined;
|
|
@@ -85,18 +85,18 @@ export default class Locale {
|
|
|
85
85
|
* maximumFractionDigits?: number | undefined;
|
|
86
86
|
* minimumSignificantDigits?: number | undefined;
|
|
87
87
|
* maximumSignificantDigits?: number | undefined;
|
|
88
|
-
|
|
88
|
+
*/
|
|
89
89
|
const locales = [this.numeric, this.all, this.lang].filter(notEmpty)
|
|
90
90
|
return (value) => {
|
|
91
91
|
return new Intl.NumberFormat(locales, options).format(value)
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
-
if (
|
|
94
|
+
if ('string' === typeof type) {
|
|
95
95
|
const locales = [this.monetary, this.numeric, this.all, this.lang].filter(notEmpty)
|
|
96
96
|
return (value) => {
|
|
97
97
|
return new Intl.NumberFormat(locales, {
|
|
98
|
-
style:
|
|
99
|
-
currency: type ===
|
|
98
|
+
style: 'currency',
|
|
99
|
+
currency: type === 'currency' ? options.currency : type,
|
|
100
100
|
...options,
|
|
101
101
|
}).format(value)
|
|
102
102
|
}
|
|
@@ -110,10 +110,9 @@ export default class Locale {
|
|
|
110
110
|
*/
|
|
111
111
|
static from(input) {
|
|
112
112
|
if (input instanceof Locale) return input
|
|
113
|
-
if (
|
|
113
|
+
if ('string' === typeof input) {
|
|
114
114
|
return new Locale({ all: input })
|
|
115
115
|
}
|
|
116
116
|
return new Locale(input)
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
|
-
|
package/src/Model/User/User.js
CHANGED
|
@@ -15,10 +15,7 @@ class User {
|
|
|
15
15
|
* @param {string} [props.email=""] - User email
|
|
16
16
|
*/
|
|
17
17
|
constructor(props = {}) {
|
|
18
|
-
const {
|
|
19
|
-
name = "",
|
|
20
|
-
email = "",
|
|
21
|
-
} = props
|
|
18
|
+
const { name = '', email = '' } = props
|
|
22
19
|
this.name = String(name)
|
|
23
20
|
this.email = String(email)
|
|
24
21
|
}
|
|
@@ -36,7 +33,7 @@ class User {
|
|
|
36
33
|
* @returns {string} User name and email (if exists)
|
|
37
34
|
*/
|
|
38
35
|
toString() {
|
|
39
|
-
return [this.name, this.email ? `<${this.email}>` :
|
|
36
|
+
return [this.name, this.email ? `<${this.email}>` : ''].filter(Boolean).join(' ')
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
/**
|
|
@@ -46,7 +43,7 @@ class User {
|
|
|
46
43
|
*/
|
|
47
44
|
static from(props) {
|
|
48
45
|
if (props instanceof User) return props
|
|
49
|
-
if (
|
|
46
|
+
if ('string' === typeof props) {
|
|
50
47
|
return new User({ name: props })
|
|
51
48
|
}
|
|
52
49
|
return new User(props)
|
package/src/Model/index.js
CHANGED
package/src/README.md.js
CHANGED
|
@@ -1,29 +1,17 @@
|
|
|
1
|
-
import { describe, it, before, beforeEach } from
|
|
2
|
-
import assert from
|
|
3
|
-
import FS from
|
|
4
|
-
import { NoConsole } from
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
runSpawn,
|
|
9
|
-
} from "@nan0web/test"
|
|
10
|
-
import {
|
|
11
|
-
Frame,
|
|
12
|
-
Model,
|
|
13
|
-
OutputMessage,
|
|
14
|
-
View,
|
|
15
|
-
FormInput,
|
|
16
|
-
UiMessage,
|
|
17
|
-
UiForm,
|
|
18
|
-
} from "./index.js"
|
|
19
|
-
import { Welcome } from "./Component/index.js"
|
|
1
|
+
import { describe, it, before, beforeEach } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import FS from '@nan0web/db-fs'
|
|
4
|
+
import { NoConsole } from '@nan0web/log'
|
|
5
|
+
import { DatasetParser, DocsParser, runSpawn } from '@nan0web/test'
|
|
6
|
+
import { Frame, Model, OutputMessage, View, FormInput, UiMessage, UiForm } from './index.js'
|
|
7
|
+
import { Welcome } from './Component/index.js'
|
|
20
8
|
|
|
21
9
|
const fs = new FS()
|
|
22
10
|
let pkg
|
|
23
11
|
|
|
24
12
|
// Load package.json once before tests
|
|
25
13
|
before(async () => {
|
|
26
|
-
const doc = await fs.loadDocument(
|
|
14
|
+
const doc = await fs.loadDocument('package.json', {})
|
|
27
15
|
pkg = doc || {}
|
|
28
16
|
})
|
|
29
17
|
|
|
@@ -33,18 +21,17 @@ beforeEach((info) => {
|
|
|
33
21
|
console = new NoConsole()
|
|
34
22
|
})
|
|
35
23
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* the final `README.md`. Keeping the comments here ensures the
|
|
41
|
-
* documentation stays close to the code.
|
|
42
|
-
*/
|
|
24
|
+
// Core test suite that also serves as the source for README generation.
|
|
25
|
+
// The block comments inside each `it` block are extracted to build
|
|
26
|
+
// the final `README.md`. Keeping the comments here ensures the
|
|
27
|
+
// documentation stays close to the code.
|
|
43
28
|
function testRender() {
|
|
44
29
|
/**
|
|
45
30
|
* @docs
|
|
46
31
|
* # @nan0web/ui
|
|
47
32
|
*
|
|
33
|
+
* 🏴 [English](./README.md) | 🇺🇦 [Українською](./docs/uk/README.md)
|
|
34
|
+
*
|
|
48
35
|
* <!-- %PACKAGE_STATUS% -->
|
|
49
36
|
*
|
|
50
37
|
* A lightweight, agnostic UI framework designed with the **nan0web philosophy**
|
|
@@ -65,35 +52,35 @@ function testRender() {
|
|
|
65
52
|
*
|
|
66
53
|
* ## Installation
|
|
67
54
|
*/
|
|
68
|
-
it(
|
|
55
|
+
it('How to install with npm?', () => {
|
|
69
56
|
/**
|
|
70
57
|
* ```bash
|
|
71
58
|
* npm install @nan0web/ui
|
|
72
59
|
* ```
|
|
73
60
|
*/
|
|
74
|
-
assert.equal(pkg.name,
|
|
61
|
+
assert.equal(pkg.name, '@nan0web/ui')
|
|
75
62
|
})
|
|
76
63
|
/**
|
|
77
64
|
* @docs
|
|
78
65
|
*/
|
|
79
|
-
it(
|
|
66
|
+
it('How to install with pnpm?', () => {
|
|
80
67
|
/**
|
|
81
68
|
* ```bash
|
|
82
69
|
* pnpm add @nan0web/ui
|
|
83
70
|
* ```
|
|
84
71
|
*/
|
|
85
|
-
assert.equal(pkg.name,
|
|
72
|
+
assert.equal(pkg.name, '@nan0web/ui')
|
|
86
73
|
})
|
|
87
74
|
/**
|
|
88
75
|
* @docs
|
|
89
76
|
*/
|
|
90
|
-
it(
|
|
77
|
+
it('How to install with yarn?', () => {
|
|
91
78
|
/**
|
|
92
79
|
* ```bash
|
|
93
80
|
* yarn add @nan0web/ui
|
|
94
81
|
* ```
|
|
95
82
|
*/
|
|
96
|
-
assert.equal(pkg.name,
|
|
83
|
+
assert.equal(pkg.name, '@nan0web/ui')
|
|
97
84
|
})
|
|
98
85
|
|
|
99
86
|
/**
|
|
@@ -110,16 +97,16 @@ function testRender() {
|
|
|
110
97
|
* Messages are simple, serializable data containers. They help build
|
|
111
98
|
* decoupled communication systems between UI components.
|
|
112
99
|
*/
|
|
113
|
-
it(
|
|
100
|
+
it('How to create input and output messages?', () => {
|
|
114
101
|
//import { InputMessage, OutputMessage } from '@nan0web/ui'
|
|
115
102
|
|
|
116
103
|
const input = UiMessage.from({ body: 'Hello User' })
|
|
117
104
|
const output = OutputMessage.from({ content: ['Welcome to @nan0web/ui'] })
|
|
118
105
|
console.info(input) // ← Message { body: "Hello User", head: {}, id: "....", type: "" }
|
|
119
106
|
console.info(String(output)) // ← Welcome to @nan0web/ui
|
|
120
|
-
assert.deepStrictEqual(console.output()[0][1].body,
|
|
107
|
+
assert.deepStrictEqual(console.output()[0][1].body, 'Hello User')
|
|
121
108
|
assert.deepStrictEqual(console.output()[0][1].head, {})
|
|
122
|
-
assert.deepStrictEqual(console.output()[0][1].type,
|
|
109
|
+
assert.deepStrictEqual(console.output()[0][1].type, '')
|
|
123
110
|
assert.ok(console.output()[0][1].id)
|
|
124
111
|
assert.ok(console.output()[1][1].endsWith('Welcome to @nan0web/ui'))
|
|
125
112
|
})
|
|
@@ -140,27 +127,32 @@ function testRender() {
|
|
|
140
127
|
* - `checkbox`
|
|
141
128
|
* - `textarea`
|
|
142
129
|
*/
|
|
143
|
-
it(
|
|
130
|
+
it('How to define and validate a UiForm?', () => {
|
|
144
131
|
//import { UiForm } from '@nan0web/ui'
|
|
145
132
|
|
|
146
133
|
const form = new UiForm({
|
|
147
|
-
title:
|
|
134
|
+
title: 'Contact Form',
|
|
148
135
|
fields: [
|
|
149
|
-
FormInput.from({ name:
|
|
150
|
-
FormInput.from({
|
|
136
|
+
FormInput.from({ name: 'email', label: 'Email Address', type: 'email', required: true }),
|
|
137
|
+
FormInput.from({
|
|
138
|
+
name: 'message',
|
|
139
|
+
label: 'Your Message',
|
|
140
|
+
type: 'textarea',
|
|
141
|
+
required: true,
|
|
142
|
+
}),
|
|
151
143
|
],
|
|
152
144
|
state: {
|
|
153
|
-
email:
|
|
154
|
-
message:
|
|
155
|
-
}
|
|
145
|
+
email: 'invalid-email',
|
|
146
|
+
message: 'Hello!',
|
|
147
|
+
},
|
|
156
148
|
})
|
|
157
149
|
|
|
158
150
|
const errors = form.validate()
|
|
159
151
|
console.info(errors.size) // ← 1
|
|
160
|
-
console.info(errors.get(
|
|
152
|
+
console.info(errors.get('email')) // ← Invalid email format
|
|
161
153
|
|
|
162
154
|
assert.equal(console.output()[0][1], 1)
|
|
163
|
-
assert.equal(console.output()[1][1],
|
|
155
|
+
assert.equal(console.output()[1][1], 'Invalid email format')
|
|
164
156
|
})
|
|
165
157
|
|
|
166
158
|
/**
|
|
@@ -172,13 +164,13 @@ function testRender() {
|
|
|
172
164
|
* - `Welcome` – greets user by name
|
|
173
165
|
* - `Process` – shows progress bar and time
|
|
174
166
|
*/
|
|
175
|
-
it(
|
|
167
|
+
it('How to render the Welcome component?', () => {
|
|
176
168
|
//import { Welcome } from '@nan0web/ui'
|
|
177
169
|
|
|
178
|
-
const frame = Welcome({ user: { name:
|
|
179
|
-
const firstLine = frame[0].join(
|
|
170
|
+
const frame = Welcome({ user: { name: 'Alice' } })
|
|
171
|
+
const firstLine = frame[0].join('')
|
|
180
172
|
console.info(firstLine) // ← Welcome Alice!
|
|
181
|
-
assert.equal(console.output()[0][1],
|
|
173
|
+
assert.equal(console.output()[0][1], 'Welcome Alice!')
|
|
182
174
|
})
|
|
183
175
|
|
|
184
176
|
/**
|
|
@@ -193,13 +185,13 @@ function testRender() {
|
|
|
193
185
|
* - StdIn / StdOut – input/output streams
|
|
194
186
|
* - Frame – output buffer with visual properties
|
|
195
187
|
*/
|
|
196
|
-
it(
|
|
188
|
+
it('How to render frame with View?', () => {
|
|
197
189
|
//import { View } from '@nan0web/ui'
|
|
198
190
|
|
|
199
191
|
const view = new View()
|
|
200
|
-
view.render(1)([
|
|
192
|
+
view.render(1)(['Hello, world'])
|
|
201
193
|
console.info(String(view.frame)) // ← "\rHello, world"
|
|
202
|
-
assert.ok(String(view.frame).includes(
|
|
194
|
+
assert.ok(String(view.frame).includes('Hello, world'))
|
|
203
195
|
})
|
|
204
196
|
|
|
205
197
|
/**
|
|
@@ -215,36 +207,36 @@ function testRender() {
|
|
|
215
207
|
* - `REPLACE` – erases and replaces full frame area
|
|
216
208
|
* - `VISIBLE` – renders only visible part of frame
|
|
217
209
|
*/
|
|
218
|
-
it(
|
|
210
|
+
it('How to create a Frame with fixed size?', () => {
|
|
219
211
|
//import { Frame } from '@nan0web/ui'
|
|
220
212
|
|
|
221
213
|
const frame = new Frame({
|
|
222
|
-
value: [[
|
|
214
|
+
value: [['Frame content']],
|
|
223
215
|
width: 20,
|
|
224
216
|
height: 5,
|
|
225
217
|
renderMethod: Frame.RenderMethod.APPEND,
|
|
226
218
|
})
|
|
227
219
|
|
|
228
220
|
const rendered = frame.render()
|
|
229
|
-
console.info(rendered.includes(
|
|
230
|
-
assert.ok(rendered.includes(
|
|
221
|
+
console.info(rendered.includes('Frame content')) // ← true
|
|
222
|
+
assert.ok(rendered.includes('Frame content'))
|
|
231
223
|
})
|
|
232
|
-
it(
|
|
224
|
+
it('How to create a Frame with different render methods?', () => {
|
|
233
225
|
//import { Frame } from '@nan0web/ui'
|
|
234
226
|
|
|
235
227
|
const frame = new Frame({
|
|
236
|
-
value: [[
|
|
228
|
+
value: [['Frame content']],
|
|
237
229
|
width: 20,
|
|
238
230
|
height: 5,
|
|
239
231
|
})
|
|
240
232
|
|
|
241
233
|
frame.renderMethod = Frame.RenderMethod.REPLACE
|
|
242
234
|
const renderedReplace = frame.render()
|
|
243
|
-
assert.ok(renderedReplace.includes(
|
|
235
|
+
assert.ok(renderedReplace.includes('Frame content'))
|
|
244
236
|
|
|
245
237
|
frame.renderMethod = Frame.RenderMethod.VISIBLE
|
|
246
238
|
const renderedVisible = frame.render()
|
|
247
|
-
assert.ok(renderedVisible.includes(
|
|
239
|
+
assert.ok(renderedVisible.includes('Frame content'))
|
|
248
240
|
})
|
|
249
241
|
|
|
250
242
|
/**
|
|
@@ -255,14 +247,14 @@ function testRender() {
|
|
|
255
247
|
*
|
|
256
248
|
* - `User` – user data
|
|
257
249
|
*/
|
|
258
|
-
it(
|
|
250
|
+
it('How to use a User model?', () => {
|
|
259
251
|
//import { Model } from '@nan0web/ui'
|
|
260
252
|
|
|
261
|
-
const user = new Model.User({ name:
|
|
253
|
+
const user = new Model.User({ name: 'Charlie', email: 'charlie@example.com' })
|
|
262
254
|
console.info(user.name) // ← Charlie
|
|
263
255
|
console.info(user.email) // ← charlie@example.com
|
|
264
|
-
assert.equal(user.name,
|
|
265
|
-
assert.equal(user.email,
|
|
256
|
+
assert.equal(user.name, 'Charlie')
|
|
257
|
+
assert.equal(user.email, 'charlie@example.com')
|
|
266
258
|
})
|
|
267
259
|
|
|
268
260
|
/**
|
|
@@ -274,18 +266,47 @@ function testRender() {
|
|
|
274
266
|
* All components, adapters, and models are designed to be testable
|
|
275
267
|
* with minimal setup.
|
|
276
268
|
*/
|
|
277
|
-
it(
|
|
269
|
+
it('How to test UI components with assertions?', () => {
|
|
278
270
|
//import { Welcome } from '@nan0web/ui'
|
|
279
271
|
|
|
280
|
-
const output = Welcome({ user: { name:
|
|
272
|
+
const output = Welcome({ user: { name: 'Test' } })
|
|
281
273
|
console.info(output) // ← Welcome Test!
|
|
282
274
|
assert.deepStrictEqual(console.output()[0][1], [
|
|
283
|
-
[
|
|
284
|
-
[
|
|
285
|
-
[
|
|
275
|
+
['Welcome', ' ', 'Test', '!'],
|
|
276
|
+
['What can we do today great?'],
|
|
277
|
+
[''],
|
|
286
278
|
])
|
|
287
279
|
})
|
|
288
280
|
|
|
281
|
+
/**
|
|
282
|
+
* @docs
|
|
283
|
+
* ### Master IDE (Component Sandbox)
|
|
284
|
+
*
|
|
285
|
+
* The Master IDE (OlmuiInspector) provides a unified environment for testing and documenting
|
|
286
|
+
* web components across platforms. It supports:
|
|
287
|
+
*
|
|
288
|
+
* - **NaN0 Spec** — a concise YAML-based shorthand for declaring component variations.
|
|
289
|
+
* - **OlmuiInspector** — unified UI for exploring component models and props.
|
|
290
|
+
* - **Live Preview** — real-time rendering of component states.
|
|
291
|
+
* - **i18n UI** — fully localized interface for global developers.
|
|
292
|
+
*
|
|
293
|
+
* It follows the **Olmui** core pattern: *One Logic — Many UI* (same manifest powers both CLI and Web).
|
|
294
|
+
*
|
|
295
|
+
* #### NaN0 Spec (YAML)
|
|
296
|
+
*
|
|
297
|
+
* Concise format for defining variations:
|
|
298
|
+
*/
|
|
299
|
+
it('How to define a component variation using NaN0 Spec?', () => {
|
|
300
|
+
/**
|
|
301
|
+
* ```yaml
|
|
302
|
+
* - Button: Primary
|
|
303
|
+
* $variant: brand
|
|
304
|
+
* $outline: true
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
assert.ok(pkg.name === '@nan0web/ui')
|
|
308
|
+
})
|
|
309
|
+
|
|
289
310
|
/**
|
|
290
311
|
* @docs
|
|
291
312
|
* ## Playground Demos
|
|
@@ -299,7 +320,7 @@ function testRender() {
|
|
|
299
320
|
*
|
|
300
321
|
* Run to explore live functionality:
|
|
301
322
|
*/
|
|
302
|
-
it(
|
|
323
|
+
it('How to run the playground?', async () => {
|
|
303
324
|
/**
|
|
304
325
|
* ```bash
|
|
305
326
|
* # Clone repository and run playground
|
|
@@ -309,10 +330,10 @@ function testRender() {
|
|
|
309
330
|
* npm run play
|
|
310
331
|
* ```
|
|
311
332
|
*/
|
|
312
|
-
assert.ok(String(pkg.scripts?.play).includes(
|
|
313
|
-
const response = await runSpawn(
|
|
314
|
-
assert.ok(response.code === 0,
|
|
315
|
-
assert.ok(response.text.trim().endsWith(
|
|
333
|
+
assert.ok(String(pkg.scripts?.play).includes('node play'))
|
|
334
|
+
const response = await runSpawn('git', ['remote', 'get-url', 'origin'])
|
|
335
|
+
assert.ok(response.code === 0, 'git command fails (e.g., not in a git repo)')
|
|
336
|
+
assert.ok(response.text.trim().endsWith(':nan0web/ui.git'))
|
|
316
337
|
})
|
|
317
338
|
|
|
318
339
|
/**
|
|
@@ -330,41 +351,45 @@ function testRender() {
|
|
|
330
351
|
* - [App](./src/App/)
|
|
331
352
|
* - [Models](./src/Model/)
|
|
332
353
|
*
|
|
354
|
+
* ## Project Architecture & Specs
|
|
355
|
+
*
|
|
356
|
+
* How the universal block spec is designed? - [check Universal Blocks Spec (`project.md`)](./project.md)
|
|
357
|
+
*
|
|
333
358
|
* ## Contributing
|
|
334
359
|
*/
|
|
335
|
-
it(
|
|
336
|
-
assert.equal(pkg.scripts?.precommit,
|
|
337
|
-
assert.equal(pkg.scripts?.prepush,
|
|
338
|
-
assert.equal(pkg.scripts?.prepare,
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
assert.ok(str.includes("# Contributing"))
|
|
360
|
+
it('How to contribute? - [check here](./CONTRIBUTING.md)', async () => {
|
|
361
|
+
assert.equal(pkg.scripts?.precommit, 'npm test')
|
|
362
|
+
assert.equal(pkg.scripts?.prepush, 'npm test')
|
|
363
|
+
assert.equal(pkg.scripts?.prepare, 'husky')
|
|
364
|
+
const str = await fs.loadDocumentAs('.txt', 'CONTRIBUTING.md')
|
|
365
|
+
assert.ok(str.includes('# Contributing'))
|
|
342
366
|
})
|
|
343
367
|
|
|
344
368
|
/**
|
|
345
369
|
* @docs
|
|
346
370
|
* ## License
|
|
347
371
|
*/
|
|
348
|
-
it(
|
|
372
|
+
it('How to license ISC? - [check here](./LICENSE)', async () => {
|
|
349
373
|
/** @docs */
|
|
350
|
-
const text = await fs.
|
|
351
|
-
assert.ok(
|
|
374
|
+
const text = await fs.loadDocumentAs('.txt', 'LICENSE')
|
|
375
|
+
assert.ok(text.includes('ISC'))
|
|
352
376
|
})
|
|
353
377
|
}
|
|
354
378
|
|
|
355
|
-
describe(
|
|
379
|
+
describe('README.md testing', testRender)
|
|
356
380
|
|
|
357
|
-
describe(
|
|
358
|
-
let text =
|
|
359
|
-
const format = new Intl.NumberFormat(
|
|
381
|
+
describe('Rendering README.md', async () => {
|
|
382
|
+
let text = ''
|
|
383
|
+
const format = new Intl.NumberFormat('en-US').format
|
|
360
384
|
const parser = new DocsParser()
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
385
|
+
const source = await fs.loadDocument('src/README.md.js')
|
|
386
|
+
text = String(parser.decode(source))
|
|
387
|
+
await fs.saveDocument('README.md', { content: text })
|
|
388
|
+
const dataset = DatasetParser.parse(text, pkg?.name ?? '@nan0web/ui')
|
|
389
|
+
await fs.saveDocument('.datasets/README.dataset.jsonl', dataset)
|
|
365
390
|
|
|
366
391
|
it(`document is rendered in README.md [${format(Buffer.byteLength(text))}b]`, async () => {
|
|
367
|
-
const text = await fs.
|
|
368
|
-
assert.ok(text.includes(
|
|
392
|
+
const text = await fs.loadDocumentAs('.txt', 'README.md')
|
|
393
|
+
assert.ok(text.includes('## License'))
|
|
369
394
|
})
|
|
370
395
|
})
|
package/src/StdIn.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import EventProcessor from
|
|
2
|
-
import { typeOf } from
|
|
3
|
-
import { UiMessage } from
|
|
1
|
+
import EventProcessor from '@nan0web/event/oop'
|
|
2
|
+
import { typeOf } from '@nan0web/types'
|
|
3
|
+
import { UiMessage } from './core/index.js'
|
|
4
4
|
|
|
5
|
-
class Processor extends EventProcessor {
|
|
5
|
+
class Processor extends EventProcessor {}
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Handles standard input stream with message buffering.
|
|
@@ -12,7 +12,7 @@ export default class StdIn extends EventProcessor {
|
|
|
12
12
|
static READ_INTERVAL = 99
|
|
13
13
|
|
|
14
14
|
/** @type {string[]} Messages to ignore */
|
|
15
|
-
static IGNORE_MESSAGES = [
|
|
15
|
+
static IGNORE_MESSAGES = ['', 'undefined']
|
|
16
16
|
|
|
17
17
|
/** @type {UiMessage[]} Input message buffer */
|
|
18
18
|
stream = []
|
|
@@ -28,13 +28,10 @@ export default class StdIn extends EventProcessor {
|
|
|
28
28
|
*/
|
|
29
29
|
constructor(props = {}) {
|
|
30
30
|
super()
|
|
31
|
-
const {
|
|
32
|
-
processor = new Processor(),
|
|
33
|
-
stream = [],
|
|
34
|
-
} = props
|
|
31
|
+
const { processor = new Processor(), stream = [] } = props
|
|
35
32
|
this.processor = processor
|
|
36
33
|
this.stream = stream
|
|
37
|
-
this.processor?.on(
|
|
34
|
+
this.processor?.on('data', (data) => {
|
|
38
35
|
this.write(data)
|
|
39
36
|
})
|
|
40
37
|
}
|
|
@@ -62,7 +59,7 @@ export default class StdIn extends EventProcessor {
|
|
|
62
59
|
*/
|
|
63
60
|
async read() {
|
|
64
61
|
while (this.ended) {
|
|
65
|
-
await new Promise(resolve => setTimeout(resolve, StdIn.READ_INTERVAL))
|
|
62
|
+
await new Promise((resolve) => setTimeout(resolve, StdIn.READ_INTERVAL))
|
|
66
63
|
}
|
|
67
64
|
return this.stream.shift() ?? new UiMessage()
|
|
68
65
|
}
|
|
@@ -105,4 +102,3 @@ export default class StdIn extends EventProcessor {
|
|
|
105
102
|
return new this(input)
|
|
106
103
|
}
|
|
107
104
|
}
|
|
108
|
-
|
package/src/StdOut.js
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
import { typeOf } from
|
|
2
|
-
import EventProcessor from
|
|
1
|
+
import { typeOf } from '@nan0web/types'
|
|
2
|
+
import EventProcessor from '@nan0web/event/oop'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Handles standard output stream with formatting capabilities.
|
|
6
6
|
*/
|
|
7
7
|
class StdOut extends EventProcessor {
|
|
8
8
|
/** @type {string} End of line character */
|
|
9
|
-
static EOL =
|
|
9
|
+
static EOL = '\n'
|
|
10
10
|
|
|
11
11
|
/** @type {string} Beginning of line character */
|
|
12
|
-
static BOL =
|
|
12
|
+
static BOL = '\r'
|
|
13
13
|
|
|
14
14
|
/** @type {string} Reset formatting escape code */
|
|
15
|
-
static RESET =
|
|
15
|
+
static RESET = '\x1b[0m'
|
|
16
16
|
|
|
17
17
|
/** @type {string} Clear screen escape code */
|
|
18
|
-
static CLEAR =
|
|
18
|
+
static CLEAR = '\x1b[2J\x1b[H'
|
|
19
19
|
|
|
20
20
|
/** @type {object} Color escape codes */
|
|
21
21
|
static COLORS = {
|
|
22
|
-
red:
|
|
23
|
-
green:
|
|
24
|
-
yellow:
|
|
25
|
-
blue:
|
|
26
|
-
magenta:
|
|
27
|
-
cyan:
|
|
28
|
-
white:
|
|
29
|
-
gray:
|
|
30
|
-
black:
|
|
22
|
+
red: '\x1b[31m',
|
|
23
|
+
green: '\x1b[32m',
|
|
24
|
+
yellow: '\x1b[33m',
|
|
25
|
+
blue: '\x1b[34m',
|
|
26
|
+
magenta: '\x1b[35m',
|
|
27
|
+
cyan: '\x1b[36m',
|
|
28
|
+
white: '\x1b[37m',
|
|
29
|
+
gray: '\x1b[90m',
|
|
30
|
+
black: '\x1b[30m',
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/** @type {Record<string, string>} Style escape codes */
|
|
34
34
|
static STYLES = {
|
|
35
|
-
dim:
|
|
36
|
-
bold:
|
|
37
|
-
underline:
|
|
35
|
+
dim: '\x1b[2m',
|
|
36
|
+
bold: '\x1b[1m',
|
|
37
|
+
underline: '\x1b[4m',
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -59,11 +59,7 @@ class StdOut extends EventProcessor {
|
|
|
59
59
|
*/
|
|
60
60
|
constructor(props = {}) {
|
|
61
61
|
super()
|
|
62
|
-
const {
|
|
63
|
-
processor,
|
|
64
|
-
stream = [],
|
|
65
|
-
windowSize = [144, 33],
|
|
66
|
-
} = props
|
|
62
|
+
const { processor, stream = [], windowSize = [144, 33] } = props
|
|
67
63
|
this.processor = processor
|
|
68
64
|
this.stream = stream
|
|
69
65
|
this.windowSize = windowSize
|
|
@@ -75,16 +71,16 @@ class StdOut extends EventProcessor {
|
|
|
75
71
|
* @param {any} output - Output to write
|
|
76
72
|
* @param {Function} onError - Error handler callback
|
|
77
73
|
*/
|
|
78
|
-
write(output, onError = v => 1) {
|
|
79
|
-
|
|
74
|
+
write(output, onError = (v) => 1) {
|
|
75
|
+
/**
|
|
80
76
|
* @todo manage the
|
|
81
77
|
*/
|
|
82
78
|
if (typeOf(Array)(output)) {
|
|
83
|
-
output = output.join(
|
|
79
|
+
output = output.join('\n')
|
|
84
80
|
}
|
|
85
81
|
this.stream.push(output)
|
|
86
82
|
this.processor?.write(output, onError)
|
|
87
|
-
this.emit(
|
|
83
|
+
this.emit('data', output)
|
|
88
84
|
}
|
|
89
85
|
|
|
90
86
|
/**
|