@inglorious/web 2.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.
package/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright © 2025 Inglorious Coderz Srl.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,383 @@
1
+ # @inglorious/web
2
+
3
+ [![NPM version](https://img.shields.io/npm/v/@inglorious/web.svg)](https://www.npmjs.com/package/@inglorious/web)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ A lightweight web framework that combines the entity-based state management of **@inglorious/store** with the performance and simplicity of **lit-html**.
7
+
8
+ ---
9
+
10
+ ## Features
11
+
12
+ - **Seamless Integration**: A simple `mount` function to connect your store, templates, and the DOM.
13
+ - **Efficient Rendering**: Leverages the performance of `lit-html` for DOM updates.
14
+ - **Entity-Based Rendering**: Includes a powerful `api.render(id)` helper to render individual entities based on their type.
15
+ - **Convenient Re-exports**: Provides direct access to `lit-html`'s `html`, `svg`, and core directives.
16
+ - **Client-Side Router**: A simple, entity-based router for single-page applications.
17
+ - **Fully Typed**: Strong TypeScript support for a great developer experience.
18
+
19
+ ---
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm install @inglorious/web
25
+ ```
26
+
27
+ ---
28
+
29
+ ## Quick Start
30
+
31
+ ### 1. Define Your Store and Entity Renders
32
+
33
+ First, set up your store with entity types. For each type you want to render, add a render method that returns a `lit-html` template.
34
+
35
+ ```javascript
36
+ // store.js
37
+ import { createStore, html } from "@inglorious/web"
38
+
39
+ const types = {
40
+ counter: {
41
+ increment(entity, id) {
42
+ if (entity.id !== id) return
43
+ entity.value++
44
+ },
45
+
46
+ // Define how a 'counter' entity should be rendered
47
+ render(entity, api) {
48
+ return html`
49
+ <div>
50
+ <span>Count: ${entity.value}</span>
51
+ <button @click=${() => api.notify("increment", entity.id)}>+1</button>
52
+ </div>
53
+ `
54
+ },
55
+ },
56
+ }
57
+
58
+ const entities = {
59
+ counter1: { type: "counter", value: 0 },
60
+ counter2: { type: "counter", value: 10 },
61
+ }
62
+
63
+ export const store = createStore({ types, entities })
64
+ ```
65
+
66
+ ### 2. Create Your Root Template and Mount
67
+
68
+ Write a root rendering function that uses the provided api to compose the UI, then use `mount` to attach it to the DOM.
69
+
70
+ ```javascript
71
+ // main.js
72
+ import { mount, html } from "@inglorious/web"
73
+ import { store } from "./store.js"
74
+
75
+ // This function receives the API and returns a lit-html template
76
+ const renderApp = (api) => {
77
+ const entities = Object.values(api.getEntities())
78
+
79
+ return html`
80
+ <h1>Counters</h1>
81
+ ${entities.map((entity) => api.render(entity.id))}
82
+ `
83
+ }
84
+
85
+ // Mount the app to the DOM
86
+ mount(store, renderApp, document.getElementById("root"))
87
+ ```
88
+
89
+ The `mount` function subscribes to the store and automatically re-renders your template whenever the state changes.
90
+
91
+ ---
92
+
93
+ ## Client-Side Router
94
+
95
+ `@inglorious/web` includes a lightweight, entity-based client-side router. It integrates directly into your `@inglorious/store` state, allowing your components to reactively update based on the current URL.
96
+
97
+ ### 1. Setup the Router
98
+
99
+ To enable the router, add it to your store's types and create a `router` entity. The entity's `routes` property maps URL patterns to the entity types that represent your pages.
100
+
101
+ ```javascript
102
+ // store.js
103
+ import { createStore, html, router } from "@inglorious/web"
104
+
105
+ const types = {
106
+ // 1. Add the router type to your store's types
107
+ router,
108
+
109
+ // 2. Define types for your pages
110
+ homePage: {
111
+ render: () => html`<h1>Welcome Home!</h1>`,
112
+ },
113
+ userPage: {
114
+ render: (entity, api) => {
115
+ // Access route params from the router entity
116
+ const { params } = api.getEntity("router")
117
+ return html`<h1>User ${params.id} - ${entity.username}</h1>`
118
+ },
119
+ },
120
+ notFoundPage: {
121
+ render: () => html`<h1>404 - Page Not Found</h1>`,
122
+ },
123
+ }
124
+
125
+ const entities = {
126
+ // 3. Create the router entity
127
+ router: {
128
+ type: "router",
129
+ routes: {
130
+ "/": "homePage",
131
+ "/users/:id": "userPage",
132
+ "*": "notFoundPage", // Fallback for unmatched routes
133
+ },
134
+ },
135
+ userPage: {
136
+ type: "userPage",
137
+ username: "Alice",
138
+ },
139
+ }
140
+
141
+ export const store = createStore({ types, entities })
142
+ ```
143
+
144
+ ### 2. Render the Current Route
145
+
146
+ In your root template, read the `route` property from the router entity and use `api.render()` to display the correct page.
147
+
148
+ ```javascript
149
+ // main.js
150
+ import { mount, html } from "@inglorious/web"
151
+ import { store } from "./store.js"
152
+
153
+ const renderApp = (api) => {
154
+ const { route } = api.getEntity("router") // e.g., "homePage" or "userPage"
155
+
156
+ return html`
157
+ <nav><a href="/">Home</a> | <a href="/users/123">User 123</a></nav>
158
+ <main>${route ? api.render(route) : ""}</main>
159
+ `
160
+ }
161
+
162
+ mount(store, renderApp, document.getElementById("root"))
163
+ ```
164
+
165
+ The router automatically intercepts clicks on local `<a>` tags and handles browser back/forward events, keeping your UI in sync with the URL.
166
+
167
+ ---
168
+
169
+ ## Forms
170
+
171
+ `@inglorious/web` includes a small but powerful `form` type for managing form state inside your entity store. It offers:
172
+
173
+ - Declarative form state held on an entity (`initialValues`, `values`, `errors`, `touched`)
174
+ - Convenient helpers for reading field value/error/touched state (`getFieldValue`, `getFieldError`, `isFieldTouched`)
175
+ - Built-in handlers for field changes, blurs, array fields, sync/async validation and submission
176
+
177
+ ### Add the `form` type
178
+
179
+ Include `form` in your `types` and create an entity for the form (use any id you like — `form` is used below for clarity):
180
+
181
+ ```javascript
182
+ import { createStore, form } from "@inglorious/web"
183
+
184
+ const types = { form }
185
+
186
+ const entities = {
187
+ form: {
188
+ type: "form",
189
+ initialValues: {
190
+ name: "",
191
+ email: "",
192
+ addresses: [],
193
+ },
194
+ },
195
+ }
196
+
197
+ const store = createStore({ types, entities })
198
+ ```
199
+
200
+ ### How it works (events & helpers)
201
+
202
+ The `form` type listens for a simple set of events (target the specific entity id with `#<id>:<event>`):
203
+
204
+ - `#<id>:fieldChange` — payload { path, value, validate? } — set a field value and optionally run a single-field validator
205
+ - `#<id>:fieldBlur` — payload { path, validate? } — mark field touched and optionally validate on blur
206
+ - `#<id>:fieldArrayAppend|fieldArrayRemove|fieldArrayInsert|fieldArrayMove` — manipulate array fields
207
+ - `#<id>:reset` — reset the form to `initialValues`
208
+ - `#<id>:validate` — synchronous whole-form validation; payload { validate }
209
+ - `#<id>:validateAsync` — async whole-form validation; payload { validate }
210
+ - `#<id>:submit` — typically handled by your `form` type's `submit` method (implement custom behavior there)
211
+
212
+ Helpers available from the package let you read state from templates and field helper components:
213
+
214
+ - `getFieldValue(formEntity, path)` — read a nested field value
215
+ - `getFieldError(formEntity, path)` — read a nested field's error message
216
+ - `isFieldTouched(formEntity, path)` — check if a field has been touched
217
+
218
+ Form state includes helpful flags:
219
+
220
+ - `isPristine` — whether the form has changed from initial values
221
+ - `isValid` — whether the current form has no validation errors
222
+ - `isValidating` — whether async validation is in progress
223
+ - `isSubmitting` — whether submission is in progress
224
+ - `submitError` — an optional submission-level error message
225
+
226
+ ### Simple example (from examples/apps/web-form)
227
+
228
+ Field components typically call `api.notify` and the `form` entity reacts accordingly. Example input field usage:
229
+
230
+ ```javascript
231
+ // inside a field component render
232
+ @input=${(e) => api.notify(`#${entity.id}:fieldChange`, { path: 'name', value: e.target.value, validate: validateName })}
233
+ @blur=${() => api.notify(`#${entity.id}:fieldBlur`, { path: 'name', validate: validateName })}
234
+ ```
235
+
236
+ Submissions and whole-form validation can be triggered from a `form` render:
237
+
238
+ ```javascript
239
+ <form @submit=${() => { api.notify(`#form:validate`, { validate: validateForm }); api.notify(`#form:submit`) }}>
240
+ <!-- inputs / buttons -->
241
+ </form>
242
+ ```
243
+
244
+ For a complete, working demo and helper components look at `examples/apps/web-form` which ships with the repository.
245
+
246
+ ---
247
+
248
+ ## Virtualized lists
249
+
250
+ `@inglorious/web` provides a small virtualized `list` type to efficiently render very long lists by only keeping visible items in the DOM. The `list` type is useful when you need to display large datasets without paying the full cost of mounting every element at once.
251
+
252
+ Key features:
253
+
254
+ - Renders only the visible slice of items and positions them absolutely inside a scrolling container.
255
+ - Automatically measures the first visible item height when not provided.
256
+ - Efficient scroll handling with simple buffer controls to avoid visual gaps.
257
+
258
+ ### Typical entity shape
259
+
260
+ When you add the `list` type to your store the entity can include these properties (the type will provide sensible defaults). Only `items` is required — all other properties are optional:
261
+
262
+ - `items` (Array) — the dataset to render.
263
+ - `visibleRange` ({ start, end }) — current visible slice indices.
264
+ - `viewportHeight` (number) — height of the scrolling viewport in pixels.
265
+ - `itemHeight` (number | null) — fixed height for each item (when null, the type will measure the first item and use an estimated height).
266
+ - `estimatedHeight` (number) — fallback height used before measurement.
267
+ - `bufferSize` (number) — extra items to render before/after the visible range to reduce flicker during scrolling.
268
+
269
+ ### Events & methods
270
+
271
+ The `list` type listens for the following events on the target entity:
272
+
273
+ - `#<id>:scroll` — payload is the scrolling container; updates `visibleRange` based on scroll position.
274
+ - `#<id>:measureHeight` — payload is the container element; used internally to measure the first item and compute `itemHeight`.
275
+
276
+ It also expects the item type to export `renderItem(item, index, api)` so each visible item can be rendered using the project's entity-based render approach.
277
+
278
+ ### Example
279
+
280
+ Minimal example showing how to extend the `list` type to create a domain-specific list (e.g. `productList`) and provide a `renderItem(item, index, api)` helper.
281
+
282
+ ```javascript
283
+ import { createStore, html, list } from "@inglorious/web"
284
+
285
+ // Extend the built-in list type to render product items
286
+ const productList = {
287
+ ...list,
288
+
289
+ renderItem(item, index) {
290
+ return html`<div class="product">
291
+ ${index}: <strong>${item.name}</strong> — ${item.price}
292
+ </div>`
293
+ },
294
+ }
295
+
296
+ const types = { list: productList }
297
+
298
+ const entities = {
299
+ products: {
300
+ type: "list",
301
+ items: Array.from({ length: 10000 }, (_, i) => ({
302
+ name: `Product ${i}`,
303
+ price: `$${i}`,
304
+ })),
305
+ viewportHeight: 400,
306
+ estimatedHeight: 40,
307
+ bufferSize: 5,
308
+ },
309
+ }
310
+
311
+ const store = createStore({ types, entities })
312
+
313
+ // Render with api.render(entity.id) as usual — the list will call productList.renderItem for each visible item.
314
+ ```
315
+
316
+ See `src/list.js` in the package for the implementation details and the `examples/apps/web-list` demo for a complete working example. In the demo the `productList` type extends the `list` type and provides `renderItem(item, index)` to render each visible item — see `examples/apps/web-list/src/product-list/product-list.js`.
317
+
318
+ ### 3. Programmatic Navigation
319
+
320
+ To navigate from your JavaScript code, dispatch a `navigate` event.
321
+
322
+ ```javascript
323
+ api.notify("navigate", "/users/456")
324
+
325
+ // Or navigate back in history
326
+ api.notify("navigate", -1)
327
+ ```
328
+
329
+ ## API Reference
330
+
331
+ **`mount(store, renderFn, element)`**
332
+
333
+ Connects a store to a `lit-html` template and renders it into a DOM element. It automatically handles re-rendering on state changes.
334
+
335
+ **Parameters:**
336
+
337
+ - `store` (required): An instance of `@inglorious/store`.
338
+ - `renderFn(api)` (required): A function that takes an `api` object and returns a `lit-html` `TemplateResult` or `null`.
339
+ - `element` (required): The `HTMLElement` or `DocumentFragment` to render the template into.
340
+
341
+ **Returns:**
342
+
343
+ - `() => void`: An `unsubscribe` function to stop listening to store updates and clean up.
344
+
345
+ ### The `api` Object
346
+
347
+ The `renderFn` receives a powerful `api` object that contains all methods from the store's API (`getEntities`, `getEntity`, `notify`, etc.) plus a special `render(id)` method.
348
+
349
+ This `render(id)` method is the cornerstone of entity-based rendering. It looks up an entity by its `id`, finds its corresponding type definition, and calls the `render(entity, api)` method on that type. This allows you to define rendering logic alongside an entity's other behaviors.
350
+
351
+ ### Re-exported `lit-html` Utilities
352
+
353
+ For convenience, `@inglorious/web` re-exports the most common utilities from `@inglorious/store` and `lit-html`, so you only need one import.
354
+
355
+ ```javascript
356
+ import {
357
+ createStore,
358
+ creteDevtools,
359
+ createSelector,
360
+ mount,
361
+ html,
362
+ router,
363
+ render,
364
+ svg,
365
+ classMap,
366
+ } from "@inglorious/web"
367
+ ```
368
+
369
+ ---
370
+
371
+ ## License
372
+
373
+ **MIT License - Free and open source**
374
+
375
+ Created by [Matteo Antony Mistretta](https://github.com/IngloriousCoderz)
376
+
377
+ You're free to use, modify, and distribute this software. See [LICENSE](./LICENSE) for details.
378
+
379
+ ---
380
+
381
+ ## Contributing
382
+
383
+ Contributions welcome! Please read our [Contributing Guidelines](../../CONTRIBUTING.md) first.
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@inglorious/web",
3
+ "version": "2.0.0",
4
+ "description": "A new web framework that leverages the power of the Inglorious Store combined with the performance and simplicity of lit-html.",
5
+ "author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/IngloriousCoderz/inglorious-engine.git",
10
+ "directory": "packages/web"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/IngloriousCoderz/inglorious-engine/issues"
14
+ },
15
+ "keywords": [
16
+ "lit-html",
17
+ "state",
18
+ "state-management",
19
+ "store",
20
+ "redux",
21
+ "inglorious-store"
22
+ ],
23
+ "type": "module",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./types/index.d.ts",
27
+ "import": "./src/index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "src",
32
+ "types"
33
+ ],
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "dependencies": {
38
+ "lit-html": "^3.3.1",
39
+ "@inglorious/store": "7.0.0",
40
+ "@inglorious/utils": "3.7.0"
41
+ },
42
+ "devDependencies": {
43
+ "prettier": "^3.6.2",
44
+ "vite": "^7.1.3",
45
+ "@inglorious/eslint-config": "1.0.1"
46
+ },
47
+ "engines": {
48
+ "node": ">= 22"
49
+ },
50
+ "scripts": {
51
+ "format": "prettier --write '**/*.{js,jsx}'",
52
+ "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0"
53
+ }
54
+ }