@inglorious/web 4.0.9 → 4.0.11

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
@@ -96,8 +96,15 @@ This framework is ideal for both small apps and large business UIs.
96
96
  ## When NOT to Use Inglorious Web
97
97
 
98
98
  - You're frequently mutating thousands of items without virtualization (though our `list` component handles this elegantly)
99
- - You're building a library that needs to be framework-agnostic
100
- - Your team is already deeply invested in React/Vue/Angular
99
+ - You need framework-agnostic components for users who might not use Inglorious (use Web Components instead)
100
+ - Your team is already deeply invested in React/Vue/Angular and migration costs outweigh benefits
101
+
102
+ ## When Inglorious Web EXCELS
103
+
104
+ - Building **internal component libraries** - Types are more customizable than React components
105
+ - Creating **design systems** - Spread, override, and compose behaviors freely
106
+ - Building **pattern libraries** - Ship pre-configured entity types that users can adapt
107
+ - Apps where **predictable state flow** matters more than ecosystem size
101
108
 
102
109
  ---
103
110
 
@@ -145,14 +152,16 @@ This makes it especially suitable for:
145
152
 
146
153
  ### TL;DR Quick Comparison
147
154
 
148
- | Framework | Reactivity | Compiler | Component State | Bundle Size | Learning Curve |
149
- | -------------- | -------------- | -------- | --------------- | ----------- | -------------- |
150
- | Inglorious Web | Event-based | None | No (store only) | Tiny | Very Low |
151
- | React | VDOM diffing | None | Yes | Large | Medium/High |
152
- | Vue | Proxy-based | Optional | Yes | Medium | Medium |
153
- | Svelte | Compiler magic | Required | Yes | Small | Medium |
154
- | SolidJS | Fine signals | None | No (runs once) | Tiny | Medium/High |
155
- | Qwik | Resumable | Required | Yes | Small | Very High |
155
+ | Framework | Reactivity | Build Transform | Runtime Compiler | Component State | Bundle Size | Learning Curve |
156
+ | -------------- | -------------- | ---------------- | ---------------- | --------------- | ----------- | -------------- |
157
+ | Inglorious Web | Event-based | None required | No | No (store only) | Tiny | Very Low |
158
+ | React | VDOM diffing | JSX + optional\* | No | Yes | Large | Medium/High |
159
+ | Vue | Proxy-based | SFC (optional) | Available | Yes | Medium | Medium |
160
+ | Svelte | Compiler magic | Required | No | Yes | Small | Medium |
161
+ | SolidJS | Fine signals | JSX only | No | No (runs once) | Tiny | Medium/High |
162
+ | Qwik | Resumable | Required | No | Yes | Small | Very High |
163
+
164
+ \*React Compiler (stable 2024+) provides automatic memoization
156
165
 
157
166
  <details>
158
167
  <summary><strong>Click to expand detailed framework comparisons</strong></summary>
@@ -1299,6 +1308,86 @@ See `src/list.js` in the package for the implementation details and the `example
1299
1308
 
1300
1309
  ---
1301
1310
 
1311
+ ## Building Component Libraries with Inglorious Web
1312
+
1313
+ **Inglorious types are uniquely suited for component libraries** because they're fully customizable through JavaScript's spread operator.
1314
+
1315
+ ### The Pattern
1316
+
1317
+ **Library publishes types:**
1318
+
1319
+ ```javascript
1320
+ // @acme/design-system/table.js
1321
+ export const table = {
1322
+ render(entity, api) {
1323
+ const type = api.getType(entity.type)
1324
+
1325
+ return html`
1326
+ <table>
1327
+ ${entity.data.map((row) => type.renderRow(entity, row, api))}
1328
+ </table>
1329
+ `
1330
+ },
1331
+
1332
+ renderRow(entity, row, api) {
1333
+ return html`<tr>
1334
+ ${row.name}
1335
+ </tr>`
1336
+ },
1337
+
1338
+ rowClick(entity, row) {
1339
+ entity.selectedRow = row
1340
+ },
1341
+ }
1342
+ ```
1343
+
1344
+ **Users customize freely:**
1345
+
1346
+ ```javascript
1347
+ import { table } from "@acme/design-system/table"
1348
+
1349
+ // Use as-is
1350
+ const simpleTable = { ...table }
1351
+
1352
+ // Override methods
1353
+ const customTable = {
1354
+ ...table,
1355
+ renderRow(entity, row, api) {
1356
+ return html`<tr class="custom">
1357
+ ${row.name} - ${row.email}
1358
+ </tr>`
1359
+ },
1360
+ }
1361
+
1362
+ // Compose with behaviors
1363
+ import { sortable, exportable } from "@acme/design-system/behaviors"
1364
+
1365
+ const advancedTable = [
1366
+ table,
1367
+ sortable,
1368
+ exportable,
1369
+ {
1370
+ rowClick(entity, row) {
1371
+ console.log("Custom click handler", row)
1372
+ },
1373
+ },
1374
+ ]
1375
+ ```
1376
+
1377
+ ### Why This Is Better Than React Components
1378
+
1379
+ | Feature | React Components | Inglorious Types |
1380
+ | -------------------------- | ------------------------------ | ----------------------------------------- |
1381
+ | Customize rendering | Only if `render` prop exposed | Override `render()` method directly |
1382
+ | Customize behavior | Only if callback props exposed | Override any method |
1383
+ | Compose multiple libraries | Wrapper hell / HOCs | Array composition `[type1, type2, {...}]` |
1384
+ | Access internals | Private - must fork | Public - spread and override |
1385
+ | Type safety | Props interface | Entity schema + method signatures |
1386
+
1387
+ **Users get complete control** without forking your library. They can override rendering, behavior, or both—down to individual methods.
1388
+
1389
+ ---
1390
+
1302
1391
  ## Using Third-Party Web Components
1303
1392
 
1304
1393
  Inglorious Web works seamlessly with any Web Component library, such as Shoelace, Material Web Components, or Lion. Thanks to lit-html's efficient diffing algorithm, Web Components maintain their internal state even when the full tree re-renders - the component only updates when its properties change.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/web",
3
- "version": "4.0.9",
3
+ "version": "4.0.11",
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",
@@ -68,8 +68,8 @@
68
68
  "dependencies": {
69
69
  "@lit-labs/ssr-client": "^1.1.8",
70
70
  "lit-html": "^3.3.1",
71
- "@inglorious/utils": "3.7.2",
72
- "@inglorious/store": "9.0.1"
71
+ "@inglorious/store": "9.0.1",
72
+ "@inglorious/utils": "3.7.2"
73
73
  },
74
74
  "devDependencies": {
75
75
  "prettier": "^3.6.2",
package/src/list/index.js CHANGED
@@ -1,168 +1,7 @@
1
- import { html } from "lit-html"
2
- import { ref } from "lit-html/directives/ref.js"
3
- import { repeat } from "lit-html/directives/repeat.js"
4
- import { styleMap } from "lit-html/directives/style-map.js"
5
-
6
- const LIST_START = 0
7
- const PRETTY_INDEX = 1
8
-
9
- /**
10
- * @typedef {import('../../types/list').ListEntity} ListEntity
11
- * @typedef {import('../../types/mount').Api} Api
12
- * @typedef {import('lit-html').TemplateResult} TemplateResult
13
- */
1
+ import { logic } from "./logic.js"
2
+ import { rendering } from "./rendering.js"
14
3
 
15
4
  export const list = {
16
- /**
17
- * Initializes the list entity with default state.
18
- * @param {ListEntity} entity
19
- */
20
- create(entity) {
21
- resetList(entity)
22
- },
23
-
24
- /**
25
- * Handles the scroll event to update the visible range.
26
- * @param {ListEntity} entity
27
- * @param {HTMLElement} containerEl
28
- */
29
- scroll(entity, containerEl) {
30
- const scrollTop = containerEl.scrollTop
31
- const { items, bufferSize, itemHeight, estimatedHeight, viewportHeight } =
32
- entity
33
- const height = itemHeight || estimatedHeight
34
-
35
- const start = Math.max(
36
- LIST_START,
37
- Math.floor(scrollTop / height) - bufferSize,
38
- )
39
- const visibleCount = Math.ceil(viewportHeight / height)
40
- const end = Math.min(start + visibleCount + bufferSize, items.length)
41
-
42
- if (
43
- entity.visibleRange.start === start &&
44
- entity.visibleRange.end === end
45
- ) {
46
- return
47
- }
48
-
49
- entity.scrollTop = scrollTop
50
- entity.visibleRange = { start, end }
51
- },
52
-
53
- /**
54
- * Mounts the list, measuring the first item to determine item height.
55
- * @param {ListEntity} entity
56
- * @param {HTMLElement} containerEl
57
- */
58
- mount(entity, containerEl) {
59
- const firstItem = containerEl.querySelector("[data-index]")
60
- if (!firstItem) return
61
-
62
- entity.itemHeight = firstItem.offsetHeight
63
- entity.visibleRange = {
64
- start: 0,
65
- end: Math.ceil(entity.viewportHeight / entity.itemHeight),
66
- }
67
- },
68
-
69
- /**
70
- * Renders the virtualized list component.
71
- * @param {ListEntity} entity
72
- * @param {Api} api
73
- * @returns {TemplateResult}
74
- */
75
- render(entity, api) {
76
- const { items, visibleRange, viewportHeight, itemHeight, estimatedHeight } =
77
- entity
78
- const type = api.getType(entity.type)
79
-
80
- if (!items) {
81
- console.warn(`list entity ${entity.id} needs 'items'`)
82
- return html``
83
- }
84
-
85
- if (!type.renderItem) {
86
- console.warn(`type ${entity.type} needs 'renderItem' method`)
87
- return html``
88
- }
89
-
90
- const visibleItems = items.slice(visibleRange.start, visibleRange.end)
91
- const height = itemHeight || estimatedHeight
92
- const totalHeight = items.length * height
93
-
94
- return html`
95
- <div
96
- style=${styleMap({ height: `${viewportHeight}px`, overflow: "auto" })}
97
- @scroll=${(e) => api.notify(`#${entity.id}:scroll`, e.target)}
98
- ${ref((el) => {
99
- if (el && !itemHeight) {
100
- queueMicrotask(() => {
101
- api.notify(`#${entity.id}:mount`, el)
102
- })
103
- }
104
- })}
105
- >
106
- <div
107
- style=${styleMap({
108
- height: `${totalHeight}px`,
109
- position: "relative",
110
- })}
111
- >
112
- ${repeat(
113
- visibleItems,
114
- (item) => item.id,
115
- (item, index) => {
116
- const absoluteIndex = visibleRange.start + index
117
- const top = absoluteIndex * height
118
-
119
- return html`
120
- <div
121
- style=${styleMap({
122
- position: "absolute",
123
- top: `${top}px`,
124
- width: "100%",
125
- })}
126
- data-index=${absoluteIndex}
127
- >
128
- ${type.renderItem(
129
- entity,
130
- { item, index: absoluteIndex },
131
- api,
132
- )}
133
- </div>
134
- `
135
- },
136
- )}
137
- </div>
138
- </div>
139
- `
140
- },
141
-
142
- /**
143
- * Default item renderer.
144
- * @param {any} item
145
- * @param {number} index
146
- * @param {Api} api
147
- * @returns {TemplateResult}
148
- */
149
- // eslint-disable-next-line no-unused-vars
150
- renderItem(entity, { item, index }, api) {
151
- return html`<div class="iw-list-item">
152
- ${index + PRETTY_INDEX}. ${JSON.stringify(item)}
153
- </div>`
154
- },
155
- }
156
-
157
- /**
158
- * Resets the list entity state.
159
- * @param {ListEntity} entity
160
- */
161
- function resetList(entity) {
162
- entity.scrollTop = 0
163
- entity.visibleRange ??= { start: 0, end: 20 }
164
- entity.viewportHeight ??= 600
165
- entity.bufferSize ??= 5
166
- entity.itemHeight ??= null
167
- entity.estimatedHeight ??= 50
5
+ ...logic,
6
+ ...rendering,
168
7
  }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * @typedef {import('../../types/list').ListEntity} ListEntity
3
+ * @typedef {import('../../types/mount').Api} Api
4
+ * @typedef {import('lit-html').TemplateResult} TemplateResult
5
+ */
6
+
7
+ const LIST_START = 0
8
+
9
+ export const logic = {
10
+ /**
11
+ * Initializes the list entity with default state.
12
+ * @param {ListEntity} entity
13
+ */
14
+ create(entity) {
15
+ resetList(entity)
16
+ },
17
+
18
+ /**
19
+ * Handles the scroll event to update the visible range.
20
+ * @param {ListEntity} entity
21
+ * @param {HTMLElement} containerEl
22
+ */
23
+ scroll(entity, containerEl) {
24
+ const scrollTop = containerEl.scrollTop
25
+ const { items, bufferSize, itemHeight, estimatedHeight, viewportHeight } =
26
+ entity
27
+ const height = itemHeight || estimatedHeight
28
+
29
+ const start = Math.max(
30
+ LIST_START,
31
+ Math.floor(scrollTop / height) - bufferSize,
32
+ )
33
+ const visibleCount = Math.ceil(viewportHeight / height)
34
+ const end = Math.min(start + visibleCount + bufferSize, items.length)
35
+
36
+ if (
37
+ entity.visibleRange.start === start &&
38
+ entity.visibleRange.end === end
39
+ ) {
40
+ return
41
+ }
42
+
43
+ entity.scrollTop = scrollTop
44
+ entity.visibleRange = { start, end }
45
+ },
46
+
47
+ /**
48
+ * Mounts the list, measuring the first item to determine item height.
49
+ * @param {ListEntity} entity
50
+ * @param {HTMLElement} containerEl
51
+ */
52
+ mount(entity, containerEl) {
53
+ const firstItem = containerEl.querySelector("[data-index]")
54
+ if (!firstItem) return
55
+
56
+ entity.itemHeight = firstItem.offsetHeight
57
+ entity.visibleRange = {
58
+ start: 0,
59
+ end: Math.ceil(entity.viewportHeight / entity.itemHeight),
60
+ }
61
+ },
62
+ }
63
+
64
+ /**
65
+ * Resets the list entity state.
66
+ * @param {ListEntity} entity
67
+ */
68
+ function resetList(entity) {
69
+ entity.scrollTop = 0
70
+ entity.visibleRange ??= { start: 0, end: 20 }
71
+ entity.viewportHeight ??= 600
72
+ entity.bufferSize ??= 5
73
+ entity.itemHeight ??= null
74
+ entity.estimatedHeight ??= 50
75
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @typedef {import('../../types/mount').Api} Api
3
+ * @typedef {import('../../types/list').ListEntity} ListEntity
4
+ * @typedef {import('lit-html').TemplateResult} TemplateResult
5
+ */
6
+
7
+ import { html } from "lit-html"
8
+ import { ref } from "lit-html/directives/ref.js"
9
+ import { repeat } from "lit-html/directives/repeat.js"
10
+ import { styleMap } from "lit-html/directives/style-map.js"
11
+
12
+ const PRETTY_INDEX = 1
13
+ /**
14
+ * Rendering logic for the virtualized list component.
15
+ */
16
+ export const rendering = {
17
+ /**
18
+ * Renders the virtualized list component.
19
+ * @param {ListEntity} entity The list entity state.
20
+ * @param {Api} api The API object.
21
+ * @returns {TemplateResult} The rendered list.
22
+ */
23
+ render(entity, api) {
24
+ const { items, visibleRange, viewportHeight, itemHeight, estimatedHeight } =
25
+ entity
26
+ const type = api.getType(entity.type)
27
+
28
+ if (!items) {
29
+ console.warn(`list entity ${entity.id} needs 'items'`)
30
+ return html``
31
+ }
32
+
33
+ if (!type.renderItem) {
34
+ console.warn(`type ${entity.type} needs 'renderItem' method`)
35
+ return html``
36
+ }
37
+
38
+ const visibleItems = items.slice(visibleRange.start, visibleRange.end)
39
+ const height = itemHeight || estimatedHeight
40
+ const totalHeight = items.length * height
41
+
42
+ return html`
43
+ <div
44
+ style=${styleMap({ height: `${viewportHeight}px`, overflow: "auto" })}
45
+ @scroll=${(e) => api.notify(`#${entity.id}:scroll`, e.target)}
46
+ ${ref((el) => {
47
+ if (el && !itemHeight) {
48
+ queueMicrotask(() => {
49
+ api.notify(`#${entity.id}:mount`, el)
50
+ })
51
+ }
52
+ })}
53
+ >
54
+ <div
55
+ style=${styleMap({
56
+ height: `${totalHeight}px`,
57
+ position: "relative",
58
+ })}
59
+ >
60
+ ${repeat(
61
+ visibleItems,
62
+ (item) => item.id,
63
+ (item, index) => {
64
+ const absoluteIndex = visibleRange.start + index
65
+ const top = absoluteIndex * height
66
+
67
+ return html`
68
+ <div
69
+ style=${styleMap({
70
+ position: "absolute",
71
+ top: `${top}px`,
72
+ width: "100%",
73
+ })}
74
+ data-index=${absoluteIndex}
75
+ >
76
+ ${type.renderItem(
77
+ entity,
78
+ { item, index: absoluteIndex },
79
+ api,
80
+ )}
81
+ </div>
82
+ `
83
+ },
84
+ )}
85
+ </div>
86
+ </div>
87
+ `
88
+ },
89
+
90
+ /**
91
+ * Default item renderer.
92
+ * @param {ListEntity} entity The list entity.
93
+ * @param {object} payload The payload for rendering the item.
94
+ * @param {any} payload.item The item data.
95
+ * @param {number} payload.index The item's absolute index in the full list.
96
+ * @param {Api} api The API object.
97
+ * @returns {TemplateResult} The rendered list item.
98
+ */
99
+ // eslint-disable-next-line no-unused-vars
100
+ renderItem(entity, { item, index }, api) {
101
+ return html`<div class="iw-list-item">
102
+ ${index + PRETTY_INDEX}. ${JSON.stringify(item)}
103
+ </div>`
104
+ },
105
+ }
@@ -1,3 +1,11 @@
1
+ /**
2
+ * @typedef {import('../../types/api').API} API
3
+ * @typedef {import('../../types/table').TableColumn} TableColumn
4
+ * @typedef {import('../../types/table').TableEntity} TableEntity
5
+ * @typedef {import('../../types/table').TableRow} TableRow
6
+ * @typedef {import('lit-html').TemplateResult} TemplateResult
7
+ */
8
+
1
9
  import { html } from "lit-html"
2
10
  import { classMap } from "lit-html/directives/class-map.js"
3
11
  import { ref } from "lit-html/directives/ref.js"
@@ -12,14 +20,28 @@ const LAST_PAGE = 1
12
20
  const PRETTY_PAGE = 1
13
21
  const PERCENTAGE_TO_FLEX = 0.01
14
22
 
23
+ /**
24
+ * Rendering logic for the table component.
25
+ */
15
26
  export const rendering = {
27
+ /**
28
+ * Measures and sets initial column widths.
29
+ * This is called after the initial render to capture the "auto" widths of columns.
30
+ * @param {TableEntity} entity The table entity.
31
+ * @param {HTMLElement} containerEl The header row element containing the columns.
32
+ */
16
33
  mount(entity, containerEl) {
17
34
  const columns = containerEl.querySelectorAll(":scope > *")
18
35
  ;[...columns].forEach((column, index) => {
19
36
  entity.columns[index].width = column.offsetWidth
20
37
  })
21
38
  },
22
-
39
+ /**
40
+ * Renders the main table component.
41
+ * @param {TableEntity} entity The table entity.
42
+ * @param {API} api The API object.
43
+ * @returns {TemplateResult} The rendered table.
44
+ */
23
45
  render(entity, api) {
24
46
  const type = api.getType(entity.type)
25
47
 
@@ -28,7 +50,12 @@ export const rendering = {
28
50
  ${type.renderFooter(entity, api)}
29
51
  </div> `
30
52
  },
31
-
53
+ /**
54
+ * Renders the table header.
55
+ * @param {TableEntity} entity The table entity.
56
+ * @param {API} api The API object.
57
+ * @returns {TemplateResult} The rendered header.
58
+ */
32
59
  renderHeader(entity, api) {
33
60
  const type = api.getType(entity.type)
34
61
 
@@ -57,7 +84,14 @@ export const rendering = {
57
84
  ${entity.search && type.renderSearchbar(entity, api)}
58
85
  </div>`
59
86
  },
60
-
87
+ /**
88
+ * Renders a single column in the header.
89
+ * @param {TableEntity} entity The table entity.
90
+ * @param {object} payload The payload.
91
+ * @param {TableColumn} payload.column The column definition.
92
+ * @param {API} api The API object.
93
+ * @returns {TemplateResult} The rendered header column.
94
+ */
61
95
  renderHeaderColumn(entity, { column }, api) {
62
96
  return html`<div
63
97
  class="iw-table-header-column"
@@ -75,7 +109,12 @@ export const rendering = {
75
109
  ${column.isFilterable && filters.render(entity, column, api)}
76
110
  </div>`
77
111
  },
78
-
112
+ /**
113
+ * Renders the search bar.
114
+ * @param {TableEntity} entity The table entity.
115
+ * @param {API} api The API object.
116
+ * @returns {TemplateResult} The rendered search bar.
117
+ */
79
118
  renderSearchbar(entity, api) {
80
119
  return html`<input
81
120
  name="search"
@@ -87,7 +126,12 @@ export const rendering = {
87
126
  class="iw-table-searchbar"
88
127
  />`
89
128
  },
90
-
129
+ /**
130
+ * Renders the table body with rows.
131
+ * @param {TableEntity} entity The table entity.
132
+ * @param {API} api The API object.
133
+ * @returns {TemplateResult} The rendered table body.
134
+ */
91
135
  renderBody(entity, api) {
92
136
  const type = api.getType(entity.type)
93
137
 
@@ -99,7 +143,15 @@ export const rendering = {
99
143
  )}
100
144
  </div>`
101
145
  },
102
-
146
+ /**
147
+ * Renders a single row in the table body.
148
+ * @param {TableEntity} entity The table entity.
149
+ * @param {object} payload The payload.
150
+ * @param {TableRow} payload.row The row data.
151
+ * @param {number} payload.index The row index.
152
+ * @param {API} api The API object.
153
+ * @returns {TemplateResult} The rendered row.
154
+ */
103
155
  renderRow(entity, { row, index }, api) {
104
156
  const type = api.getType(entity.type)
105
157
  const rowId = row[entity.rowId ?? "id"]
@@ -119,7 +171,15 @@ export const rendering = {
119
171
  )}
120
172
  </div>`
121
173
  },
122
-
174
+ /**
175
+ * Renders a single cell within a row.
176
+ * @param {TableEntity} entity The table entity.
177
+ * @param {object} payload The payload.
178
+ * @param {any} payload.cell The cell data.
179
+ * @param {number} payload.index The column index.
180
+ * @param {API} api The API object.
181
+ * @returns {TemplateResult} The rendered cell.
182
+ */
123
183
  renderCell(entity, { cell, index }, api) {
124
184
  const type = api.getType(entity.type)
125
185
  const column = entity.columns[index]
@@ -136,10 +196,22 @@ export const rendering = {
136
196
  </div>`
137
197
  },
138
198
 
139
- renderValue(_, { value }) {
199
+ /**
200
+ * Renders the value within a cell. This can be overridden for custom formatting.
201
+ * @param {TableEntity} _entity The table entity (ignored).
202
+ * @param {object} payload The payload.
203
+ * @param {any} payload.value The value to render.
204
+ * @returns {any} The value to be rendered.
205
+ */
206
+ renderValue(_entity, { value }) {
140
207
  return value
141
208
  },
142
-
209
+ /**
210
+ * Renders the table footer.
211
+ * @param {TableEntity} entity The table entity.
212
+ * @param {API} api The API object.
213
+ * @returns {TemplateResult} The rendered footer.
214
+ */
143
215
  renderFooter(entity, api) {
144
216
  const type = api.getType(entity.type)
145
217
  const pagination = getPaginationInfo(entity)
@@ -172,7 +244,13 @@ export const rendering = {
172
244
  </div>
173
245
  </div>`
174
246
  },
175
-
247
+ /**
248
+ * Renders the pagination controls.
249
+ * @param {TableEntity} entity The table entity.
250
+ * @param {object} pagination The pagination info object from `getPaginationInfo`.
251
+ * @param {API} api The API object.
252
+ * @returns {TemplateResult} The rendered pagination controls.
253
+ */
176
254
  renderPagination(entity, pagination, api) {
177
255
  return html`<div class="iw-table-row">
178
256
  <button
@@ -226,6 +304,11 @@ export const rendering = {
226
304
  },
227
305
  }
228
306
 
307
+ /**
308
+ * Generates the style string for a column.
309
+ * @param {TableColumn} column The column definition.
310
+ * @returns {string} The style string.
311
+ */
229
312
  function getColumnStyle(column) {
230
313
  if (typeof column.width === "string") {
231
314
  if (column.width?.endsWith("%")) {
@@ -240,6 +323,11 @@ function getColumnStyle(column) {
240
323
  return `width: ${column.width}px`
241
324
  }
242
325
 
326
+ /**
327
+ * Gets the appropriate sort icon for a given direction.
328
+ * @param {"asc"|"desc"|null} direction The sort direction.
329
+ * @returns {string} The sort icon.
330
+ */
243
331
  function getSortIcon(direction) {
244
332
  switch (direction) {
245
333
  case "asc":
package/types/list.d.ts CHANGED
@@ -2,58 +2,66 @@ import type { TemplateResult } from "lit-html"
2
2
  import type { Api } from "./mount"
3
3
 
4
4
  /**
5
- * Represents the visible range of items in the list.
5
+ * Represents a single item in the list's data array.
6
+ * It's a generic record but should ideally have a unique `id`.
6
7
  */
7
- export interface VisibleRange {
8
- start: number
9
- end: number
10
- }
8
+ export type ListItem = Record<string, any> & { id: string | number }
11
9
 
12
10
  /**
13
- * Represents the state of a list entity.
11
+ * Represents the state of a virtualized list entity.
14
12
  */
15
- export interface ListEntity<T = any> {
13
+ export interface ListEntity<T extends ListItem = ListItem> {
16
14
  /** A unique identifier for the list entity. */
17
15
  id: string | number
18
- /** The entity type (usually 'list'). */
16
+
17
+ /** The entity type, used to look up rendering logic. */
19
18
  type: string
20
- /** The array of items to render. */
19
+
20
+ /** The full array of items for the list. */
21
21
  items: T[]
22
- /** The current scroll position of the list container. */
22
+
23
+ /** The current scroll position from the top in pixels. */
23
24
  scrollTop: number
24
- /** The range of items currently visible (plus buffer). */
25
- visibleRange: VisibleRange
26
- /** The height of the viewport in pixels. */
25
+
26
+ /** The start and end indices of the currently visible items (including buffer). */
27
+ visibleRange: { start: number; end: number }
28
+
29
+ /** The height of the visible viewport in pixels. */
27
30
  viewportHeight: number
28
- /** The number of extra items to render above and below the visible range. */
31
+
32
+ /** The number of items to render outside the viewport (above and below). */
29
33
  bufferSize: number
30
- /** The fixed height of each item in pixels, or null if measuring. */
34
+
35
+ /** The measured height of a single item in pixels. Null until measured on mount. */
31
36
  itemHeight: number | null
32
- /** The estimated height of an item, used before measurement. */
37
+
38
+ /** An estimated height for items used for calculations before they are measured. */
33
39
  estimatedHeight: number
40
+
34
41
  /** Any other custom properties. */
35
42
  [key: string]: any
36
43
  }
37
44
 
38
45
  /**
39
- * The list type implementation.
46
+ * The list type implementation, combining logic and rendering.
40
47
  */
41
48
  export declare const list: {
42
49
  /**
43
50
  * Initializes the list entity with default state.
44
- * @param entity The list entity.
51
+ * @param entity The list entity to initialize.
45
52
  */
46
53
  create(entity: ListEntity): void
47
54
 
48
55
  /**
49
- * Handles the scroll event to update the visible range.
56
+ * Handles the scroll event to update the visible range of items.
50
57
  * @param entity The list entity.
51
58
  * @param containerEl The scrolling container element.
52
59
  */
53
60
  scroll(entity: ListEntity, containerEl: HTMLElement): void
54
61
 
55
62
  /**
56
- * Mounts the list, measuring the first item to determine item height.
63
+ * Mounts the list, measuring the first item to determine the `itemHeight`
64
+ * for accurate virtualization calculations.
57
65
  * @param entity The list entity.
58
66
  * @param containerEl The scrolling container element.
59
67
  */
@@ -67,10 +75,17 @@ export declare const list: {
67
75
  render(entity: ListEntity, api: Api): TemplateResult
68
76
 
69
77
  /**
70
- * Default item renderer.
71
- * @param item The item to render.
72
- * @param index The index of the item.
78
+ * Renders a single item in the list. This is a default implementation
79
+ * and is expected to be overridden by a specific list type.
80
+ * @param entity The list entity.
81
+ * @param payload The payload for rendering the item.
82
+ * @param payload.item The item data.
83
+ * @param payload.index The item's absolute index in the full list.
73
84
  * @param api The store API.
74
85
  */
75
- renderItem(item: any, index: number, api: Api): TemplateResult
86
+ renderItem(
87
+ entity: ListEntity,
88
+ payload: { item: ListItem; index: number },
89
+ api: Api,
90
+ ): TemplateResult
76
91
  }
package/types/table.d.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import type { TemplateResult } from "lit-html"
2
2
  import type { Api } from "./mount"
3
+ import type { SelectOption } from "./select"
4
+
5
+ /**
6
+ * Represents a single row of data in the table.
7
+ */
8
+ export type TableRow = Record<string, any>
3
9
 
4
10
  /**
5
11
  * Represents a column definition for the table.
@@ -18,17 +24,17 @@ export interface TableColumn {
18
24
  /** Filter configuration. */
19
25
  filter?: {
20
26
  type: "text" | "number" | "range" | "select" | "date" | "time" | "datetime"
21
- options?: any[]
27
+ options?: SelectOption[]
22
28
  [key: string]: any
23
29
  }
24
- /** The width of the column. */
25
- width?: number
30
+ /** The width of the column, in pixels or as a string (e.g., '10%'). */
31
+ width?: number | string
26
32
  /** Optional formatter key or function. */
27
33
  formatter?: string | ((value: any) => any)
28
- /** Custom sort function. */
29
- sortFn?: (a: any, b: any) => number
30
- /** Custom filter function. */
31
- filterFn?: (row: any, filterValue: any) => boolean
34
+ /** Custom sort function for a column, operating on two rows. */
35
+ sortFn?: (a: TableRow, b: TableRow) => number
36
+ /** Custom filter function for a column, operating on a single row. */
37
+ filterFn?: (row: TableRow, filterValue: any) => boolean
32
38
  /** Custom format function. */
33
39
  format?: (value: any) => string
34
40
  /** Any other custom properties. */
@@ -38,7 +44,7 @@ export interface TableColumn {
38
44
  /**
39
45
  * Represents the state of a table entity.
40
46
  */
41
- export interface TableEntity<T = any> {
47
+ export interface TableEntity<T extends TableRow = TableRow> {
42
48
  /** A unique identifier for the table entity. */
43
49
  id: string | number
44
50
  /** The entity type. */
@@ -52,11 +58,13 @@ export interface TableEntity<T = any> {
52
58
  /** Filtering state. */
53
59
  filters: Record<string, any>
54
60
  /** Search state. */
55
- search: { value: string } | null
61
+ search: { value: string; placeholder?: string } | null
56
62
  /** Selected row IDs. */
57
63
  selection: (string | number)[]
58
64
  /** Pagination state. */
59
65
  pagination: { page: number; pageSize: number } | null
66
+ /** The property on a row object that uniquely identifies it. Defaults to 'id'. */
67
+ rowId?: string
60
68
  /** Whether multi-select is enabled. */
61
69
  isMultiSelect: boolean
62
70
  /** Any other custom properties. */
@@ -198,35 +206,43 @@ export declare const table: {
198
206
  /**
199
207
  * Renders a single row.
200
208
  * @param entity The table entity.
201
- * @param item The data item for the row.
202
- * @param index The index of the row.
209
+ * @param payload The payload for rendering the row.
210
+ * @param payload.row The data item for the row.
211
+ * @param payload.index The index of the row.
203
212
  * @param api The store API.
204
213
  */
205
214
  renderRow(
206
215
  entity: TableEntity,
207
- item: any,
208
- index: number,
216
+ payload: { row: TableRow; index: number },
209
217
  api: Api,
210
218
  ): TemplateResult
211
219
 
212
220
  /**
213
221
  * Renders a single cell.
214
222
  * @param entity The table entity.
215
- * @param item The data item for the row.
216
- * @param column The column definition.
223
+ * @param payload The payload for rendering the cell.
224
+ * @param payload.cell The data for the cell.
225
+ * @param payload.index The column index of the cell.
217
226
  * @param api The store API.
218
227
  */
219
228
  renderCell(
220
229
  entity: TableEntity,
221
- item: any,
222
- column: TableColumn,
230
+ payload: { cell: any; index: number },
223
231
  api: Api,
224
232
  ): TemplateResult
225
233
 
226
234
  /**
227
235
  * Renders the value of a cell.
228
- * @param value The raw value from the data item.
229
- * @param column The column definition.
236
+ * @param entity The table entity.
237
+ * @param payload The payload for rendering the value.
238
+ * @param payload.value The raw value from the data item.
239
+ * @param payload.column The column definition.
240
+ * @param payload.index The column index.
241
+ * @param api The store API.
230
242
  */
231
- renderValue(value: any, column: TableColumn): any
243
+ renderValue(
244
+ entity: TableEntity,
245
+ payload: { value: any; column: TableColumn; index: number },
246
+ api: Api,
247
+ ): any
232
248
  }