@inglorious/web 2.2.0 → 2.2.1

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
@@ -412,9 +412,9 @@ This method is the cornerstone of entity-based rendering. It looks up an entity
412
412
 
413
413
  **`api.select(selectorFn)`**
414
414
 
415
- Selects a slice of the application state and returns a reactive object. This is useful for creating components that only depend on a small part of the state, avoiding unnecessary re-renders.
415
+ Selects a slice of the application state and returns a reactive getter function. This is useful for creating components that only depend on a small part of the state, avoiding unnecessary re-renders.
416
416
 
417
- The `selectorFn` receives the `api` and should return a value. The `select` method returns an object with a `value` property and an `unsubscribe` function.
417
+ The `selectorFn` receives the `api` and should return a value. The `select` method returns a getter function that you call to get the latest value. This function also has an `unsubscribe` property.
418
418
 
419
419
  **Parameters:**
420
420
 
@@ -422,7 +422,7 @@ The `selectorFn` receives the `api` and should return a value. The `select` meth
422
422
 
423
423
  **Returns:**
424
424
 
425
- - `{ value, unsubscribe }`: A reactive result object.
425
+ - `() => T`: A reactive getter function. It also has an `unsubscribe` method attached.
426
426
 
427
427
  **Example:**
428
428
 
@@ -430,7 +430,8 @@ While `mount` re-renders the entire application on any state change, `api.select
430
430
 
431
431
  ```javascript
432
432
  // Inside a component's render method
433
- const { value: user } = api.select((api) => api.getEntity("user-1"))
433
+ const getUser = api.select((api) => api.getEntity("user-1"))
434
+ const user = getUser() // Call the getter to get the value
434
435
  ```
435
436
 
436
437
  ### Re-exported `lit-html` Utilities
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/web",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
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",
@@ -38,8 +38,8 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "lit-html": "^3.3.1",
41
- "@inglorious/store": "7.1.1",
42
- "@inglorious/utils": "3.7.0"
41
+ "@inglorious/utils": "3.7.0",
42
+ "@inglorious/store": "7.1.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "prettier": "^3.6.2",
package/src/mount.js CHANGED
@@ -22,13 +22,15 @@ export function mount(store, renderFn, element) {
22
22
  * Creates a reactive selector function for the mount API.
23
23
  * @param {import('../types/mount').Api} api - The mount API.
24
24
  * @param {import('@inglorious/store').Store} store - The application state store.
25
- * @returns {import('../types/mount').Api['select']} A `select` function that can be used to get a reactive slice of the state.
25
+ * @returns {import('../types/mount').Api['select']} A `select` function that returns a reactive getter.
26
26
  * @private
27
27
  */
28
28
  function createReactiveSelector(api, store) {
29
29
  return function select(selectorFn) {
30
30
  let current = selectorFn(api)
31
31
 
32
+ const getter = () => current // stable function, lit-html will call this each render
33
+
32
34
  const unsubscribe = store.subscribe(() => {
33
35
  const next = selectorFn(api)
34
36
  if (next !== current) {
@@ -36,12 +38,8 @@ function createReactiveSelector(api, store) {
36
38
  }
37
39
  })
38
40
 
39
- return {
40
- get value() {
41
- return current
42
- },
43
- unsubscribe,
44
- }
41
+ getter.unsubscribe = unsubscribe
42
+ return getter
45
43
  }
46
44
  }
47
45
 
package/src/mount.test.js CHANGED
@@ -116,11 +116,11 @@ describe("mount", () => {
116
116
  capturedSelectResult = api.select(
117
117
  (api) => api.getEntity("player-1").score,
118
118
  )
119
- return html`<div>Selected: ${capturedSelectResult.value}</div>`
119
+ return html`<div>Selected: ${capturedSelectResult()}</div>`
120
120
  }
121
121
  mount(store, renderFn, rootElement)
122
122
 
123
- expect(capturedSelectResult.value).toBe(0)
123
+ expect(capturedSelectResult()).toBe(0)
124
124
  expect(rootElement.textContent).toBe("Selected: 0")
125
125
  })
126
126
 
@@ -130,16 +130,16 @@ describe("mount", () => {
130
130
  capturedSelectResult = api.select(
131
131
  (api) => api.getEntity("player-1").score,
132
132
  )
133
- return html`<div>Selected: ${capturedSelectResult.value}</div>`
133
+ return html`<div>Selected: ${capturedSelectResult()}</div>`
134
134
  }
135
135
  mount(store, renderFn, rootElement)
136
136
 
137
- expect(capturedSelectResult.value).toBe(0)
137
+ expect(capturedSelectResult()).toBe(0)
138
138
 
139
139
  store.notify("incrementScore", "player-1")
140
140
  await Promise.resolve() // Wait for store subscription to trigger and update `current` in select, and for lit-html to render
141
141
 
142
- expect(capturedSelectResult.value).toBe(1)
142
+ expect(capturedSelectResult()).toBe(1)
143
143
  expect(rootElement.textContent).toBe("Selected: 1")
144
144
  })
145
145
 
@@ -149,16 +149,16 @@ describe("mount", () => {
149
149
  capturedSelectResult = api.select(
150
150
  (api) => api.getEntity("player-1").score,
151
151
  )
152
- return html`<div>Selected: ${capturedSelectResult.value}</div>`
152
+ return html`<div>Selected: ${capturedSelectResult()}</div>`
153
153
  }
154
154
  mount(store, renderFn, rootElement)
155
155
 
156
- expect(capturedSelectResult.value).toBe(0)
156
+ expect(capturedSelectResult()).toBe(0)
157
157
 
158
158
  store.notify("takeDamage", "enemy-1", 10) // This changes enemy health, not player score
159
159
  await Promise.resolve()
160
160
 
161
- expect(capturedSelectResult.value).toBe(0) // Should remain 0
161
+ expect(capturedSelectResult()).toBe(0) // Should remain 0
162
162
  expect(rootElement.textContent).toBe("Selected: 0") // DOM also remains 0
163
163
  })
164
164
 
@@ -172,19 +172,19 @@ describe("mount", () => {
172
172
  (api) => api.getEntity("player-1").score,
173
173
  )
174
174
  }
175
- return html`<div>Selected: ${initialSelectResult.value}</div>`
175
+ return html`<div>Selected: ${initialSelectResult()}</div>`
176
176
  }
177
177
  mount(store, renderFn, rootElement)
178
178
 
179
- expect(initialSelectResult.value).toBe(0)
179
+ expect(initialSelectResult()).toBe(0)
180
180
 
181
181
  initialSelectResult.unsubscribe() // Unsubscribe the reactive selector's internal listener
182
182
  store.notify("incrementScore", "player-1")
183
183
  await Promise.resolve() // Wait for store subscription to trigger and for lit-html to render
184
184
 
185
- // The main mount subscription still triggers renderFn, but the `initialSelectResult.value`
185
+ // The main mount subscription still triggers renderFn, but the value from `initialSelectResult()`
186
186
  // should *not* have updated because its internal listener was unsubscribed.
187
- expect(initialSelectResult.value).toBe(0) // Should remain 0
187
+ expect(initialSelectResult()).toBe(0) // Should remain 0
188
188
  expect(rootElement.textContent).toBe("Selected: 0")
189
189
  expect(rootElement.textContent).not.toBe("Selected: 1")
190
190
  })
package/types/mount.d.ts CHANGED
@@ -2,24 +2,24 @@ import type { TemplateResult } from "lit-html"
2
2
  import type { Store, Api as StoreApi } from "@inglorious/store"
3
3
 
4
4
  /**
5
- * The result of a reactive selector.
5
+ * A reactive getter function returned by `api.select`.
6
+ * Call the function to get the current value.
7
+ * The function also has an `unsubscribe` method to stop listening for updates.
6
8
  * @template T The type of the selected value.
7
9
  */
8
- export type ReactiveSelectorResult<T> = {
9
- /** The current value of the selected state. */
10
- readonly value: T
10
+ export type ReactiveSelectorResult<T> = (() => T) & {
11
11
  /** A function to stop listening for updates. */
12
12
  unsubscribe: () => void
13
13
  }
14
14
 
15
15
  export type Api = StoreApi & {
16
16
  /**
17
- * Selects a slice of the application state and returns a reactive object.
17
+ * Selects a slice of the application state and returns a reactive getter function.
18
18
  * The value will update whenever the selected part of the state changes.
19
19
  *
20
20
  * @template T The type of the selected state slice.
21
21
  * @param selectorFn A function that takes the API and returns a slice of the state.
22
- * @returns A reactive result object with the current value and an unsubscribe function.
22
+ * @returns A reactive getter function. Call it to get the value. It also has an `unsubscribe` method.
23
23
  */
24
24
  select: <T>(selectorFn: (api: Api) => T) => ReactiveSelectorResult<T>
25
25