@inglorious/ui 9.10.1 → 9.11.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/package.json +3 -3
- package/src/controls/select/style.css +1 -1
- package/src/data-display/data-grid/filters/helpers.js +24 -0
- package/src/data-display/data-grid/filters/input.js +3 -1
- package/src/data-display/data-grid/filters/range.js +11 -1
- package/src/data-display/data-grid/filters/select.js +2 -0
- package/src/data-display/data-grid/handlers.js +2 -0
- package/src/data-display/data-grid/helpers.js +9 -2
- package/src/data-display/data-grid/template.js +17 -10
- package/src/data-display/data-grid/theme.css +16 -0
- package/types/data-display/data-grid.d.ts +34 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/ui",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.11.1",
|
|
4
4
|
"description": "A design system for Inglorious Web with render-function components and customizable themes",
|
|
5
5
|
"author": "Matteo Antony Mistretta <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -341,7 +341,7 @@
|
|
|
341
341
|
"peerDependencies": {
|
|
342
342
|
"@inglorious/store": "^12.1.1",
|
|
343
343
|
"@inglorious/utils": "^5.1.0",
|
|
344
|
-
"@inglorious/web": "^6.14.
|
|
344
|
+
"@inglorious/web": "^6.14.1"
|
|
345
345
|
},
|
|
346
346
|
"devDependencies": {
|
|
347
347
|
"@iconify-icons/fa7-brands": "^2.0.0",
|
|
@@ -353,7 +353,7 @@
|
|
|
353
353
|
"@inglorious/store": "12.1.1",
|
|
354
354
|
"@inglorious/vite-plugin-jsx": "2.0.6",
|
|
355
355
|
"@inglorious/vite-plugin-vue": "1.0.7",
|
|
356
|
-
"@inglorious/web": "6.14.
|
|
356
|
+
"@inglorious/web": "6.14.1",
|
|
357
357
|
"@lit-labs/ssr": "^4.1.0",
|
|
358
358
|
"@storybook/addon-docs": "^10.6.5",
|
|
359
359
|
"@storybook/web-components-vite": "^10.6.5",
|
|
@@ -6,3 +6,27 @@ export function format(value, type) {
|
|
|
6
6
|
return new Date(value).getTime()
|
|
7
7
|
return value
|
|
8
8
|
}
|
|
9
|
+
|
|
10
|
+
const DATE_LENGTH = 10
|
|
11
|
+
const DATETIME_LENGTH = 16
|
|
12
|
+
const MINUTE_MS = 60_000
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A filter value as its field shows it. Date fields read their text as UTC,
|
|
16
|
+
* datetime fields as local time, so each turns back the same way.
|
|
17
|
+
* @param {any} value
|
|
18
|
+
* @param {string} inputType
|
|
19
|
+
* @returns {string|number}
|
|
20
|
+
*/
|
|
21
|
+
export function toField(value, inputType) {
|
|
22
|
+
if (value == null || Number.isNaN(value)) return ""
|
|
23
|
+
if (typeof value !== "number") return value
|
|
24
|
+
if (inputType === "date") {
|
|
25
|
+
return new Date(value).toISOString().slice(0, DATE_LENGTH)
|
|
26
|
+
}
|
|
27
|
+
if (inputType === "datetime-local") {
|
|
28
|
+
const offset = new Date(value).getTimezoneOffset() * MINUTE_MS
|
|
29
|
+
return new Date(value - offset).toISOString().slice(0, DATETIME_LENGTH)
|
|
30
|
+
}
|
|
31
|
+
return value
|
|
32
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Input } from "../../../controls/input/index.js"
|
|
2
|
-
import { format } from "./helpers.js"
|
|
2
|
+
import { format, toField } from "./helpers.js"
|
|
3
3
|
|
|
4
4
|
const DEFAULT_PLACEHOLDER = {
|
|
5
5
|
number: "=",
|
|
@@ -10,7 +10,9 @@ export const InputFilter = {
|
|
|
10
10
|
render(entity, column) {
|
|
11
11
|
return Input.render({
|
|
12
12
|
name: column.id,
|
|
13
|
+
ariaLabel: column.filter.ariaLabel ?? `Filter ${column.title}`,
|
|
13
14
|
inputType: column.filter.type,
|
|
15
|
+
value: toField(entity.filters[column.id], column.filter.type),
|
|
14
16
|
placeholder:
|
|
15
17
|
column.filter.placeholder ?? DEFAULT_PLACEHOLDER[column.filter.type],
|
|
16
18
|
size: "sm",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { html } from "@inglorious/web"
|
|
2
2
|
|
|
3
3
|
import { Input } from "../../../controls/input/index.js"
|
|
4
|
-
import { format } from "./helpers.js"
|
|
4
|
+
import { format, toField } from "./helpers.js"
|
|
5
5
|
|
|
6
6
|
const RANGE_TYPE = {
|
|
7
7
|
range: "number",
|
|
@@ -15,7 +15,12 @@ export const RangeFilter = {
|
|
|
15
15
|
return html`<div class="iw-data-grid-filter-range">
|
|
16
16
|
${Input.render({
|
|
17
17
|
name: `${column.id}Min`,
|
|
18
|
+
ariaLabel: column.filter.minLabel ?? `Filter ${column.title}: min`,
|
|
18
19
|
inputType: RANGE_TYPE[column.filter.type],
|
|
20
|
+
value: toField(
|
|
21
|
+
entity.filters[column.id]?.min,
|
|
22
|
+
RANGE_TYPE[column.filter.type],
|
|
23
|
+
),
|
|
19
24
|
placeholder: column.filter.placeholder ?? "≥",
|
|
20
25
|
size: "sm",
|
|
21
26
|
isFullWidth: true,
|
|
@@ -29,7 +34,12 @@ export const RangeFilter = {
|
|
|
29
34
|
})}
|
|
30
35
|
${Input.render({
|
|
31
36
|
name: `${column.id}Max`,
|
|
37
|
+
ariaLabel: column.filter.maxLabel ?? `Filter ${column.title}: max`,
|
|
32
38
|
inputType: RANGE_TYPE[column.filter.type],
|
|
39
|
+
value: toField(
|
|
40
|
+
entity.filters[column.id]?.max,
|
|
41
|
+
RANGE_TYPE[column.filter.type],
|
|
42
|
+
),
|
|
33
43
|
placeholder: column.filter.placeholder ?? "≤",
|
|
34
44
|
size: "sm",
|
|
35
45
|
isFullWidth: true,
|
|
@@ -5,6 +5,8 @@ export const SelectFilter = {
|
|
|
5
5
|
render(entity, column) {
|
|
6
6
|
return Select.render({
|
|
7
7
|
name: column.id,
|
|
8
|
+
ariaLabel: column.filter.ariaLabel ?? `Filter ${column.title}`,
|
|
9
|
+
value: entity.filters[column.id] ?? "",
|
|
8
10
|
size: "sm",
|
|
9
11
|
isFullWidth: true,
|
|
10
12
|
options: column.filter.options,
|
|
@@ -60,11 +60,15 @@ export function getEditedRows(entity) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* Gets the processed rows (filtered, searched, sorted, paginated).
|
|
63
|
+
* Gets the processed rows (filtered, searched, sorted, paginated). A grid with
|
|
64
|
+
* `totalRows` holds the server's page already processed, so it gets the rows
|
|
65
|
+
* as they are.
|
|
64
66
|
* @param {TableEntity} entity
|
|
65
67
|
* @returns {any[]}
|
|
66
68
|
*/
|
|
67
69
|
export function getRows(entity) {
|
|
70
|
+
if (entity.totalRows != null) return entity.rows
|
|
71
|
+
|
|
68
72
|
let rows = entity.rows
|
|
69
73
|
rows = applyFilters(entity, rows)
|
|
70
74
|
rows = applySearch(entity, rows)
|
|
@@ -75,11 +79,14 @@ export function getRows(entity) {
|
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
/**
|
|
78
|
-
* Gets the total number of rows after filtering and searching
|
|
82
|
+
* Gets the total number of rows after filtering and searching, or the
|
|
83
|
+
* server's `totalRows` when the grid has it.
|
|
79
84
|
* @param {TableEntity} entity
|
|
80
85
|
* @returns {number}
|
|
81
86
|
*/
|
|
82
87
|
export function getTotalRows(entity) {
|
|
88
|
+
if (entity.totalRows != null) return entity.totalRows
|
|
89
|
+
|
|
83
90
|
let rows = entity.rows
|
|
84
91
|
rows = applyFilters(entity, rows)
|
|
85
92
|
rows = applySearch(entity, rows)
|
|
@@ -117,16 +117,22 @@ export const DataGrid = {
|
|
|
117
117
|
class="iw-data-grid-header-column"
|
|
118
118
|
style=${getColumnStyle(column)}
|
|
119
119
|
>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
120
|
+
${when(
|
|
121
|
+
column.isSortable,
|
|
122
|
+
() =>
|
|
123
|
+
html`<button
|
|
124
|
+
type="button"
|
|
125
|
+
class="iw-data-grid-header-title iw-data-grid-header-sort"
|
|
126
|
+
@click=${() => props.onSortChange?.(column.id)}
|
|
127
|
+
>
|
|
128
|
+
${column.title}
|
|
129
|
+
<span aria-hidden="true"
|
|
130
|
+
>${getSortIcon(getSortDirection(props, column.id))}</span
|
|
131
|
+
>
|
|
132
|
+
</button>`,
|
|
133
|
+
() =>
|
|
134
|
+
html`<div class="iw-data-grid-header-title">${column.title}</div>`,
|
|
135
|
+
)}
|
|
130
136
|
${when(column.isFilterable, () => Filters.render(props, column))}
|
|
131
137
|
|
|
132
138
|
<div
|
|
@@ -153,6 +159,7 @@ export const DataGrid = {
|
|
|
153
159
|
size: "sm",
|
|
154
160
|
isFullWidth: true,
|
|
155
161
|
name: "search",
|
|
162
|
+
ariaLabel: props.search.ariaLabel ?? "Search",
|
|
156
163
|
inputType: "text",
|
|
157
164
|
placeholder: props.search.placeholder ?? "Fuzzy search...",
|
|
158
165
|
value: props.search.value,
|
|
@@ -29,6 +29,22 @@
|
|
|
29
29
|
white-space: nowrap;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
.iw-data-grid-header-sort {
|
|
33
|
+
padding: 0;
|
|
34
|
+
border: 0;
|
|
35
|
+
background: none;
|
|
36
|
+
color: inherit;
|
|
37
|
+
font: inherit;
|
|
38
|
+
font-weight: var(--iw-font-weight-bold);
|
|
39
|
+
text-align: start;
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
|
|
42
|
+
&:focus-visible {
|
|
43
|
+
outline: 2px solid var(--iw-color-primary);
|
|
44
|
+
outline-offset: 2px;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
32
48
|
.iw-data-grid-column-resizer {
|
|
33
49
|
&::after {
|
|
34
50
|
content: "";
|
|
@@ -36,6 +36,12 @@ export interface Column {
|
|
|
36
36
|
filter?: {
|
|
37
37
|
type: "text" | "number" | "range" | "select" | "date" | "time" | "datetime"
|
|
38
38
|
options?: SelectOption[]
|
|
39
|
+
/** The filter field's accessible name. Defaults to "Filter <title>". */
|
|
40
|
+
ariaLabel?: string
|
|
41
|
+
/** A range filter's lower field's accessible name. */
|
|
42
|
+
minLabel?: string
|
|
43
|
+
/** A range filter's upper field's accessible name. */
|
|
44
|
+
maxLabel?: string
|
|
39
45
|
[key: string]: any
|
|
40
46
|
}
|
|
41
47
|
/**
|
|
@@ -67,7 +73,12 @@ export interface DataGridProps<T extends Row = Row> {
|
|
|
67
73
|
columns: Column[]
|
|
68
74
|
sorts: { column: string; direction: "asc" | "desc" }[]
|
|
69
75
|
filters: Record<string, any>
|
|
70
|
-
search: {
|
|
76
|
+
search: {
|
|
77
|
+
value: string
|
|
78
|
+
placeholder?: string
|
|
79
|
+
/** The search field's accessible name. Defaults to "Search". */
|
|
80
|
+
ariaLabel?: string
|
|
81
|
+
} | null
|
|
71
82
|
isStriped?: boolean
|
|
72
83
|
isVirtualized?: boolean
|
|
73
84
|
virtualization?: DataGridVirtualization
|
|
@@ -75,6 +86,13 @@ export interface DataGridProps<T extends Row = Row> {
|
|
|
75
86
|
selectionAnchor?: string | number | null
|
|
76
87
|
pagination?: { page: number; pageSize: number; pageSizes: number[] } | null
|
|
77
88
|
rowId?: string
|
|
89
|
+
/**
|
|
90
|
+
* How many rows the server has for the current filters and search. When it
|
|
91
|
+
* is set, `rows` is the page the server already filtered, sorted and sliced,
|
|
92
|
+
* so the grid shows them as they are and pages by this total. Set it to `0`
|
|
93
|
+
* before the first load to start the grid server-side.
|
|
94
|
+
*/
|
|
95
|
+
totalRows?: number | null
|
|
78
96
|
isMultiSelect: boolean
|
|
79
97
|
/**
|
|
80
98
|
* The user's unsaved edits, by row key and column id, shown over the rows
|
|
@@ -159,36 +177,41 @@ export type DataGridType = {
|
|
|
159
177
|
virtualMount(entity: DataGridProps, containerEl: HTMLElement): void
|
|
160
178
|
virtualScroll(entity: DataGridProps, containerEl: HTMLElement): void
|
|
161
179
|
render(entity: DataGridProps, api: Api): TemplateResult
|
|
162
|
-
renderDataGrid(entity: DataGridProps
|
|
163
|
-
renderHeader(entity: DataGridProps
|
|
164
|
-
renderToolbar(entity: DataGridProps
|
|
180
|
+
renderDataGrid(entity: DataGridProps): TemplateResult
|
|
181
|
+
renderHeader(entity: DataGridProps): TemplateResult
|
|
182
|
+
renderToolbar(entity: DataGridProps): TemplateResult
|
|
183
|
+
renderSearchbar(entity: DataGridProps): TemplateResult
|
|
165
184
|
renderHeaderColumn(
|
|
166
185
|
entity: DataGridProps,
|
|
167
186
|
payload: { column: Column; index: number },
|
|
168
|
-
api: Api,
|
|
169
187
|
): TemplateResult
|
|
170
|
-
renderBody(entity: DataGridProps
|
|
188
|
+
renderBody(entity: DataGridProps): TemplateResult
|
|
171
189
|
renderRow(
|
|
172
190
|
entity: DataGridProps,
|
|
173
191
|
payload: { row: Row; index: number },
|
|
174
|
-
api: Api,
|
|
175
192
|
): TemplateResult
|
|
176
193
|
renderCell(
|
|
177
194
|
entity: DataGridProps,
|
|
178
195
|
payload: { cell: any; index: number; row?: Row },
|
|
179
|
-
api: Api,
|
|
180
196
|
): TemplateResult
|
|
181
197
|
renderEditor(
|
|
182
198
|
entity: DataGridProps,
|
|
183
199
|
payload: { value: any; column: Column; row: Row; index: number },
|
|
184
|
-
api: Api,
|
|
185
200
|
): TemplateResult
|
|
186
|
-
renderEdits(entity: DataGridProps, editCount: number): TemplateResult
|
|
187
201
|
renderValue(
|
|
188
202
|
entity: DataGridProps,
|
|
189
203
|
payload: { value: any; column: Column; index: number },
|
|
190
|
-
api: Api,
|
|
191
204
|
): any
|
|
205
|
+
renderFooter(entity: DataGridProps): TemplateResult
|
|
206
|
+
renderEdits(entity: DataGridProps, editCount: number): TemplateResult
|
|
207
|
+
renderPagination(
|
|
208
|
+
entity: DataGridProps,
|
|
209
|
+
pagination: DataGridPaginationInfo,
|
|
210
|
+
): TemplateResult
|
|
211
|
+
renderPageSize(
|
|
212
|
+
entity: DataGridProps,
|
|
213
|
+
pagination: DataGridPaginationInfo,
|
|
214
|
+
): TemplateResult
|
|
192
215
|
}
|
|
193
216
|
|
|
194
217
|
export declare const DataGrid: DataGridType
|