@inglorious/web 2.1.1 → 2.2.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 +27 -2
- package/package.json +5 -2
- package/src/index.js +1 -0
- package/src/mount.js +65 -31
- package/src/mount.test.js +255 -0
- package/types/mount.d.ts +29 -4
package/README.md
CHANGED
|
@@ -404,9 +404,34 @@ Connects a store to a `lit-html` template and renders it into a DOM element. It
|
|
|
404
404
|
|
|
405
405
|
### The `api` Object
|
|
406
406
|
|
|
407
|
-
The `renderFn` receives a powerful `api` object that contains all methods from the store's API (`getEntities`, `getEntity`, `notify`, etc.) plus
|
|
407
|
+
The `renderFn` receives a powerful `api` object that contains all methods from the store's API (`getEntities`, `getEntity`, `notify`, etc.) plus special methods for the web package.
|
|
408
408
|
|
|
409
|
-
|
|
409
|
+
**`api.render(id, options?)`**
|
|
410
|
+
|
|
411
|
+
This 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.
|
|
412
|
+
|
|
413
|
+
**`api.select(selectorFn)`**
|
|
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.
|
|
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.
|
|
418
|
+
|
|
419
|
+
**Parameters:**
|
|
420
|
+
|
|
421
|
+
- `selectorFn(api)` (required): A function that takes the `api` and returns a slice of the state.
|
|
422
|
+
|
|
423
|
+
**Returns:**
|
|
424
|
+
|
|
425
|
+
- `{ value, unsubscribe }`: A reactive result object.
|
|
426
|
+
|
|
427
|
+
**Example:**
|
|
428
|
+
|
|
429
|
+
While `mount` re-renders the entire application on any state change, `api.select` can be used inside a component to react only to specific changes. This is an advanced pattern for performance optimization.
|
|
430
|
+
|
|
431
|
+
```javascript
|
|
432
|
+
// Inside a component's render method
|
|
433
|
+
const { value: user } = api.select((api) => api.getEntity("user-1"))
|
|
434
|
+
```
|
|
410
435
|
|
|
411
436
|
### Re-exported `lit-html` Utilities
|
|
412
437
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/web",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.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",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"prettier": "^3.6.2",
|
|
46
46
|
"vite": "^7.1.3",
|
|
47
|
+
"vitest": "^4.0.15",
|
|
47
48
|
"@inglorious/eslint-config": "1.1.0"
|
|
48
49
|
},
|
|
49
50
|
"engines": {
|
|
@@ -51,6 +52,8 @@
|
|
|
51
52
|
},
|
|
52
53
|
"scripts": {
|
|
53
54
|
"format": "prettier --write '**/*.{js,jsx}'",
|
|
54
|
-
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0"
|
|
55
|
+
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
|
56
|
+
"test:watch": "vitest",
|
|
57
|
+
"test": "vitest run"
|
|
55
58
|
}
|
|
56
59
|
}
|
package/src/index.js
CHANGED
|
@@ -12,3 +12,4 @@ export { classMap } from "lit-html/directives/class-map.js"
|
|
|
12
12
|
export { ref } from "lit-html/directives/ref.js"
|
|
13
13
|
export { repeat } from "lit-html/directives/repeat.js"
|
|
14
14
|
export { styleMap } from "lit-html/directives/style-map.js"
|
|
15
|
+
export { when } from "lit-html/directives/when.js"
|
package/src/mount.js
CHANGED
|
@@ -8,41 +8,75 @@ import { html, render } from "lit-html"
|
|
|
8
8
|
* @returns {() => void} An unsubscribe function
|
|
9
9
|
*/
|
|
10
10
|
export function mount(store, renderFn, element) {
|
|
11
|
-
const api = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
/** @param {string} id */
|
|
15
|
-
render(id, options = {}) {
|
|
16
|
-
const entity = api.getEntity(id)
|
|
17
|
-
|
|
18
|
-
if (!entity) {
|
|
19
|
-
const { allowType } = options
|
|
20
|
-
if (!allowType) {
|
|
21
|
-
return ""
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// No entity with this ID, try static type
|
|
25
|
-
const type = api.getType(id)
|
|
26
|
-
if (!type?.render) {
|
|
27
|
-
console.warn(`No entity or type found: ${id}`)
|
|
28
|
-
return html`<div>Not found: ${id}</div>`
|
|
29
|
-
}
|
|
30
|
-
return type.render(api)
|
|
31
|
-
}
|
|
11
|
+
const api = { ...store._api }
|
|
12
|
+
api.select = createReactiveSelector(api, store)
|
|
13
|
+
api.render = createRender(api)
|
|
32
14
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
15
|
+
const unsubscribe = store.subscribe(() => render(renderFn(api), element))
|
|
16
|
+
store.notify("init")
|
|
17
|
+
|
|
18
|
+
return unsubscribe
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a reactive selector function for the mount API.
|
|
23
|
+
* @param {import('../types/mount').Api} api - The mount API.
|
|
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.
|
|
26
|
+
* @private
|
|
27
|
+
*/
|
|
28
|
+
function createReactiveSelector(api, store) {
|
|
29
|
+
return function select(selectorFn) {
|
|
30
|
+
let current = selectorFn(api)
|
|
31
|
+
|
|
32
|
+
const unsubscribe = store.subscribe(() => {
|
|
33
|
+
const next = selectorFn(api)
|
|
34
|
+
if (next !== current) {
|
|
35
|
+
current = next
|
|
38
36
|
}
|
|
37
|
+
})
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
return {
|
|
40
|
+
get value() {
|
|
41
|
+
return current
|
|
42
|
+
},
|
|
43
|
+
unsubscribe,
|
|
44
|
+
}
|
|
42
45
|
}
|
|
46
|
+
}
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Creates a render function for the mount API.
|
|
50
|
+
* @param {import('../types/mount').Api} api - The mount API.
|
|
51
|
+
* @returns {import('../types/mount').Api['render']} A `render` function that can render an entity or a type by its ID.
|
|
52
|
+
* @private
|
|
53
|
+
*/
|
|
54
|
+
function createRender(api) {
|
|
55
|
+
return function (id, options = {}) {
|
|
56
|
+
const entity = api.getEntity(id)
|
|
46
57
|
|
|
47
|
-
|
|
58
|
+
if (!entity) {
|
|
59
|
+
const { allowType } = options
|
|
60
|
+
if (!allowType) {
|
|
61
|
+
return ""
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// No entity with this ID, try static type
|
|
65
|
+
const type = api.getType(id)
|
|
66
|
+
if (!type?.render) {
|
|
67
|
+
console.warn(`No entity or type found: ${id}`)
|
|
68
|
+
return html`<div>Not found: ${id}</div>`
|
|
69
|
+
}
|
|
70
|
+
return type.render(api)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Entity exists, render it
|
|
74
|
+
const type = api.getType(entity.type)
|
|
75
|
+
if (!type?.render) {
|
|
76
|
+
console.warn(`No render function for type: ${entity.type}`)
|
|
77
|
+
return html`<div>No renderer for ${entity.type}</div>`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return type.render(entity, api)
|
|
81
|
+
}
|
|
48
82
|
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createStore } from "@inglorious/store"
|
|
6
|
+
import { html } from "lit-html" // Only html is needed for templates, render is handled by mount
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
|
8
|
+
|
|
9
|
+
import { mount } from "./mount.js"
|
|
10
|
+
|
|
11
|
+
let rootElement
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Create a fresh DOM element for each test
|
|
15
|
+
rootElement = document.createElement("div")
|
|
16
|
+
document.body.appendChild(rootElement)
|
|
17
|
+
// Spy on console.warn to check for warnings without polluting test output
|
|
18
|
+
vi.spyOn(console, "warn").mockImplementation(() => {})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
// Clean up the DOM element after each test
|
|
23
|
+
document.body.removeChild(rootElement)
|
|
24
|
+
// Restore console.warn to its original implementation
|
|
25
|
+
vi.restoreAllMocks()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// Helper function to create a store with some common entities and types for testing
|
|
29
|
+
function setupStore(entitiesOverride = {}, typesOverride = {}) {
|
|
30
|
+
return createStore({
|
|
31
|
+
// Pass 'entities' as a top-level property to createStore
|
|
32
|
+
entities: {
|
|
33
|
+
"player-1": {
|
|
34
|
+
id: "player-1",
|
|
35
|
+
type: "player",
|
|
36
|
+
name: "Player One",
|
|
37
|
+
score: 0,
|
|
38
|
+
},
|
|
39
|
+
"enemy-1": { id: "enemy-1", type: "enemy", health: 100 },
|
|
40
|
+
...entitiesOverride,
|
|
41
|
+
},
|
|
42
|
+
// Pass 'types' as a top-level property to createStore
|
|
43
|
+
types: {
|
|
44
|
+
player: {
|
|
45
|
+
render: (entity) =>
|
|
46
|
+
html`<span>Player: ${entity.name}, Score: ${entity.score}</span>`,
|
|
47
|
+
incrementScore: (entity) => {
|
|
48
|
+
entity.score++
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
enemy: {
|
|
52
|
+
render: (entity) => html`<span>Enemy: ${entity.health} HP</span>`,
|
|
53
|
+
takeDamage: (entity, amount) => {
|
|
54
|
+
entity.health -= amount
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
simpleType: {
|
|
58
|
+
render: () => html`<span>Simple Type Rendered</span>`,
|
|
59
|
+
},
|
|
60
|
+
typeWithoutRender: {
|
|
61
|
+
/* no render function */
|
|
62
|
+
},
|
|
63
|
+
...typesOverride,
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
describe("mount", () => {
|
|
69
|
+
it("should render the initial state into the element", () => {
|
|
70
|
+
const store = setupStore()
|
|
71
|
+
const renderFn = (api) =>
|
|
72
|
+
html`<div>Hello, ${api.getEntity("player-1").name}!</div>`
|
|
73
|
+
mount(store, renderFn, rootElement)
|
|
74
|
+
|
|
75
|
+
expect(rootElement.textContent).toBe("Hello, Player One!")
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it("should re-render when the store state changes", async () => {
|
|
79
|
+
const store = setupStore()
|
|
80
|
+
const renderFn = (api) =>
|
|
81
|
+
html`<div>Score: ${api.getEntity("player-1").score}</div>`
|
|
82
|
+
mount(store, renderFn, rootElement)
|
|
83
|
+
|
|
84
|
+
expect(rootElement.textContent).toBe("Score: 0")
|
|
85
|
+
|
|
86
|
+
store.notify("incrementScore", "player-1")
|
|
87
|
+
// lit-html renders asynchronously, so we need to wait for the next microtask
|
|
88
|
+
await Promise.resolve()
|
|
89
|
+
|
|
90
|
+
expect(rootElement.textContent).toBe("Score: 1")
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it("should stop re-rendering after unsubscribe is called", async () => {
|
|
94
|
+
const store = setupStore()
|
|
95
|
+
const renderFn = (api) =>
|
|
96
|
+
html`<div>Score: ${api.getEntity("player-1").score}</div>`
|
|
97
|
+
const unsubscribe = mount(store, renderFn, rootElement)
|
|
98
|
+
|
|
99
|
+
expect(rootElement.textContent).toBe("Score: 0")
|
|
100
|
+
|
|
101
|
+
unsubscribe()
|
|
102
|
+
store.notify("incrementScore", "player-1")
|
|
103
|
+
await Promise.resolve() // Wait for potential re-render
|
|
104
|
+
|
|
105
|
+
// The score should still be 0 in the DOM because unsubscribe was called
|
|
106
|
+
expect(rootElement.textContent).toBe("Score: 0")
|
|
107
|
+
expect(rootElement.textContent).not.toBe("Score: 1")
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
describe("api.select", () => {
|
|
111
|
+
let capturedSelectResult // Used to capture the ReactiveSelectorResult instance across renders
|
|
112
|
+
|
|
113
|
+
it("should return the initial selected value", () => {
|
|
114
|
+
const store = setupStore()
|
|
115
|
+
const renderFn = (api) => {
|
|
116
|
+
capturedSelectResult = api.select(
|
|
117
|
+
(api) => api.getEntity("player-1").score,
|
|
118
|
+
)
|
|
119
|
+
return html`<div>Selected: ${capturedSelectResult.value}</div>`
|
|
120
|
+
}
|
|
121
|
+
mount(store, renderFn, rootElement)
|
|
122
|
+
|
|
123
|
+
expect(capturedSelectResult.value).toBe(0)
|
|
124
|
+
expect(rootElement.textContent).toBe("Selected: 0")
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it("should update the selected value when the relevant state changes", async () => {
|
|
128
|
+
const store = setupStore()
|
|
129
|
+
const renderFn = (api) => {
|
|
130
|
+
capturedSelectResult = api.select(
|
|
131
|
+
(api) => api.getEntity("player-1").score,
|
|
132
|
+
)
|
|
133
|
+
return html`<div>Selected: ${capturedSelectResult.value}</div>`
|
|
134
|
+
}
|
|
135
|
+
mount(store, renderFn, rootElement)
|
|
136
|
+
|
|
137
|
+
expect(capturedSelectResult.value).toBe(0)
|
|
138
|
+
|
|
139
|
+
store.notify("incrementScore", "player-1")
|
|
140
|
+
await Promise.resolve() // Wait for store subscription to trigger and update `current` in select, and for lit-html to render
|
|
141
|
+
|
|
142
|
+
expect(capturedSelectResult.value).toBe(1)
|
|
143
|
+
expect(rootElement.textContent).toBe("Selected: 1")
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it("should not update the selected value when unrelated state changes", async () => {
|
|
147
|
+
const store = setupStore()
|
|
148
|
+
const renderFn = (api) => {
|
|
149
|
+
capturedSelectResult = api.select(
|
|
150
|
+
(api) => api.getEntity("player-1").score,
|
|
151
|
+
)
|
|
152
|
+
return html`<div>Selected: ${capturedSelectResult.value}</div>`
|
|
153
|
+
}
|
|
154
|
+
mount(store, renderFn, rootElement)
|
|
155
|
+
|
|
156
|
+
expect(capturedSelectResult.value).toBe(0)
|
|
157
|
+
|
|
158
|
+
store.notify("takeDamage", "enemy-1", 10) // This changes enemy health, not player score
|
|
159
|
+
await Promise.resolve()
|
|
160
|
+
|
|
161
|
+
expect(capturedSelectResult.value).toBe(0) // Should remain 0
|
|
162
|
+
expect(rootElement.textContent).toBe("Selected: 0") // DOM also remains 0
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it("should stop updating after api.select unsubscribe is called", async () => {
|
|
166
|
+
const store = setupStore()
|
|
167
|
+
let initialSelectResult
|
|
168
|
+
const renderFn = (api) => {
|
|
169
|
+
// Capture the select result only on the first render to test its specific unsubscribe
|
|
170
|
+
if (!initialSelectResult) {
|
|
171
|
+
initialSelectResult = api.select(
|
|
172
|
+
(api) => api.getEntity("player-1").score,
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
return html`<div>Selected: ${initialSelectResult.value}</div>`
|
|
176
|
+
}
|
|
177
|
+
mount(store, renderFn, rootElement)
|
|
178
|
+
|
|
179
|
+
expect(initialSelectResult.value).toBe(0)
|
|
180
|
+
|
|
181
|
+
initialSelectResult.unsubscribe() // Unsubscribe the reactive selector's internal listener
|
|
182
|
+
store.notify("incrementScore", "player-1")
|
|
183
|
+
await Promise.resolve() // Wait for store subscription to trigger and for lit-html to render
|
|
184
|
+
|
|
185
|
+
// The main mount subscription still triggers renderFn, but the `initialSelectResult.value`
|
|
186
|
+
// should *not* have updated because its internal listener was unsubscribed.
|
|
187
|
+
expect(initialSelectResult.value).toBe(0) // Should remain 0
|
|
188
|
+
expect(rootElement.textContent).toBe("Selected: 0")
|
|
189
|
+
expect(rootElement.textContent).not.toBe("Selected: 1")
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
describe("api.render", () => {
|
|
194
|
+
it("should render an entity by its ID", async () => {
|
|
195
|
+
const store = setupStore()
|
|
196
|
+
const renderFn = (api) => html`<div>${api.render("player-1")}</div>`
|
|
197
|
+
mount(store, renderFn, rootElement)
|
|
198
|
+
await Promise.resolve()
|
|
199
|
+
|
|
200
|
+
expect(rootElement.textContent).toBe("Player: Player One, Score: 0")
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it("should render a type by its ID when allowType is true", async () => {
|
|
204
|
+
const store = setupStore()
|
|
205
|
+
const renderFn = (api) =>
|
|
206
|
+
html`<div>${api.render("simpleType", { allowType: true })}</div>`
|
|
207
|
+
mount(store, renderFn, rootElement)
|
|
208
|
+
await Promise.resolve()
|
|
209
|
+
|
|
210
|
+
expect(rootElement.textContent).toBe("Simple Type Rendered")
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it("should return an empty string for a non-existent entity/type without allowType", async () => {
|
|
214
|
+
const store = setupStore()
|
|
215
|
+
const renderFn = (api) =>
|
|
216
|
+
html`<div>${api.render("non-existent-id")}</div>`
|
|
217
|
+
mount(store, renderFn, rootElement)
|
|
218
|
+
await Promise.resolve()
|
|
219
|
+
|
|
220
|
+
expect(rootElement.textContent).toBe("") // Empty div because api.render returns ""
|
|
221
|
+
expect(console.warn).not.toHaveBeenCalled() // No warning for non-existent entity without allowType
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it("should return a fallback template and warn for a non-existent entity/type with allowType", async () => {
|
|
225
|
+
const store = setupStore()
|
|
226
|
+
const renderFn = (api) =>
|
|
227
|
+
html`<div>${api.render("non-existent-id", { allowType: true })}</div>`
|
|
228
|
+
mount(store, renderFn, rootElement)
|
|
229
|
+
await Promise.resolve()
|
|
230
|
+
|
|
231
|
+
expect(rootElement.textContent).toBe("Not found: non-existent-id")
|
|
232
|
+
expect(console.warn).toHaveBeenCalledWith(
|
|
233
|
+
"No entity or type found: non-existent-id",
|
|
234
|
+
)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it("should return a fallback template and warn for an entity whose type has no render function", async () => {
|
|
238
|
+
const store = setupStore({
|
|
239
|
+
"no-render-entity": {
|
|
240
|
+
id: "no-render-entity",
|
|
241
|
+
type: "typeWithoutRender",
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
const renderFn = (api) =>
|
|
245
|
+
html`<div>${api.render("no-render-entity")}</div>`
|
|
246
|
+
mount(store, renderFn, rootElement)
|
|
247
|
+
await Promise.resolve()
|
|
248
|
+
|
|
249
|
+
expect(rootElement.textContent).toBe("No renderer for typeWithoutRender")
|
|
250
|
+
expect(console.warn).toHaveBeenCalledWith(
|
|
251
|
+
"No render function for type: typeWithoutRender",
|
|
252
|
+
)
|
|
253
|
+
})
|
|
254
|
+
})
|
|
255
|
+
})
|
package/types/mount.d.ts
CHANGED
|
@@ -1,13 +1,38 @@
|
|
|
1
1
|
import type { TemplateResult } from "lit-html"
|
|
2
2
|
import type { Store, Api as StoreApi } from "@inglorious/store"
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* The result of a reactive selector.
|
|
6
|
+
* @template T The type of the selected value.
|
|
7
|
+
*/
|
|
8
|
+
export type ReactiveSelectorResult<T> = {
|
|
9
|
+
/** The current value of the selected state. */
|
|
10
|
+
readonly value: T
|
|
11
|
+
/** A function to stop listening for updates. */
|
|
12
|
+
unsubscribe: () => void
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
export type Api = StoreApi & {
|
|
5
16
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
17
|
+
* Selects a slice of the application state and returns a reactive object.
|
|
18
|
+
* The value will update whenever the selected part of the state changes.
|
|
19
|
+
*
|
|
20
|
+
* @template T The type of the selected state slice.
|
|
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.
|
|
23
|
+
*/
|
|
24
|
+
select: <T>(selectorFn: (api: Api) => T) => ReactiveSelectorResult<T>
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Renders an entity or a type component by its ID.
|
|
28
|
+
* @param id The ID of the entity or type to render.
|
|
29
|
+
* @param options Rendering options.
|
|
30
|
+
* @returns The rendered template or an empty string if not found.
|
|
9
31
|
*/
|
|
10
|
-
render: (
|
|
32
|
+
render: (
|
|
33
|
+
id: string,
|
|
34
|
+
options?: { allowType?: boolean },
|
|
35
|
+
) => TemplateResult | string
|
|
11
36
|
}
|
|
12
37
|
|
|
13
38
|
/**
|