@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 +5 -4
- package/package.json +3 -3
- package/src/mount.js +5 -7
- package/src/mount.test.js +12 -12
- package/types/mount.d.ts +6 -6
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
|
|
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
|
|
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
|
-
- `
|
|
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
|
|
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.
|
|
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/
|
|
42
|
-
"@inglorious/
|
|
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
|
|
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
|
-
|
|
40
|
-
|
|
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
|
|
119
|
+
return html`<div>Selected: ${capturedSelectResult()}</div>`
|
|
120
120
|
}
|
|
121
121
|
mount(store, renderFn, rootElement)
|
|
122
122
|
|
|
123
|
-
expect(capturedSelectResult
|
|
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
|
|
133
|
+
return html`<div>Selected: ${capturedSelectResult()}</div>`
|
|
134
134
|
}
|
|
135
135
|
mount(store, renderFn, rootElement)
|
|
136
136
|
|
|
137
|
-
expect(capturedSelectResult
|
|
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
|
|
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
|
|
152
|
+
return html`<div>Selected: ${capturedSelectResult()}</div>`
|
|
153
153
|
}
|
|
154
154
|
mount(store, renderFn, rootElement)
|
|
155
155
|
|
|
156
|
-
expect(capturedSelectResult
|
|
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
|
|
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
|
|
175
|
+
return html`<div>Selected: ${initialSelectResult()}</div>`
|
|
176
176
|
}
|
|
177
177
|
mount(store, renderFn, rootElement)
|
|
178
178
|
|
|
179
|
-
expect(initialSelectResult
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|