@nan0web/ui 1.0.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 (131) hide show
  1. package/.datasets/README.dataset.jsonl +12 -0
  2. package/.editorconfig +20 -0
  3. package/CONTRIBUTING.md +42 -0
  4. package/LICENSE +15 -0
  5. package/README.md +238 -0
  6. package/docs/uk/README.md +240 -0
  7. package/package.json +64 -0
  8. package/playground/User.js +52 -0
  9. package/playground/currency.exchange.js +48 -0
  10. package/playground/i18n/index.js +21 -0
  11. package/playground/i18n/uk.js +53 -0
  12. package/playground/language.form.js +25 -0
  13. package/playground/main.js +72 -0
  14. package/playground/registration.form.js +58 -0
  15. package/playground/topup.telephone.js +62 -0
  16. package/src/App/Command/Options.js +78 -0
  17. package/src/App/Command/index.js +9 -0
  18. package/src/App/Core/CoreApp.js +129 -0
  19. package/src/App/Core/UI.js +116 -0
  20. package/src/App/Core/Widget.js +67 -0
  21. package/src/App/Core/index.js +11 -0
  22. package/src/App/Scenario.js +45 -0
  23. package/src/App/User/Command/Message.js +44 -0
  24. package/src/App/User/Command/Options.js +48 -0
  25. package/src/App/User/Command/index.js +11 -0
  26. package/src/App/User/UserApp.js +73 -0
  27. package/src/App/User/UserApp.test.js +56 -0
  28. package/src/App/User/UserUI.js +20 -0
  29. package/src/App/User/UserUI.test.js +51 -0
  30. package/src/App/User/index.js +15 -0
  31. package/src/App/index.js +22 -0
  32. package/src/Component/Process/Input.js +70 -0
  33. package/src/Component/Process/Process.js +26 -0
  34. package/src/Component/Process/index.js +5 -0
  35. package/src/Component/Welcome/Input.js +50 -0
  36. package/src/Component/Welcome/Welcome.js +26 -0
  37. package/src/Component/Welcome/index.js +5 -0
  38. package/src/Component/index.js +9 -0
  39. package/src/Frame/Frame.js +591 -0
  40. package/src/Frame/Frame.test.js +429 -0
  41. package/src/Frame/Props.js +102 -0
  42. package/src/Locale.js +119 -0
  43. package/src/Model/User/User.js +56 -0
  44. package/src/Model/index.js +7 -0
  45. package/src/README.md.js +371 -0
  46. package/src/StdIn.js +111 -0
  47. package/src/StdOut.js +99 -0
  48. package/src/View/RenderOptions.js +48 -0
  49. package/src/View/View.js +289 -0
  50. package/src/View/View.test.js +77 -0
  51. package/src/core/Form/Form.js +289 -0
  52. package/src/core/Form/Form.test.js +116 -0
  53. package/src/core/Form/Input.js +116 -0
  54. package/src/core/Form/Input.test.js +58 -0
  55. package/src/core/Form/Message.js +86 -0
  56. package/src/core/Form/Message.test.js +54 -0
  57. package/src/core/Form/index.js +11 -0
  58. package/src/core/InputAdapter.js +41 -0
  59. package/src/core/InputAdapter.test.js +35 -0
  60. package/src/core/Message/InputMessage.js +119 -0
  61. package/src/core/Message/InputMessage.test.js +45 -0
  62. package/src/core/Message/Message.js +77 -0
  63. package/src/core/Message/Message.test.js +58 -0
  64. package/src/core/Message/OutputMessage.js +143 -0
  65. package/src/core/Message/OutputMessage.test.js +61 -0
  66. package/src/core/Message/index.js +7 -0
  67. package/src/core/OutputAdapter.js +50 -0
  68. package/src/core/OutputAdapter.test.js +35 -0
  69. package/src/core/Stream.js +71 -0
  70. package/src/core/Stream.test.js +78 -0
  71. package/src/core/StreamEntry.js +59 -0
  72. package/src/core/index.js +13 -0
  73. package/src/functions.js +38 -0
  74. package/src/index.js +34 -0
  75. package/src/index.test.js +14 -0
  76. package/src/models/SimpleUser.js +18 -0
  77. package/stories/App/AppView.js +15 -0
  78. package/stories/App/AppView.test.js +22 -0
  79. package/stories/App/RenderOptions.js +14 -0
  80. package/stories/nodejs/interface.test.js +27 -0
  81. package/system.md +187 -0
  82. package/system1.md +137 -0
  83. package/task.md +181 -0
  84. package/tsconfig.json +23 -0
  85. package/types/App/Command/Options.d.ts +46 -0
  86. package/types/App/Command/index.d.ts +8 -0
  87. package/types/App/Core/CoreApp.d.ts +70 -0
  88. package/types/App/Core/UI.d.ts +49 -0
  89. package/types/App/Core/Widget.d.ts +40 -0
  90. package/types/App/Core/index.d.ts +10 -0
  91. package/types/App/Scenario.d.ts +26 -0
  92. package/types/App/User/Command/Message.d.ts +30 -0
  93. package/types/App/User/Command/Options.d.ts +27 -0
  94. package/types/App/User/Command/index.d.ts +8 -0
  95. package/types/App/User/UserApp.d.ts +31 -0
  96. package/types/App/User/UserUI.d.ts +18 -0
  97. package/types/App/User/index.d.ts +12 -0
  98. package/types/App/index.d.ts +14 -0
  99. package/types/Component/Process/Input.d.ts +48 -0
  100. package/types/Component/Process/Process.d.ts +13 -0
  101. package/types/Component/Process/index.d.ts +4 -0
  102. package/types/Component/Welcome/Input.d.ts +34 -0
  103. package/types/Component/Welcome/Welcome.d.ts +13 -0
  104. package/types/Component/Welcome/index.d.ts +4 -0
  105. package/types/Component/index.d.ts +8 -0
  106. package/types/Frame/Frame.d.ts +186 -0
  107. package/types/Frame/Props.d.ts +77 -0
  108. package/types/Locale.d.ts +55 -0
  109. package/types/Model/User/User.d.ts +36 -0
  110. package/types/Model/index.d.ts +6 -0
  111. package/types/StdIn.d.ts +62 -0
  112. package/types/StdOut.d.ts +52 -0
  113. package/types/View/RenderOptions.d.ts +29 -0
  114. package/types/View/View.d.ts +115 -0
  115. package/types/core/Form/Form.d.ts +123 -0
  116. package/types/core/Form/Input.d.ts +69 -0
  117. package/types/core/Form/Message.d.ts +28 -0
  118. package/types/core/Form/index.d.ts +5 -0
  119. package/types/core/InputAdapter.d.ts +28 -0
  120. package/types/core/Message/InputMessage.d.ts +71 -0
  121. package/types/core/Message/Message.d.ts +50 -0
  122. package/types/core/Message/OutputMessage.d.ts +53 -0
  123. package/types/core/Message/index.d.ts +5 -0
  124. package/types/core/OutputAdapter.d.ts +33 -0
  125. package/types/core/Stream.d.ts +27 -0
  126. package/types/core/StreamEntry.d.ts +45 -0
  127. package/types/core/index.d.ts +9 -0
  128. package/types/functions.d.ts +3 -0
  129. package/types/index.d.ts +20 -0
  130. package/types/models/SimpleUser.d.ts +21 -0
  131. package/vitest.config.js +26 -0
@@ -0,0 +1,7 @@
1
+ import User from "./User/User.js"
2
+
3
+ export { User }
4
+
5
+ export default {
6
+ User,
7
+ }
@@ -0,0 +1,371 @@
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 {
6
+ DatasetParser,
7
+ DocsParser,
8
+ runSpawn,
9
+ } from "@nan0web/test"
10
+ import {
11
+ App,
12
+ Component,
13
+ Frame,
14
+ InputMessage,
15
+ Model,
16
+ OutputMessage,
17
+ UIMessage,
18
+ UIForm,
19
+ UIStream,
20
+ View,
21
+ FormInput,
22
+ } from "./index.js"
23
+ import { Welcome } from "./Component/index.js"
24
+
25
+ const fs = new FS()
26
+ let pkg
27
+
28
+ // Load package.json once before tests
29
+ before(async () => {
30
+ const doc = await fs.loadDocument("package.json", {})
31
+ pkg = doc || {}
32
+ })
33
+
34
+ let console = new NoConsole()
35
+
36
+ beforeEach((info) => {
37
+ console = new NoConsole()
38
+ })
39
+
40
+ /**
41
+ * Core test suite that also serves as the source for README generation.
42
+ *
43
+ * The block comments inside each `it` block are extracted to build
44
+ * the final `README.md`. Keeping the comments here ensures the
45
+ * documentation stays close to the code.
46
+ */
47
+ function testRender() {
48
+ /**
49
+ * @docs
50
+ * # @nan0web/ui
51
+ *
52
+ * <!-- %PACKAGE_STATUS% -->
53
+ *
54
+ * A lightweight, agnostic UI framework designed with the **nan0web philosophy**
55
+ * — one application logic, many UI implementations.
56
+ *
57
+ * This library provides core classes and utilities for building structured user interfaces.
58
+ * It supports:
59
+ *
60
+ * - Messaging (Input/Output)
61
+ * - Forms with validation
62
+ * - Progress tracking
63
+ * - Component rendering
64
+ * - View management with Frame rendering
65
+ * - App structure with core and user apps
66
+ *
67
+ * Built to work in sync or async, terminal-based or web-based apps,
68
+ * focusing on type safety, minimalism, and pure JavaScript design.
69
+ *
70
+ * ## Installation
71
+ */
72
+ it("How to install with npm?", () => {
73
+ /**
74
+ * ```bash
75
+ * npm install @nan0web/ui
76
+ * ```
77
+ */
78
+ assert.equal(pkg.name, "@nan0web/ui")
79
+ })
80
+ /**
81
+ * @docs
82
+ */
83
+ it("How to install with pnpm?", () => {
84
+ /**
85
+ * ```bash
86
+ * pnpm add @nan0web/ui
87
+ * ```
88
+ */
89
+ assert.equal(pkg.name, "@nan0web/ui")
90
+ })
91
+ /**
92
+ * @docs
93
+ */
94
+ it("How to install with yarn?", () => {
95
+ /**
96
+ * ```bash
97
+ * yarn add @nan0web/ui
98
+ * ```
99
+ */
100
+ assert.equal(pkg.name, "@nan0web/ui")
101
+ })
102
+
103
+ /**
104
+ * @docs
105
+ * ## Concepts & Architecture
106
+ *
107
+ * ### Message Flow
108
+ *
109
+ * UI communication is built around messages:
110
+ *
111
+ * - **`UIMessage`** – abstract message base class
112
+ * - **`InputMessage`** – user input message (value, options)
113
+ * - **`OutputMessage`** – system output (content, error, priority)
114
+ *
115
+ * Messages are simple, serializable data containers. They help build
116
+ * decoupled communication systems between UI components.
117
+ */
118
+ it("How to create input and output messages?", () => {
119
+ //import { InputMessage, OutputMessage } from '@nan0web/ui'
120
+
121
+ const input = InputMessage.from({ value: 'Hello User' })
122
+ const output = OutputMessage.from({ content: ['Welcome to @nan0web/ui'] })
123
+ console.info(input.value) // ← Hello User
124
+ console.info(output.content[0]) // ← Welcome to @nan0web/ui
125
+ assert.equal(input.value, 'Hello User')
126
+ assert.equal(output.content[0], 'Welcome to @nan0web/ui')
127
+ })
128
+
129
+ /**
130
+ * @docs
131
+ * ### Forms
132
+ *
133
+ * `UIForm` supports field definitions, data management, and schema validation.
134
+ * Every form includes a title, fields, and current state.
135
+ *
136
+ * Field types include:
137
+ *
138
+ * - `text`
139
+ * - `email`
140
+ * - `number`
141
+ * - `select`
142
+ * - `checkbox`
143
+ * - `textarea`
144
+ */
145
+ it("How to define and validate a UIForm?", () => {
146
+ //import { UIForm } from '@nan0web/ui'
147
+
148
+ const form = new UIForm({
149
+ title: "Contact Form",
150
+ fields: [
151
+ FormInput.from({ name: "email", label: "Email Address", type: "email", required: true }),
152
+ FormInput.from({ name: "message", label: "Your Message", type: "textarea", required: true })
153
+ ],
154
+ state: {
155
+ email: "invalid-email",
156
+ message: "Hello!"
157
+ }
158
+ })
159
+
160
+ const result = form.validate()
161
+ console.info(result.isValid) // ← false
162
+ console.info(result.errors.email) // ← Invalid email format
163
+
164
+ assert.equal(result.isValid, false)
165
+ assert.equal(result.errors.email, "Invalid email format")
166
+ })
167
+
168
+ /**
169
+ * @docs
170
+ * ### Components
171
+ *
172
+ * Components render data as frame-ready output.
173
+ *
174
+ * - `Welcome` – greets user by name
175
+ * - `Process` – shows progress bar and time
176
+ */
177
+ it("How to render the Welcome component?", () => {
178
+ //import { Welcome } from '@nan0web/ui'
179
+
180
+ const frame = Welcome({ user: { name: "Alice" } })
181
+ const firstLine = frame[0].join("")
182
+ console.info(firstLine) // ← Welcome Alice!
183
+ assert.equal(console.output()[0][1], "Welcome Alice!")
184
+ })
185
+
186
+ /**
187
+ * @docs
188
+ * ### View Manager
189
+ *
190
+ * `View` combines components and renders frames.
191
+ *
192
+ * Every view has:
193
+ *
194
+ * - Locale – formatted text, numbers, currency
195
+ * - StdIn / StdOut – input/output streams
196
+ * - Frame – output buffer with visual properties
197
+ */
198
+ it("How to render frame with View?", () => {
199
+ //import { View } from '@nan0web/ui'
200
+
201
+ const view = new View()
202
+ view.render(1)(["Hello, world"])
203
+ console.info(String(view.frame)) // ← "\rHello, world"
204
+ assert.ok(String(view.frame).includes("Hello, world"))
205
+ })
206
+
207
+ /**
208
+ * @docs
209
+ * ### Frame Rendering
210
+ *
211
+ * `Frame` manages visual rendering with width and height limits.
212
+ * Useful for fixed-size terminals or UI blocks.
213
+ */
214
+ it("How to create a Frame with fixed size?", () => {
215
+ //import { Frame } from '@nan0web/ui'
216
+
217
+ const frame = new Frame({
218
+ value: [["Frame content"]],
219
+ width: 20,
220
+ height: 5,
221
+ renderMethod: Frame.RenderMethod.APPEND,
222
+ })
223
+
224
+ const rendered = frame.render()
225
+ console.info(rendered.includes("Frame content")) // ← true
226
+ assert.ok(rendered.includes("Frame content"))
227
+ })
228
+
229
+ /**
230
+ * @docs
231
+ * ### App Architecture
232
+ *
233
+ * `App` provides the main application logic.
234
+ *
235
+ * - Core – minimal UI layer
236
+ * - User – user-specific UI commands
237
+ *
238
+ * Each app registers commands and binds them to UI actions.
239
+ */
240
+ it("How to create a basic user app that greets?", async () => {
241
+ //import { App, View } from '@nan0web/ui'
242
+
243
+ const app = new App.User.App({ name: "GreetApp" })
244
+ const view = new View()
245
+ view.register("Welcome", Welcome)
246
+
247
+ const cmd = App.Command.Message.parse("welcome --user Bob")
248
+ const result = await app.processCommand(cmd, new App.User.UI(app, view))
249
+ console.info(String(result)) // ← Welcome Bob!
250
+ assert.ok(console.output()[0][1].includes("Welcome Bob!"))
251
+ })
252
+
253
+ /**
254
+ * @docs
255
+ * ### Models
256
+ *
257
+ * UI models are plain data objects managed by `Model` classes.
258
+ *
259
+ * - `User` – user data
260
+ */
261
+ it("How to use a User model?", () => {
262
+ //import { Model } from '@nan0web/ui'
263
+
264
+ const user = new Model.User({ name: "Charlie", email: "charlie@example.com" })
265
+ console.info(user.name) // ← Charlie
266
+ console.info(user.email) // ← charlie@example.com
267
+ assert.equal(user.name, "Charlie")
268
+ assert.equal(user.email, "charlie@example.com")
269
+ })
270
+
271
+ /**
272
+ * @docs
273
+ * ### Testing UI
274
+ *
275
+ * Core unit-tested to ensure stability in different environments.
276
+ *
277
+ * All components, adapters, and models are designed to be testable
278
+ * with minimal setup.
279
+ */
280
+ it("How to test UI components with assertions?", () => {
281
+ //import { Welcome, InputMessage } from '@nan0web/ui'
282
+
283
+ const output = Welcome({ user: { name: "Test" } })
284
+ const input = InputMessage.from({ value: "test" })
285
+ console.log(output[0].join("")) // ← Welcome Test!
286
+ assert.equal(console.output()[0][1], "Welcome Test!")
287
+ assert.ok(input instanceof InputMessage)
288
+ })
289
+
290
+ /**
291
+ * @docs
292
+ * ## Playground Demos
293
+ *
294
+ * The library includes rich playground demos:
295
+ *
296
+ * - [Registration Form](./playground/registration.form.js)
297
+ * - [Currency Exchange](./playground/currency.exchange.js)
298
+ * - [Mobile Top-up](./playground/topup.telephone.js)
299
+ * - [Language Selector](./playground/language.form.js)
300
+ *
301
+ * Run to explore live functionality:
302
+ */
303
+ it("How to run the playground?", async () => {
304
+ /**
305
+ * ```bash
306
+ * # Clone repository and run playground
307
+ * git clone https://github.com/nan0web/ui.git
308
+ * cd ui
309
+ * npm install
310
+ * npm run playground
311
+ * ```
312
+ */
313
+ assert.ok(String(pkg.scripts?.playground).includes("node playground"))
314
+ const response = await runSpawn("git", ["remote", "get-url", "origin"])
315
+ assert.ok(response.code === 0, "git command fails (e.g., not in a git repo)")
316
+ assert.ok(response.text.trim().endsWith(":nan0web/ui.git"))
317
+ })
318
+
319
+ /**
320
+ * @docs
321
+ * ## API Documentation
322
+ *
323
+ * Detailed API docs are available in each class JSDoc.
324
+ * Explore:
325
+ *
326
+ * - [Messages](./src/core/Message/)
327
+ * - [Forms](./src/core/Form/)
328
+ * - [Stream](./src/core/Stream.js)
329
+ * - [Components](./src/Component/)
330
+ * - [View](./src/View/)
331
+ * - [App](./src/App/)
332
+ * - [Models](./src/Model/)
333
+ *
334
+ * ## Contributing
335
+ */
336
+ it("How to contribute? - [check here](./CONTRIBUTING.md)", async () => {
337
+ assert.equal(pkg.scripts?.precommit, "npm test")
338
+ assert.equal(pkg.scripts?.prepush, "npm test")
339
+ assert.equal(pkg.scripts?.prepare, "husky")
340
+ const text = await fs.loadDocument("CONTRIBUTING.md")
341
+ const str = String(text)
342
+ assert.ok(str.includes("# Contributing"))
343
+ })
344
+
345
+ /**
346
+ * @docs
347
+ * ## License
348
+ */
349
+ it("How to license ISC? - [check here](./LICENSE)", async () => {
350
+ /** @docs */
351
+ const text = await fs.loadDocument("LICENSE")
352
+ assert.ok(String(text).includes("ISC"))
353
+ })
354
+ }
355
+
356
+ describe("README.md testing", testRender)
357
+
358
+ describe("Rendering README.md", async () => {
359
+ let text = ""
360
+ const format = new Intl.NumberFormat("en-US").format
361
+ const parser = new DocsParser()
362
+ text = String(parser.decode(testRender))
363
+ await fs.saveDocument("README.md", text)
364
+ const dataset = DatasetParser.parse(text, pkg.name)
365
+ await fs.saveDocument(".datasets/README.dataset.jsonl", dataset)
366
+
367
+ it(`document is rendered in README.md [${format(Buffer.byteLength(text))}b]`, async () => {
368
+ const text = await fs.loadDocument("README.md")
369
+ assert.ok(text.includes("## License"))
370
+ })
371
+ })
package/src/StdIn.js ADDED
@@ -0,0 +1,111 @@
1
+ import EventProcessor from "@nan0web/event/oop"
2
+ import { CommandMessage } from "@nan0web/co"
3
+ import { typeOf } from "@nan0web/types"
4
+ import InputMessage from "./core/Message/InputMessage.js"
5
+
6
+ class Processor extends EventProcessor { }
7
+
8
+ /**
9
+ * Handles standard input stream with message buffering.
10
+ */
11
+ class StdIn extends EventProcessor {
12
+ /** @type {number} Read interval in milliseconds */
13
+ static READ_INTERVAL = 99
14
+
15
+ /** @type {string[]} Messages to ignore */
16
+ static IGNORE_MESSAGES = ["", "undefined"]
17
+
18
+ /** @type {InputMessage[]} Input message buffer */
19
+ stream = []
20
+
21
+ /** @type {Processor} Input processor */
22
+ processor
23
+
24
+ /**
25
+ * Creates a new StdIn instance.
26
+ * @param {object} props - StdIn properties
27
+ * @param {Processor} [props.processor] - Input processor
28
+ * @param {InputMessage[]} [props.stream=[]] - Initial input stream
29
+ */
30
+ constructor(props = {}) {
31
+ super()
32
+ const {
33
+ processor = new Processor(),
34
+ stream = [],
35
+ } = props
36
+ this.processor = processor
37
+ this.stream = stream
38
+ this.processor?.on("data", (data) => {
39
+ this.write(data)
40
+ })
41
+ }
42
+
43
+ /**
44
+ * Checks if there are messages waiting in the input stream.
45
+ * @returns {boolean} True if waiting messages, false otherwise
46
+ */
47
+ get waiting() {
48
+ return this.stream.length > 0
49
+ }
50
+
51
+ /**
52
+ * Checks if the input stream has ended (no messages left).
53
+ * @returns {boolean} True if no messages left, false otherwise
54
+ */
55
+ get ended() {
56
+ return 0 === this.stream.length
57
+ }
58
+
59
+ /**
60
+ * Reads a message from the input stream.
61
+ * Waits until messages are available if stream is empty.
62
+ * @returns {Promise<InputMessage>} Next input message
63
+ */
64
+ async read() {
65
+ while (this.ended) {
66
+ await new Promise(resolve => setTimeout(resolve, StdIn.READ_INTERVAL))
67
+ }
68
+ return this.stream.shift() ?? new InputMessage()
69
+ }
70
+
71
+ /**
72
+ * Writes a message to the input stream.
73
+ * @param {string} message - Message to write
74
+ * @returns {boolean} True if message accepted, False if ignored
75
+ */
76
+ write(message) {
77
+ // this.processor?.emit("data", message)
78
+ const text = String(message)
79
+ if (StdIn.IGNORE_MESSAGES.includes(text)) {
80
+ return false
81
+ }
82
+ this.stream.push(this.decode(text))
83
+ return true
84
+ }
85
+
86
+ /**
87
+ * Decodes a message into an InputMessage instance.
88
+ * @param {InputMessage | string[] | any} message - Message to decode
89
+ * @returns {InputMessage} Decoded input message
90
+ */
91
+ decode(message) {
92
+ if (message instanceof InputMessage) return message
93
+ if (Array.isArray(message) && message.every(typeOf(String))) {
94
+ const parsed = CommandMessage.parse(message)
95
+ return new InputMessage({ value: parsed })
96
+ }
97
+ return new InputMessage(message)
98
+ }
99
+
100
+ /**
101
+ * Creates a StdIn instance from the given input.
102
+ * @param {StdIn|object} input - The input to create from
103
+ * @returns {StdIn} A StdIn instance
104
+ */
105
+ static from(input) {
106
+ if (input instanceof StdIn) return input
107
+ return new this(input)
108
+ }
109
+ }
110
+
111
+ export default StdIn
package/src/StdOut.js ADDED
@@ -0,0 +1,99 @@
1
+ import { typeOf } from "@nan0web/types"
2
+ import EventProcessor from "@nan0web/event/oop"
3
+
4
+ /**
5
+ * Handles standard output stream with formatting capabilities.
6
+ */
7
+ class StdOut extends EventProcessor {
8
+ /** @type {string} End of line character */
9
+ static EOL = "\n"
10
+
11
+ /** @type {string} Beginning of line character */
12
+ static BOL = "\r"
13
+
14
+ /** @type {string} Reset formatting escape code */
15
+ static RESET = "\x1b[0m"
16
+
17
+ /** @type {string} Clear screen escape code */
18
+ static CLEAR = "\x1b[2J\x1b[H"
19
+
20
+ /** @type {object} Color escape codes */
21
+ static COLORS = {
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
+ }
32
+
33
+ /** @type {Record<string, string>} Style escape codes */
34
+ static STYLES = {
35
+ dim: "\x1b[2m",
36
+ bold: "\x1b[1m",
37
+ underline: "\x1b[4m",
38
+ }
39
+
40
+ /**
41
+ * @todo define go top by rows constants.
42
+ */
43
+
44
+ /** @type {string[]} Output stream buffer */
45
+ stream = []
46
+
47
+ /** @type {number[]} Window size [width, height] */
48
+ windowSize = []
49
+
50
+ /** @type {any} Output processor */
51
+ processor
52
+
53
+ /**
54
+ * Creates a new StdOut instance.
55
+ * @param {object} props - StdOut properties
56
+ * @param {any} [props.processor] - Output processor
57
+ * @param {string[]} [props.stream=[]] - Initial output stream
58
+ * @param {number[]} [props.windowSize=[144, 33]] - Window size [width, height]
59
+ */
60
+ constructor(props = {}) {
61
+ super()
62
+ const {
63
+ processor,
64
+ stream = [],
65
+ windowSize = [144, 33],
66
+ } = props
67
+ this.processor = processor
68
+ this.stream = stream
69
+ this.windowSize = windowSize
70
+ }
71
+
72
+ /**
73
+ * Writes output to the output stream.
74
+ * Must be overwritten by other apps.
75
+ * @param {any} output - Output to write
76
+ * @param {Function} onError - Error handler callback
77
+ */
78
+ write(output, onError = v => 1) {
79
+ /**
80
+ * @todo manage the
81
+ */
82
+ if (typeOf(Array)(output)) {
83
+ output = output.join("\n")
84
+ }
85
+ this.stream.push(output)
86
+ this.processor?.write(output, onError)
87
+ this.emit("data", output)
88
+ }
89
+
90
+ /**
91
+ * Gets the window size.
92
+ * @returns {number[]} Window size [width, height]
93
+ */
94
+ getWindowSize() {
95
+ return this.processor?.getWindowSize?.() ?? this.windowSize
96
+ }
97
+ }
98
+
99
+ export default StdOut
@@ -0,0 +1,48 @@
1
+ import Frame from "../Frame/Frame.js"
2
+
3
+ class RenderOptions {
4
+ static DEFAULTS = {
5
+ resizeToView: false,
6
+ translateFrame: false,
7
+ render: true,
8
+ renderMethod: Frame.RenderMethod.VISIBLE,
9
+ }
10
+ /** @type {boolean} [false] */
11
+ resizeToView
12
+ /** @type {boolean} [false] */
13
+ translateFrame
14
+ /** @type {boolean} [true] */
15
+ render
16
+ /** @type {string} */
17
+ renderMethod
18
+ /** @type {number} */
19
+ width
20
+ /** @type {number} */
21
+ height
22
+ constructor(props = {}) {
23
+ const DEFAULTS = this.DEFAULTS
24
+ const {
25
+ resizeToView = DEFAULTS.resizeToView,
26
+ translateFrame = DEFAULTS.translateFrame,
27
+ render = DEFAULTS.render,
28
+ renderMethod = DEFAULTS.renderMethod,
29
+ width = 0,
30
+ height = 0,
31
+ } = props
32
+ this.resizeToView = resizeToView
33
+ this.translateFrame = translateFrame
34
+ this.render = render
35
+ this.renderMethod = renderMethod
36
+ this.width = width
37
+ this.height = height
38
+ }
39
+ get DEFAULTS() {
40
+ return /** @type {typeof RenderOptions} */ (this.constructor).DEFAULTS
41
+ }
42
+ static from(props = {}) {
43
+ if (props instanceof RenderOptions) return props
44
+ return new this(props)
45
+ }
46
+ }
47
+
48
+ export default RenderOptions