@inglorious/web 4.3.0 → 4.5.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.
@@ -1,340 +0,0 @@
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
-
9
- import { html } from "lit-html"
10
- import { classMap } from "lit-html/directives/class-map.js"
11
- import { ref } from "lit-html/directives/ref.js"
12
- import { repeat } from "lit-html/directives/repeat.js"
13
-
14
- import { filters } from "./filters.js"
15
- import { getPaginationInfo, getRows, getSortDirection } from "./logic.js"
16
-
17
- const DIVISOR = 2
18
- const FIRST_PAGE = 0
19
- const LAST_PAGE = 1
20
- const PRETTY_PAGE = 1
21
- const PERCENTAGE_TO_FLEX = 0.01
22
-
23
- /**
24
- * Rendering logic for the table component.
25
- */
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
- */
33
- mount(entity, containerEl) {
34
- const columns = containerEl.querySelectorAll(":scope > *")
35
- ;[...columns].forEach((column, index) => {
36
- entity.columns[index].width = column.offsetWidth
37
- })
38
- },
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
- */
45
- render(entity, api) {
46
- const type = api.getType(entity.type)
47
-
48
- return html`<div class="iw-table">
49
- ${type.renderHeader(entity, api)} ${type.renderBody(entity, api)}
50
- ${type.renderFooter(entity, api)}
51
- </div> `
52
- },
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
- */
59
- renderHeader(entity, api) {
60
- const type = api.getType(entity.type)
61
-
62
- return html`<div class="iw-table-header">
63
- <div
64
- class="iw-table-header-row"
65
- ${ref((el) => {
66
- if (
67
- el &&
68
- entity.columns.some(({ width }) => typeof width === "string")
69
- ) {
70
- queueMicrotask(() => {
71
- api.notify(`#${entity.id}:mount`, el)
72
- })
73
- }
74
- })}
75
- >
76
- ${repeat(
77
- entity.columns,
78
- (column) => column.id,
79
- (column, index) =>
80
- type.renderHeaderColumn(entity, { column, index }, api),
81
- )}
82
- </div>
83
-
84
- ${entity.search && type.renderSearchbar(entity, api)}
85
- </div>`
86
- },
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
- */
95
- renderHeaderColumn(entity, { column }, api) {
96
- return html`<div
97
- class="iw-table-header-column"
98
- style=${getColumnStyle(column)}
99
- >
100
- <div
101
- @click=${() =>
102
- column.isSortable &&
103
- api.notify(`#${entity.id}:sortChange`, column.id)}
104
- class="iw-table-header-title"
105
- >
106
- ${column.title} ${getSortIcon(getSortDirection(entity, column.id))}
107
- </div>
108
-
109
- ${column.isFilterable && filters.render(entity, column, api)}
110
- </div>`
111
- },
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
- */
118
- renderSearchbar(entity, api) {
119
- return html`<input
120
- name="search"
121
- type="text"
122
- placeholder=${entity.search.placeholder ?? "Fuzzy search..."}
123
- value=${entity.search.value}
124
- @input=${(event) =>
125
- api.notify(`#${entity.id}:searchChange`, event.target.value)}
126
- class="iw-table-searchbar"
127
- />`
128
- },
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
- */
135
- renderBody(entity, api) {
136
- const type = api.getType(entity.type)
137
-
138
- return html`<div class="iw-table-body">
139
- ${repeat(
140
- getRows(entity),
141
- (row) => row.id,
142
- (row, index) => type.renderRow(entity, { row, index }, api),
143
- )}
144
- </div>`
145
- },
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
- */
155
- renderRow(entity, { row, index }, api) {
156
- const type = api.getType(entity.type)
157
- const rowId = row[entity.rowId ?? "id"]
158
-
159
- return html`<div
160
- @click=${() => api.notify(`#${entity.id}:rowToggle`, rowId)}
161
- class="iw-table-row ${classMap({
162
- "iw-table-row-even": index % DIVISOR,
163
- "iw-table-row-selected": entity.selection?.includes(rowId),
164
- })}"
165
- >
166
- ${repeat(
167
- entity.columns,
168
- (column) => column.id,
169
- (column, index) =>
170
- type.renderCell(entity, { cell: row[column.id], index }, api),
171
- )}
172
- </div>`
173
- },
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
- */
183
- renderCell(entity, { cell, index }, api) {
184
- const type = api.getType(entity.type)
185
- const column = entity.columns[index]
186
-
187
- return html`<div
188
- class="iw-table-cell ${classMap({
189
- "iw-table-cell-number": column.type === "number",
190
- "iw-table-cell-date": column.type === "date",
191
- "iw-table-cell-boolean": column.type === "boolean",
192
- })}"
193
- style=${getColumnStyle(column)}
194
- >
195
- ${type.renderValue(entity, { value: cell, column, index }, api)}
196
- </div>`
197
- },
198
-
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 }) {
207
- return value
208
- },
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
- */
215
- renderFooter(entity, api) {
216
- const type = api.getType(entity.type)
217
- const pagination = getPaginationInfo(entity)
218
-
219
- return html`<div class="iw-table-footer">
220
- <div class="iw-table-footer-row">
221
- <div>
222
- ${pagination.start + PRETTY_PAGE} to ${pagination.end} of ${pagination.totalRows}
223
- entries
224
- </div>
225
-
226
- ${type.renderPagination(entity, pagination, api)}
227
-
228
- <div class="iw-table-footer-row">
229
- <div>Page size:</div>
230
- <select
231
- name="pageSize"
232
- @change=${(event) =>
233
- api.notify(
234
- `#${entity.id}:pageSizeChange`,
235
- Number(event.target.value),
236
- )}
237
- >
238
- <option>10</option>
239
- <option>20</option>
240
- <option>30</option>
241
- </select>
242
- </div>
243
- </div>
244
- </div>
245
- </div>`
246
- },
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
- */
254
- renderPagination(entity, pagination, api) {
255
- return html`<div class="iw-table-row">
256
- <button
257
- ?disabled=${!pagination.hasPrevPage}
258
- @click=${() => api.notify(`#${entity.id}:pageChange`, FIRST_PAGE)}
259
- class="iw-table-pagination-button"
260
- >
261
- |&#10094;
262
- </button>
263
- <button
264
- ?disabled=${!pagination.hasPrevPage}
265
- @click=${() => api.notify(`#${entity.id}:pagePrev`)}
266
- class="iw-table-pagination-button"
267
- >
268
- &#10094;
269
- </button>
270
- <input
271
- name="page"
272
- type="number"
273
- min="1"
274
- max=${pagination.totalPages}
275
- value=${pagination.page + PRETTY_PAGE}
276
- class="iw-table-page-input"
277
- @input=${(event) =>
278
- api.notify(
279
- `#${entity.id}:pageChange`,
280
- Number(event.target.value) - PRETTY_PAGE,
281
- )}
282
- />
283
- /
284
- <span>${pagination.totalPages}</span>
285
- <button
286
- ?disabled="${!pagination.hasNextPage}"
287
- @click=${() => api.notify(`#${entity.id}:pageNext`)}
288
- class="iw-table-pagination-button"
289
- >
290
- &#10095;
291
- </button>
292
- <button
293
- ?disabled=${!pagination.hasNextPage}
294
- @click=${() =>
295
- api.notify(
296
- `#${entity.id}:pageChange`,
297
- pagination.totalPages - LAST_PAGE,
298
- )}
299
- class="iw-table-pagination-button"
300
- >
301
- &#10095;|
302
- </button>
303
- </div>`
304
- },
305
- }
306
-
307
- /**
308
- * Generates the style string for a column.
309
- * @param {TableColumn} column The column definition.
310
- * @returns {string} The style string.
311
- */
312
- function getColumnStyle(column) {
313
- if (typeof column.width === "string") {
314
- if (column.width?.endsWith("%")) {
315
- // eslint-disable-next-line no-magic-numbers
316
- const percentage = Number(column.width.slice(0, -1))
317
- return `flex: ${percentage * PERCENTAGE_TO_FLEX}`
318
- }
319
-
320
- return `width: ${column.width}`
321
- }
322
-
323
- return `width: ${column.width}px`
324
- }
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
- */
331
- function getSortIcon(direction) {
332
- switch (direction) {
333
- case "asc":
334
- return "▲"
335
- case "desc":
336
- return "▼"
337
- default:
338
- return "▲▼"
339
- }
340
- }
@@ -1,83 +0,0 @@
1
- .iw-table {
2
- .iw-table-header {
3
- border-bottom: 1px solid grey;
4
- padding: 1em 0;
5
- row-gap: 1em;
6
- }
7
-
8
- .iw-table-header-row {
9
- align-items: flex-start;
10
- column-gap: 1em;
11
- }
12
-
13
- .iw-table-header-column {
14
- row-gap: 0.5em;
15
- }
16
-
17
- .iw-table-header-title {
18
- font-weight: bold;
19
- white-space: nowrap;
20
- }
21
-
22
- .iw-table-body {
23
- max-height: 35em;
24
- overflow: auto;
25
- border-bottom: 1px solid grey;
26
- }
27
-
28
- .iw-table-row {
29
- align-items: center;
30
- column-gap: 1em;
31
- }
32
-
33
- .iw-table-row-even {
34
- background-color: aliceblue;
35
- }
36
-
37
- .iw-table-row-selected {
38
- border-left: 1px solid cornflowerblue;
39
- border-right: 1px solid cornflowerblue;
40
- }
41
-
42
- :not(.iw-table-row-selected) + .iw-table-row-selected {
43
- border-top: 1px solid cornflowerblue;
44
- margin-top: -1px;
45
- }
46
-
47
- .iw-table-row-selected + :not(.iw-table-row-selected) {
48
- border-top: 1px solid cornflowerblue;
49
- margin-top: -1px;
50
- }
51
-
52
- .iw-table-cell {
53
- padding: 1em;
54
- }
55
-
56
- .iw-table-cell-number,
57
- .iw-table-cell-date {
58
- text-align: right;
59
- }
60
-
61
- .iw-table-cell-boolean {
62
- text-align: center;
63
- }
64
-
65
- .iw-table-footer {
66
- padding: 1em 0;
67
- }
68
-
69
- .iw-table-footer-row {
70
- justify-content: space-between;
71
- align-items: center;
72
- column-gap: 1em;
73
- }
74
-
75
- .iw-table-page-input {
76
- min-width: 4em;
77
- text-align: right;
78
- }
79
-
80
- .iw-table-pagination-button {
81
- white-space: nowrap;
82
- }
83
- }
package/types/list.d.ts DELETED
@@ -1,91 +0,0 @@
1
- import type { TemplateResult } from "lit-html"
2
- import type { Api } from "./mount"
3
-
4
- /**
5
- * Represents a single item in the list's data array.
6
- * It's a generic record but should ideally have a unique `id`.
7
- */
8
- export type ListItem = Record<string, any> & { id: string | number }
9
-
10
- /**
11
- * Represents the state of a virtualized list entity.
12
- */
13
- export interface ListEntity<T extends ListItem = ListItem> {
14
- /** A unique identifier for the list entity. */
15
- id: string | number
16
-
17
- /** The entity type, used to look up rendering logic. */
18
- type: string
19
-
20
- /** The full array of items for the list. */
21
- items: T[]
22
-
23
- /** The current scroll position from the top in pixels. */
24
- scrollTop: number
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. */
30
- viewportHeight: number
31
-
32
- /** The number of items to render outside the viewport (above and below). */
33
- bufferSize: number
34
-
35
- /** The measured height of a single item in pixels. Null until measured on mount. */
36
- itemHeight: number | null
37
-
38
- /** An estimated height for items used for calculations before they are measured. */
39
- estimatedHeight: number
40
-
41
- /** Any other custom properties. */
42
- [key: string]: any
43
- }
44
-
45
- /**
46
- * The list type implementation, combining logic and rendering.
47
- */
48
- export declare const list: {
49
- /**
50
- * Initializes the list entity with default state.
51
- * @param entity The list entity to initialize.
52
- */
53
- create(entity: ListEntity): void
54
-
55
- /**
56
- * Handles the scroll event to update the visible range of items.
57
- * @param entity The list entity.
58
- * @param containerEl The scrolling container element.
59
- */
60
- scroll(entity: ListEntity, containerEl: HTMLElement): void
61
-
62
- /**
63
- * Mounts the list, measuring the first item to determine the `itemHeight`
64
- * for accurate virtualization calculations.
65
- * @param entity The list entity.
66
- * @param containerEl The scrolling container element.
67
- */
68
- mount(entity: ListEntity, containerEl: HTMLElement): void
69
-
70
- /**
71
- * Renders the virtualized list component.
72
- * @param entity The list entity.
73
- * @param api The store API.
74
- */
75
- render(entity: ListEntity, api: Api): TemplateResult
76
-
77
- /**
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.
84
- * @param api The store API.
85
- */
86
- renderItem(
87
- entity: ListEntity,
88
- payload: { item: ListItem; index: number },
89
- api: Api,
90
- ): TemplateResult
91
- }