@inglorious/web 3.0.0 → 4.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/README.md CHANGED
@@ -371,7 +371,8 @@ To enable the router, add it to your store's types and create a `router` entity.
371
371
 
372
372
  ```javascript
373
373
  // store.js
374
- import { createStore, html, router } from "@inglorious/web"
374
+ import { createStore, html } from "@inglorious/web"
375
+ import { router } from "@inglorious/web/router"
375
376
 
376
377
  const types = {
377
378
  // 1. Add the router type to your store's types
@@ -520,7 +521,8 @@ export const requireAuth = (type) => ({
520
521
 
521
522
  ```javascript
522
523
  // store.js
523
- import { createStore, router } from "@inglorious/web"
524
+ import { createStore } from "@inglorious/web"
525
+ import { router } from "@inglorious/web/router"
524
526
  import { requireAuth } from "./guards/require-auth.js"
525
527
  import { adminPage } from "./pages/admin.js"
526
528
  import { loginPage } from "./pages/login.js"
@@ -646,7 +648,7 @@ To use it, import the `table` type and its CSS, then create an entity for your t
646
648
 
647
649
  ```javascript
648
650
  // In your entity definition file
649
- import { table } from "@inglorious/web"
651
+ import { table } from "@inglorious/web/table"
650
652
 
651
653
  // Import base styles and a theme. You can create your own theme.
652
654
  import "@inglorious/web/table/base.css"
@@ -673,7 +675,7 @@ You can customize how data is rendered in the table cells by overriding the `ren
673
675
  The example below from `examples/apps/web-table/src/product-table/product-table.js` shows how to format values based on a `formatter` property in the column definition.
674
676
 
675
677
  ```javascript
676
- import { table } from "@inglorious/web"
678
+ import { table } from "@inglorious/web/table"
677
679
  import { format } from "date-fns"
678
680
 
679
681
  const formatters = {
@@ -705,7 +707,8 @@ The table comes with a base stylesheet (`@inglorious/web/table/base.css`) and a
705
707
  Import the `select` type and its CSS, then create an entity.
706
708
 
707
709
  ```javascript
708
- import { createStore, select } from "@inglorious/web"
710
+ import { createStore } from "@inglorious/web"
711
+ import { select } from "@inglorious/web/select"
709
712
  // Import base styles and theme
710
713
  import "@inglorious/web/select/base.css"
711
714
  import "@inglorious/web/select/theme.css"
@@ -765,7 +768,8 @@ It listens to internal events like `#<id>:toggle`, `#<id>:optionSelect`, etc. Yo
765
768
  Include `form` in your `types` and create an entity for the form (use any id you like — `form` is used below for clarity):
766
769
 
767
770
  ```javascript
768
- import { createStore, form } from "@inglorious/web"
771
+ import { createStore } from "@inglorious/web"
772
+ import { form } from "@inglorious/web/form"
769
773
 
770
774
  const types = { form }
771
775
 
@@ -866,7 +870,8 @@ It also expects the item type to export `renderItem(item, index, api)` so each v
866
870
  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.
867
871
 
868
872
  ```javascript
869
- import { createStore, html, list } from "@inglorious/web"
873
+ import { createStore, html } from "@inglorious/web"
874
+ import { list } from "@inglorious/web/list"
870
875
 
871
876
  // Extend the built-in list type to render product items
872
877
  const productList = {
@@ -952,18 +957,19 @@ import {
952
957
  styleMap,
953
958
  unsafeHTML,
954
959
  when,
955
- // router stuff
956
- router,
957
- // table stuff
958
- table,
959
- // form stuff
960
+ } from "@inglorious/web"
961
+
962
+ // Subpath imports for tree-shaking
963
+ import {
960
964
  form,
961
965
  getFieldError,
962
966
  getFieldValue,
963
967
  isFieldTouched,
964
- // virtualized list stuff
965
- list,
966
- } from "@inglorious/web"
968
+ } from "@inglorious/web/form"
969
+ import { list } from "@inglorious/web/list"
970
+ import { router } from "@inglorious/web/router"
971
+ import { select } from "@inglorious/web/select"
972
+ import { table } from "@inglorious/web/table"
967
973
  ```
968
974
 
969
975
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/web",
3
- "version": "3.0.0",
3
+ "version": "4.0.0",
4
4
  "description": "A new web framework that leverages the power of the Inglorious Store combined with the performance and simplicity of lit-html.",
5
5
  "author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
6
6
  "license": "MIT",
@@ -53,16 +53,20 @@
53
53
  },
54
54
  "files": [
55
55
  "src",
56
- "types"
56
+ "types",
57
+ "!src/**/*.test.js"
57
58
  ],
58
59
  "publishConfig": {
59
60
  "access": "public"
60
61
  },
61
- "sideEffects": false,
62
+ "sideEffects": [
63
+ "**/*.css"
64
+ ],
62
65
  "dependencies": {
66
+ "@lit-labs/ssr-client": "^1.1.8",
63
67
  "lit-html": "^3.3.1",
64
- "@inglorious/utils": "3.7.0",
65
- "@inglorious/store": "8.0.0"
68
+ "@inglorious/store": "9.0.0",
69
+ "@inglorious/utils": "3.7.1"
66
70
  },
67
71
  "devDependencies": {
68
72
  "prettier": "^3.6.2",
package/src/mount.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { hydrate } from "@lit-labs/ssr-client"
1
2
  import { html, render } from "lit-html"
2
3
 
3
4
  /**
@@ -11,20 +12,19 @@ export function mount(store, renderFn, element) {
11
12
  const api = { ...store._api }
12
13
  api.render = createRender(api)
13
14
 
14
- // let renderScheduled = false
15
+ let shouldHydrate = element.hasChildNodes()
15
16
 
16
- // const scheduleRender = () => {
17
- // if (!renderScheduled) {
18
- // renderScheduled = true
19
- // requestAnimationFrame(() => {
20
- // renderScheduled = false
21
- // })
22
- // render(renderFn(api), element)
23
- // }
24
- // }
17
+ const unsubscribe = store.subscribe(() => {
18
+ const template = renderFn(api)
19
+
20
+ if (shouldHydrate) {
21
+ hydrate(template, element)
22
+ shouldHydrate = false
23
+ } else {
24
+ render(template, element)
25
+ }
26
+ })
25
27
 
26
- // const unsubscribe = store.subscribe(scheduleRender)
27
- const unsubscribe = store.subscribe(() => render(renderFn(api), element))
28
28
  store.notify("init")
29
29
 
30
30
  return unsubscribe
@@ -7,6 +7,7 @@
7
7
  const SKIP_FULL_MATCH_GROUP = 1 // .match() result at index 0 is the full string
8
8
  const REMOVE_COLON_PREFIX = 1
9
9
 
10
+ const routeConfig = {}
10
11
  let areListenersInitialized = false
11
12
 
12
13
  /**
@@ -25,7 +26,7 @@ export const router = {
25
26
  // Handle initial route
26
27
  const { pathname, search } = window.location
27
28
  const initialPath = pathname + search
28
- const route = findRoute(entity.routes, initialPath)
29
+ const route = findRoute(routeConfig, initialPath)
29
30
  const entityId = entity.id
30
31
 
31
32
  if (route) {
@@ -42,8 +43,7 @@ export const router = {
42
43
  // Listen for browser back/forward
43
44
  window.addEventListener("popstate", () => {
44
45
  const path = window.location.pathname + window.location.search
45
- const { routes } = api.getEntity(entityId)
46
- const route = findRoute(routes, path)
46
+ const route = findRoute(routeConfig, path)
47
47
 
48
48
  if (route) {
49
49
  api.notify(`#${entityId}:routeSync`, {
@@ -83,18 +83,6 @@ export const router = {
83
83
  })
84
84
  },
85
85
 
86
- create(entity) {
87
- entity.routes ??= {}
88
- },
89
-
90
- routeAdd(entity, route) {
91
- entity.routes[route.path] = route.entityType
92
- },
93
-
94
- routeRemove(entity, path) {
95
- delete [entity.routes[path]]
96
- },
97
-
98
86
  /**
99
87
  * Handles navigation to a new route.
100
88
  * @param {RouterEntity} entity - The router entity.
@@ -122,9 +110,9 @@ export const router = {
122
110
  }
123
111
 
124
112
  // If "to" is already a final path (like "/users/1"), use it directly
125
- // The router will match it against patterns in entity.routes
113
+ // The router will match it against patterns in routeConfig
126
114
 
127
- const route = findRoute(entity.routes, path)
115
+ const route = findRoute(routeConfig, path)
128
116
 
129
117
  if (!route) {
130
118
  console.warn(`No route matches path: ${path}`)
@@ -142,7 +130,7 @@ export const router = {
142
130
 
143
131
  // Asynchronous navigation
144
132
  if (typeof route.entityType === "function") {
145
- entity.loading = true
133
+ entity.isLoading = true
146
134
  entity.error = null
147
135
  const entityId = entity.id
148
136
 
@@ -186,14 +174,16 @@ export const router = {
186
174
  loadSuccess(entity, payload, api) {
187
175
  const { module, route, path, replace, state } = payload
188
176
 
189
- const [[typeName, type]] = Object.entries(module)
177
+ const [typeName, type] = Object.entries(module).find(
178
+ ([, type]) => type?.render,
179
+ )
190
180
 
191
- api.notify("morph", { name: typeName, type })
181
+ api.setType(typeName, type)
192
182
 
193
- entity.routes[route.pattern] = typeName
183
+ routeConfig[route.pattern] = typeName
194
184
 
195
185
  // Complete the navigation
196
- entity.loading = false
186
+ entity.isLoading = false
197
187
 
198
188
  doNavigate(
199
189
  entity,
@@ -211,7 +201,7 @@ export const router = {
211
201
  const { error, path } = payload
212
202
  console.error(`Failed to load route ${path}:`, error)
213
203
  entity.path = path
214
- entity.loading = false
204
+ entity.isLoading = false
215
205
  entity.error = error
216
206
  },
217
207
 
@@ -225,6 +215,49 @@ export const router = {
225
215
  },
226
216
  }
227
217
 
218
+ /**
219
+ * Retrieves the current route configuration.
220
+ * @returns {Record<string, string|function>} The current route configuration.
221
+ */
222
+ export function getRoutes() {
223
+ return routeConfig
224
+ }
225
+
226
+ /**
227
+ * Retrieves a single route configuration given its path.
228
+ * @param {string} path - The path of the route to retrieve.
229
+ * @returns {string|function|undefined} The route configuration or undefined if not found.
230
+ */
231
+ export function getRoute(path) {
232
+ return routeConfig[path]
233
+ }
234
+
235
+ /**
236
+ * Sets or updates routes in the route configuration.
237
+ * Can be used both during initialization and at any point to add or update routes dynamically.
238
+ * @param {Record<string, string|function>} routes - An object mapping route paths/patterns to entity type names or loader functions.
239
+ */
240
+ export function setRoutes(routes) {
241
+ Object.assign(routeConfig, routes)
242
+ }
243
+
244
+ /**
245
+ * Adds a single route to the route configuration.
246
+ * @param {string} path - The route path or pattern (e.g., "/users/:userId").
247
+ * @param {string|function} route - The entity type name or a function that dynamically loads it.
248
+ */
249
+ export function addRoute(path, route) {
250
+ routeConfig[path] = route
251
+ }
252
+
253
+ /**
254
+ * Removes a route from the route configuration.
255
+ * @param {string} path - The route path or pattern to remove.
256
+ */
257
+ export function removeRoute(path) {
258
+ delete routeConfig[path]
259
+ }
260
+
228
261
  /**
229
262
  * Builds a URL path by substituting parameters into a route pattern.
230
263
  * @param {string} pattern - The route pattern (e.g., "/users/:userId").
@@ -244,16 +277,16 @@ function buildPath(pattern, params = {}) {
244
277
  /**
245
278
  * Finds a matching route configuration for a given URL path.
246
279
  * It supports parameterized routes and a fallback "*" route.
247
- * @param {Record<string, string>} routes - The routes configuration map.
280
+ * @param {Record<string, string>} routeConfig - The routes configuration map.
248
281
  * @param {string} path - The URL path to match.
249
282
  * @returns {{pattern: string, entityType: string, params: Record<string, string>, path: string}|null}
250
283
  * The matched route object or null if no match is found.
251
284
  */
252
- function findRoute(routes, path) {
285
+ function findRoute(routeConfig, path) {
253
286
  const [pathname] = path.split("?")
254
287
  let fallbackRoute = null
255
288
 
256
- for (const [pattern, entityType] of Object.entries(routes)) {
289
+ for (const [pattern, entityType] of Object.entries(routeConfig)) {
257
290
  if (pattern === "*") {
258
291
  fallbackRoute = { pattern, entityType, params: {}, path: pathname }
259
292
  continue
package/types/router.d.ts CHANGED
@@ -26,8 +26,6 @@ export type QueryParams = Record<string, string>
26
26
  export interface RouterEntity {
27
27
  /** A unique identifier for the router entity. */
28
28
  id: string | number
29
- /** The route configuration. */
30
- routes: RoutesConfig
31
29
  /** The current active path, without query string or hash. */
32
30
  path?: string
33
31
  /** The entity type of the current active route. */
@@ -38,6 +36,10 @@ export interface RouterEntity {
38
36
  query?: QueryParams
39
37
  /** The hash from the current URL. */
40
38
  hash?: string
39
+ /** Whether a route is currently loading asynchronously. */
40
+ isLoading?: boolean
41
+ /** An error that occurred during route loading. */
42
+ error?: Error | null
41
43
  }
42
44
 
43
45
  /**
@@ -98,7 +100,7 @@ export interface RouterType {
98
100
  entity: RouterEntity,
99
101
  payload: string | number | NavigatePayload,
100
102
  api: StoreApi,
101
- ): void
103
+ ): void | Promise<void>
102
104
 
103
105
  /**
104
106
  * Synchronizes the router entity's state with data from a routing event,
@@ -128,3 +130,41 @@ export interface RouterType {
128
130
  */
129
131
  loadError(entity: RouterEntity, payload: any): void
130
132
  }
133
+
134
+ /**
135
+ * Returns the current route configuration.
136
+ * @returns {Record<string, string|function>} The current route configuration.
137
+ */
138
+ export function getRoutes(): Record<string, string | (() => Promise<any>)>
139
+
140
+ /**
141
+ * Retrieves a single route configuration given its path.
142
+ * @param {string} path - The path of the route to retrieve.
143
+ * @returns {string|function|undefined} The route configuration or undefined if not found.
144
+ */
145
+ export function getRoute(path: string): string | (() => Promise<any>)
146
+
147
+ /**
148
+ * Sets or updates routes in the route configuration.
149
+ * Can be used both during initialization and at any point to add or update routes dynamically.
150
+ * @param routes An object mapping route paths/patterns to entity type names or loader functions.
151
+ */
152
+ export function setRoutes(
153
+ routes: Record<string, string | (() => Promise<any>)>,
154
+ ): void
155
+
156
+ /**
157
+ * Adds a single route to the route configuration.
158
+ * @param path The route path or pattern (e.g., "/users/:userId").
159
+ * @param route The entity type name or a function that dynamically loads it.
160
+ */
161
+ export function addRoute(
162
+ path: string,
163
+ route: string | (() => Promise<any>),
164
+ ): void
165
+
166
+ /**
167
+ * Removes a route from the route configuration.
168
+ * @param path The route path or pattern to remove.
169
+ */
170
+ export function removeRoute(path: string): void