@inglorious/web 2.5.0 → 2.6.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.
@@ -1,14 +1,24 @@
1
1
  /* eslint-disable no-magic-numbers */
2
2
 
3
- export const logic = {
4
- init(entity) {
5
- initTable(entity)
6
- },
3
+ /**
4
+ * @typedef {import('../../types/table').TableEntity} TableEntity
5
+ * @typedef {import('../../types/table').TableColumn} TableColumn
6
+ */
7
7
 
8
+ export const logic = {
9
+ /**
10
+ * Resets the table entity with default state.
11
+ * @param {TableEntity} entity
12
+ */
8
13
  create(entity) {
9
14
  initTable(entity)
10
15
  },
11
16
 
17
+ /**
18
+ * Toggles sorting for a column.
19
+ * @param {TableEntity} entity
20
+ * @param {string} columnId
21
+ */
12
22
  sortChange(entity, columnId) {
13
23
  const column = entity.columns.find((c) => c.id === columnId)
14
24
  if (!column?.isSortable) return
@@ -34,10 +44,19 @@ export const logic = {
34
44
  }
35
45
  },
36
46
 
47
+ /**
48
+ * Clears all sorts.
49
+ * @param {TableEntity} entity
50
+ */
37
51
  sortsClear(entity) {
38
52
  entity.sorts = []
39
53
  },
40
54
 
55
+ /**
56
+ * Updates a filter value for a column.
57
+ * @param {TableEntity} entity
58
+ * @param {{ columnId: string, value: any }} payload
59
+ */
41
60
  filterChange(entity, { columnId, value }) {
42
61
  if (value == null || value === "") {
43
62
  delete entity.filters[columnId]
@@ -51,6 +70,10 @@ export const logic = {
51
70
  }
52
71
  },
53
72
 
73
+ /**
74
+ * Clears all filters.
75
+ * @param {TableEntity} entity
76
+ */
54
77
  filtersClear(entity) {
55
78
  entity.filters = {}
56
79
  if (entity.pagination) {
@@ -58,10 +81,20 @@ export const logic = {
58
81
  }
59
82
  },
60
83
 
84
+ /**
85
+ * Updates the search term.
86
+ * @param {TableEntity} entity
87
+ * @param {string} search
88
+ */
61
89
  searchChange(entity, search) {
62
90
  entity.search.value = search
63
91
  },
64
92
 
93
+ /**
94
+ * Changes the current page.
95
+ * @param {TableEntity} entity
96
+ * @param {number} page
97
+ */
65
98
  pageChange(entity, page) {
66
99
  if (!entity.pagination) return
67
100
 
@@ -71,6 +104,10 @@ export const logic = {
71
104
  entity.pagination.page = Math.max(0, Math.min(page, totalPages - 1))
72
105
  },
73
106
 
107
+ /**
108
+ * Moves to the next page.
109
+ * @param {TableEntity} entity
110
+ */
74
111
  pageNext(entity) {
75
112
  if (!entity.pagination) return
76
113
  const totalPages = Math.ceil(
@@ -82,11 +119,20 @@ export const logic = {
82
119
  )
83
120
  },
84
121
 
122
+ /**
123
+ * Moves to the previous page.
124
+ * @param {TableEntity} entity
125
+ */
85
126
  pagePrev(entity) {
86
127
  if (!entity.pagination) return
87
128
  entity.pagination.page = Math.max(entity.pagination.page - 1, 0)
88
129
  },
89
130
 
131
+ /**
132
+ * Changes the page size.
133
+ * @param {TableEntity} entity
134
+ * @param {number} pageSize
135
+ */
90
136
  pageSizeChange(entity, pageSize) {
91
137
  if (!entity.pagination) return
92
138
 
@@ -94,6 +140,11 @@ export const logic = {
94
140
  entity.pagination.page = 0
95
141
  },
96
142
 
143
+ /**
144
+ * Selects a row.
145
+ * @param {TableEntity} entity
146
+ * @param {string|number} rowId
147
+ */
97
148
  rowSelect(entity, rowId) {
98
149
  if (!entity.isMultiSelect) {
99
150
  entity.selection = []
@@ -104,6 +155,11 @@ export const logic = {
104
155
  }
105
156
  },
106
157
 
158
+ /**
159
+ * Deselects a row.
160
+ * @param {TableEntity} entity
161
+ * @param {string|number} rowId
162
+ */
107
163
  rowDeselect(entity, rowId) {
108
164
  const index = entity.selection.indexOf(rowId)
109
165
  if (index !== -1) {
@@ -111,6 +167,11 @@ export const logic = {
111
167
  }
112
168
  },
113
169
 
170
+ /**
171
+ * Toggles row selection.
172
+ * @param {TableEntity} entity
173
+ * @param {string|number} rowId
174
+ */
114
175
  rowToggle(entity, rowId) {
115
176
  const index = entity.selection.indexOf(rowId)
116
177
 
@@ -125,6 +186,10 @@ export const logic = {
125
186
  }
126
187
  },
127
188
 
189
+ /**
190
+ * Toggles selection of all currently visible rows.
191
+ * @param {TableEntity} entity
192
+ */
128
193
  rowsToggleAll(entity) {
129
194
  const rows = getRows(entity)
130
195
  const allSelected = rows.every((row) => entity.selection.includes(row.id))
@@ -145,6 +210,10 @@ export const logic = {
145
210
  }
146
211
  },
147
212
 
213
+ /**
214
+ * Selects all currently visible rows.
215
+ * @param {TableEntity} entity
216
+ */
148
217
  rowsSelectAll(entity) {
149
218
  const rows = getRows(entity)
150
219
  rows.forEach((row) => {
@@ -154,13 +223,22 @@ export const logic = {
154
223
  })
155
224
  },
156
225
 
226
+ /**
227
+ * Clears all row selections.
228
+ * @param {TableEntity} entity
229
+ */
157
230
  selectionClear(entity) {
158
231
  entity.selection.length = 0
159
232
  },
160
233
  }
161
234
 
162
- // Helper functions outside the type (like form helpers)
235
+ // Helper functions
163
236
 
237
+ /**
238
+ * Gets the processed rows (filtered, searched, sorted, paginated).
239
+ * @param {TableEntity} entity
240
+ * @returns {any[]}
241
+ */
164
242
  export function getRows(entity) {
165
243
  let rows = entity.data
166
244
  rows = applyFilters(entity, rows)
@@ -171,6 +249,11 @@ export function getRows(entity) {
171
249
  return rows
172
250
  }
173
251
 
252
+ /**
253
+ * Gets the total number of rows after filtering and searching.
254
+ * @param {TableEntity} entity
255
+ * @returns {number}
256
+ */
174
257
  export function getTotalRows(entity) {
175
258
  let rows = entity.data
176
259
  rows = applyFilters(entity, rows)
@@ -178,6 +261,11 @@ export function getTotalRows(entity) {
178
261
  return rows.length
179
262
  }
180
263
 
264
+ /**
265
+ * Gets pagination information.
266
+ * @param {TableEntity} entity
267
+ * @returns {object|null}
268
+ */
181
269
  export function getPaginationInfo(entity) {
182
270
  if (!entity.pagination) return null
183
271
 
@@ -199,28 +287,62 @@ export function getPaginationInfo(entity) {
199
287
  }
200
288
  }
201
289
 
290
+ /**
291
+ * Gets the sort direction for a column.
292
+ * @param {TableEntity} entity
293
+ * @param {string} columnId
294
+ * @returns {"asc"|"desc"|null}
295
+ */
202
296
  export function getSortDirection(entity, columnId) {
203
297
  const sort = entity.sorts.find((s) => s.column === columnId)
204
298
  return sort?.direction || null
205
299
  }
206
300
 
301
+ /**
302
+ * Gets the sort index (priority) for a column.
303
+ * @param {TableEntity} entity
304
+ * @param {string} columnId
305
+ * @returns {number}
306
+ */
207
307
  export function getSortIndex(entity, columnId) {
208
308
  return entity.sorts.findIndex((s) => s.column === columnId)
209
309
  }
210
310
 
311
+ /**
312
+ * Gets the filter value for a column.
313
+ * @param {TableEntity} entity
314
+ * @param {string} columnId
315
+ * @returns {any}
316
+ */
211
317
  export function getFilter(entity, columnId) {
212
318
  return entity.filters[columnId]
213
319
  }
214
320
 
321
+ /**
322
+ * Checks if a row is selected.
323
+ * @param {TableEntity} entity
324
+ * @param {string|number} rowId
325
+ * @returns {boolean}
326
+ */
215
327
  export function isRowSelected(entity, rowId) {
216
328
  return entity.selection.includes(rowId)
217
329
  }
218
330
 
331
+ /**
332
+ * Checks if all visible rows are selected.
333
+ * @param {TableEntity} entity
334
+ * @returns {boolean}
335
+ */
219
336
  export function isAllSelected(entity) {
220
337
  const rows = getRows(entity)
221
338
  return rows.length && rows.every((row) => entity.selection.includes(row.id))
222
339
  }
223
340
 
341
+ /**
342
+ * Checks if some (but not all) visible rows are selected.
343
+ * @param {TableEntity} entity
344
+ * @returns {boolean}
345
+ */
224
346
  export function isSomeSelected(entity) {
225
347
  const rows = getRows(entity)
226
348
  const selectedCount = rows.filter((row) =>
@@ -229,6 +351,12 @@ export function isSomeSelected(entity) {
229
351
  return selectedCount && selectedCount < rows.length
230
352
  }
231
353
 
354
+ // Private helper functions
355
+
356
+ /**
357
+ * Initializes the table state.
358
+ * @param {TableEntity} entity
359
+ */
232
360
  function initTable(entity) {
233
361
  entity.data ??= []
234
362
 
@@ -276,6 +404,11 @@ function initTable(entity) {
276
404
  }
277
405
  }
278
406
 
407
+ /**
408
+ * Infers the column type from a value.
409
+ * @param {any} value
410
+ * @returns {string}
411
+ */
279
412
  function getDefaultColumnType(value) {
280
413
  if (typeof value === "number") return "number"
281
414
  if (typeof value === "boolean") return "boolean"
@@ -283,6 +416,11 @@ function getDefaultColumnType(value) {
283
416
  return "string"
284
417
  }
285
418
 
419
+ /**
420
+ * Gets the default filter configuration for a column type.
421
+ * @param {string} type
422
+ * @returns {object}
423
+ */
286
424
  function getDefaultColumnFilter(type) {
287
425
  if (type === "number") return { type: "range" }
288
426
  if (type === "boolean")
@@ -291,6 +429,11 @@ function getDefaultColumnFilter(type) {
291
429
  return { type: "text" }
292
430
  }
293
431
 
432
+ /**
433
+ * Gets the default column width based on filter type.
434
+ * @param {string} filterType
435
+ * @returns {number}
436
+ */
294
437
  function getDefaultColumnWidth(filterType) {
295
438
  if (filterType === "number") return 70
296
439
  if (filterType === "range") return 100
@@ -301,6 +444,12 @@ function getDefaultColumnWidth(filterType) {
301
444
  return 200
302
445
  }
303
446
 
447
+ /**
448
+ * Applies filters to the data.
449
+ * @param {TableEntity} entity
450
+ * @param {any[]} rows
451
+ * @returns {any[]}
452
+ */
304
453
  function applyFilters(entity, rows) {
305
454
  if (!Object.keys(entity.filters).length) {
306
455
  return rows
@@ -338,6 +487,12 @@ function applyFilters(entity, rows) {
338
487
  })
339
488
  }
340
489
 
490
+ /**
491
+ * Applies search to the data.
492
+ * @param {TableEntity} entity
493
+ * @param {any[]} rows
494
+ * @returns {any[]}
495
+ */
341
496
  function applySearch(entity, rows) {
342
497
  if (!entity.search?.value) {
343
498
  return rows
@@ -354,6 +509,12 @@ function applySearch(entity, rows) {
354
509
  )
355
510
  }
356
511
 
512
+ /**
513
+ * Applies sorting to the data.
514
+ * @param {TableEntity} entity
515
+ * @param {any[]} rows
516
+ * @returns {any[]}
517
+ */
357
518
  function applySorts(entity, rows) {
358
519
  if (!entity.sorts.length) {
359
520
  return rows
@@ -385,6 +546,12 @@ function applySorts(entity, rows) {
385
546
  })
386
547
  }
387
548
 
549
+ /**
550
+ * Applies pagination to the data.
551
+ * @param {TableEntity} entity
552
+ * @param {any[]} rows
553
+ * @returns {any[]}
554
+ */
388
555
  function applyPagination(entity, rows) {
389
556
  if (!entity.pagination) {
390
557
  return rows
@@ -395,6 +562,11 @@ function applyPagination(entity, rows) {
395
562
  return rows.slice(start, start + pageSize)
396
563
  }
397
564
 
565
+ /**
566
+ * Capitalizes the first letter of a string.
567
+ * @param {string} str
568
+ * @returns {string}
569
+ */
398
570
  function capitalize(str) {
399
571
  const [firstChar, ...rest] = str
400
572
  return [firstChar.toUpperCase(), ...rest].join("")
package/src/table.test.js CHANGED
@@ -61,14 +61,13 @@ describe("table", () => {
61
61
  pagination: { page: 0, pageSize: 2 },
62
62
  search: { value: "" },
63
63
  }
64
- table.init(entity)
65
64
  })
66
65
 
67
66
  describe("logic", () => {
68
- describe("init() and create()", () => {
67
+ describe("create()", () => {
69
68
  it("should initialize with default state", () => {
70
69
  const newEntity = { data: [{ id: 1, name: "Test" }] }
71
- table.init(newEntity)
70
+ table.create(newEntity)
72
71
  expect(newEntity.sorts).toEqual([])
73
72
  expect(newEntity.filters).toEqual({})
74
73
  expect(newEntity.selection).toEqual([])
@@ -79,6 +78,10 @@ describe("table", () => {
79
78
  })
80
79
 
81
80
  describe("sortChange()", () => {
81
+ beforeEach(() => {
82
+ table.create(entity)
83
+ })
84
+
82
85
  it("should add a new sort", () => {
83
86
  table.sortChange(entity, "name")
84
87
  expect(entity.sorts).toEqual([{ column: "name", direction: "asc" }])
@@ -105,6 +108,10 @@ describe("table", () => {
105
108
  })
106
109
 
107
110
  describe("filterChange()", () => {
111
+ beforeEach(() => {
112
+ table.create(entity)
113
+ })
114
+
108
115
  it("should add a filter", () => {
109
116
  table.filterChange(entity, { columnId: "name", value: "Alice" })
110
117
  expect(entity.filters.name).toBe("Alice")
@@ -124,6 +131,10 @@ describe("table", () => {
124
131
  })
125
132
 
126
133
  describe("pagination", () => {
134
+ beforeEach(() => {
135
+ table.create(entity)
136
+ })
137
+
127
138
  it("pageNext: should go to the next page", () => {
128
139
  table.pageNext(entity)
129
140
  expect(entity.pagination.page).toBe(1)
@@ -155,6 +166,10 @@ describe("table", () => {
155
166
  })
156
167
 
157
168
  describe("selection", () => {
169
+ beforeEach(() => {
170
+ table.create(entity)
171
+ })
172
+
158
173
  it("rowToggle: should select an unselected row", () => {
159
174
  table.rowToggle(entity, 1)
160
175
  expect(entity.selection).toContain(1)
@@ -189,6 +204,10 @@ describe("table", () => {
189
204
  })
190
205
 
191
206
  describe("getters and selectors", () => {
207
+ beforeEach(() => {
208
+ table.create(entity)
209
+ })
210
+
192
211
  it("getRows: should return sorted, filtered, and paginated rows", () => {
193
212
  // Sort by age descending
194
213
  table.sortChange(entity, "age")
@@ -256,6 +275,8 @@ describe("table", () => {
256
275
  renderFooter: vi.fn(() => html`<div>Footer</div>`),
257
276
  }),
258
277
  }
278
+
279
+ table.create(entity)
259
280
  })
260
281
 
261
282
  it("render: should call sub-renderers", () => {
package/types/form.d.ts CHANGED
@@ -191,20 +191,10 @@ export interface FormValidationErrorPayload {
191
191
  */
192
192
  export declare const form: {
193
193
  /**
194
- * Initializes the form entity by resetting it to its initial state.
194
+ * Initializes the form entity with default state.
195
195
  * @param entity The form entity.
196
196
  */
197
- init<T extends FormValues>(entity: FormEntity<T>): void
198
-
199
- /**
200
- * Resets the form entity when a 'create' event payload matches its ID.
201
- * @param entity The form entity.
202
- * @param entityId The entity ID from the create event, used to target a specific form.
203
- */
204
- create<T extends FormValues>(
205
- entity: FormEntity<T>,
206
- entityId: string | number,
207
- ): void
197
+ create<T extends FormValues>(entity: FormEntity<T>): void
208
198
 
209
199
  /**
210
200
  * Appends an item to a field array.
package/types/index.d.ts CHANGED
@@ -1,2 +1,6 @@
1
+ export * from "./form"
2
+ export * from "./list"
1
3
  export * from "./mount"
2
4
  export * from "./router"
5
+ export * from "./select"
6
+ export * from "./table"
@@ -0,0 +1,76 @@
1
+ import type { TemplateResult } from "lit-html"
2
+ import type { Api } from "./mount"
3
+
4
+ /**
5
+ * Represents the visible range of items in the list.
6
+ */
7
+ export interface VisibleRange {
8
+ start: number
9
+ end: number
10
+ }
11
+
12
+ /**
13
+ * Represents the state of a list entity.
14
+ */
15
+ export interface ListEntity<T = any> {
16
+ /** A unique identifier for the list entity. */
17
+ id: string | number
18
+ /** The entity type (usually 'list'). */
19
+ type: string
20
+ /** The array of items to render. */
21
+ items: T[]
22
+ /** The current scroll position of the list container. */
23
+ scrollTop: number
24
+ /** The range of items currently visible (plus buffer). */
25
+ visibleRange: VisibleRange
26
+ /** The height of the viewport in pixels. */
27
+ viewportHeight: number
28
+ /** The number of extra items to render above and below the visible range. */
29
+ bufferSize: number
30
+ /** The fixed height of each item in pixels, or null if measuring. */
31
+ itemHeight: number | null
32
+ /** The estimated height of an item, used before measurement. */
33
+ estimatedHeight: number
34
+ /** Any other custom properties. */
35
+ [key: string]: any
36
+ }
37
+
38
+ /**
39
+ * The list type implementation.
40
+ */
41
+ export declare const list: {
42
+ /**
43
+ * Initializes the list entity with default state.
44
+ * @param entity The list entity.
45
+ */
46
+ create(entity: ListEntity): void
47
+
48
+ /**
49
+ * Handles the scroll event to update the visible range.
50
+ * @param entity The list entity.
51
+ * @param containerEl The scrolling container element.
52
+ */
53
+ scroll(entity: ListEntity, containerEl: HTMLElement): void
54
+
55
+ /**
56
+ * Mounts the list, measuring the first item to determine item height.
57
+ * @param entity The list entity.
58
+ * @param containerEl The scrolling container element.
59
+ */
60
+ mount(entity: ListEntity, containerEl: HTMLElement): void
61
+
62
+ /**
63
+ * Renders the virtualized list component.
64
+ * @param entity The list entity.
65
+ * @param api The store API.
66
+ */
67
+ render(entity: ListEntity, api: Api): TemplateResult
68
+
69
+ /**
70
+ * Default item renderer.
71
+ * @param item The item to render.
72
+ * @param index The index of the item.
73
+ * @param api The store API.
74
+ */
75
+ renderItem(item: any, index: number, api: Api): TemplateResult
76
+ }
@@ -0,0 +1,136 @@
1
+ import type { TemplateResult } from "lit-html"
2
+ import type { Api } from "./mount"
3
+
4
+ /**
5
+ * Represents an option in the select component.
6
+ * Can be a simple string/number or an object with value and label.
7
+ */
8
+ export type SelectOption =
9
+ | string
10
+ | number
11
+ | {
12
+ value: string | number
13
+ label?: string
14
+ disabled?: boolean
15
+ [key: string]: any
16
+ }
17
+
18
+ /**
19
+ * Represents the state of a select entity.
20
+ */
21
+ export interface SelectEntity {
22
+ /** A unique identifier for the select entity. */
23
+ id: string | number
24
+ /** The entity type (usually 'select'). */
25
+ type: string
26
+ /** The list of options available for selection. */
27
+ options: SelectOption[]
28
+ /** Whether the dropdown is currently open. */
29
+ isOpen: boolean
30
+ /** The current search term entered by the user. */
31
+ searchTerm: string
32
+ /** The index of the currently focused option (for keyboard navigation). */
33
+ focusedIndex: number
34
+ /** Whether multiple options can be selected. */
35
+ isMulti: boolean
36
+ /** The currently selected value(s). */
37
+ selectedValue: string | number | (string | number)[] | null
38
+ /** Whether the options are currently loading. */
39
+ isLoading: boolean
40
+ /** Whether the select component is disabled. */
41
+ isDisabled: boolean
42
+ /** Whether the user can search through options. */
43
+ isSearchable: boolean
44
+ /** Whether the selection can be cleared. */
45
+ isClearable: boolean
46
+ /** Whether new options can be created by the user. */
47
+ isCreatable: boolean
48
+ /** Placeholder text to display when no value is selected. */
49
+ placeholder: string
50
+ /** Message to display when no options match the search term. */
51
+ noOptionsMessage: string
52
+ /** Message to display while options are loading. */
53
+ loadingMessage: string
54
+ /** Property name to group options by. */
55
+ groupBy: string | null
56
+ }
57
+
58
+ /**
59
+ * The select type implementation.
60
+ */
61
+ export declare const select: {
62
+ /**
63
+ * Initializes the select entity with default state.
64
+ * @param {SelectEntity} entity
65
+ */
66
+ create(entity: SelectEntity): void
67
+
68
+ /**
69
+ * Renders the select component.
70
+ * @param entity The select entity.
71
+ * @param api The store API.
72
+ */
73
+ render(entity: SelectEntity, api: Api): TemplateResult
74
+
75
+ /**
76
+ * Opens the select dropdown.
77
+ * @param entity The select entity.
78
+ */
79
+ open(entity: SelectEntity): void
80
+
81
+ /**
82
+ * Closes the select dropdown.
83
+ * @param entity The select entity.
84
+ */
85
+ close(entity: SelectEntity): void
86
+
87
+ /**
88
+ * Toggles the select dropdown open/closed state.
89
+ * @param entity The select entity.
90
+ */
91
+ toggle(entity: SelectEntity): void
92
+
93
+ /**
94
+ * Selects an option.
95
+ * @param entity The select entity.
96
+ * @param option The option to select.
97
+ */
98
+ optionSelect(entity: SelectEntity, option: SelectOption): void
99
+
100
+ /**
101
+ * Clears the current selection.
102
+ * @param entity The select entity.
103
+ */
104
+ clear(entity: SelectEntity): void
105
+
106
+ /**
107
+ * Updates the search term and filters options.
108
+ * @param entity The select entity.
109
+ * @param term The new search term.
110
+ */
111
+ searchChange(entity: SelectEntity, term: string): void
112
+
113
+ /**
114
+ * Moves focus to the next option.
115
+ * @param entity The select entity.
116
+ */
117
+ focusNext(entity: SelectEntity): void
118
+
119
+ /**
120
+ * Moves focus to the previous option.
121
+ * @param entity The select entity.
122
+ */
123
+ focusPrev(entity: SelectEntity): void
124
+
125
+ /**
126
+ * Moves focus to the first option.
127
+ * @param entity The select entity.
128
+ */
129
+ focusFirst(entity: SelectEntity): void
130
+
131
+ /**
132
+ * Moves focus to the last option.
133
+ * @param entity The select entity.
134
+ */
135
+ focusLast(entity: SelectEntity): void
136
+ }