@inglorious/ui 9.2.0 → 9.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/ui",
3
- "version": "9.2.0",
3
+ "version": "9.3.0",
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",
@@ -315,7 +315,7 @@
315
315
  "@iconify-icons/fa7-regular": "^2.0.0",
316
316
  "@iconify-icons/fa7-solid": "^2.0.0",
317
317
  "@iconify-icons/material-symbols": "^2.0.6",
318
- "@inglorious/charts": "12.1.0",
318
+ "@inglorious/charts": "12.2.0",
319
319
  "@inglorious/eslint-config": "1.2.0",
320
320
  "@inglorious/store": "12.0.0",
321
321
  "@inglorious/vite-plugin-jsx": "2.0.5",
@@ -93,6 +93,7 @@ export const Input = {
93
93
  type, // eslint-disable-line no-unused-vars
94
94
  name = "",
95
95
  label,
96
+ ariaLabel,
96
97
  hint, // eslint-disable-line no-unused-vars
97
98
  messageId,
98
99
  inputType = "text",
@@ -129,7 +130,7 @@ export const Input = {
129
130
  id=${controlId || nothing}
130
131
  name=${name}
131
132
  type=${inputType}
132
- aria-label=${fallbackLabel(controlId, label)}
133
+ aria-label=${ariaLabel || fallbackLabel(controlId, label)}
133
134
  aria-describedby=${messageId || nothing}
134
135
  aria-invalid=${error ? "true" : "false"}
135
136
  .value=${value}
@@ -48,6 +48,10 @@
48
48
  display: flex;
49
49
  }
50
50
 
51
+ .iw-data-grid-edits-actions {
52
+ display: flex;
53
+ }
54
+
51
55
  .iw-data-grid-column-resizer {
52
56
  position: absolute;
53
57
  inset: 0 0 0 auto;
@@ -74,6 +74,45 @@ export const Default = {
74
74
  },
75
75
  }
76
76
 
77
+ /** Editable columns: change cells, then reset or save the edits in the footer. */
78
+ export const Editable = {
79
+ args: {
80
+ ...Default.args,
81
+ id: "dataGridEditable",
82
+ search: null,
83
+ selection: [],
84
+ columns: [
85
+ { id: "name", title: "Name", isSortable: true, isEditable: true },
86
+ { id: "age", title: "Age", type: "number", isEditable: true },
87
+ {
88
+ id: "active",
89
+ title: "Active",
90
+ type: "boolean",
91
+ width: 100,
92
+ isEditable: true,
93
+ },
94
+ {
95
+ id: "role",
96
+ title: "Role",
97
+ width: 140,
98
+ isEditable: true,
99
+ options: [
100
+ { value: "Admin", label: "Admin" },
101
+ { value: "User", label: "User" },
102
+ ],
103
+ },
104
+ ],
105
+ },
106
+ parameters: {
107
+ docs: {
108
+ description: {
109
+ story:
110
+ "Columns with `isEditable` put an editor in each cell, by type, or a drop-down with `options`. Edits stay apart from the rows, counted in the footer, until saved.",
111
+ },
112
+ },
113
+ },
114
+ }
115
+
77
116
  export const Paginated = {
78
117
  args: {
79
118
  ...Default.args,
@@ -3,7 +3,12 @@
3
3
  * @typedef {import('../../../types/data-display/data-grid').Column} Column
4
4
  */
5
5
 
6
- import { getRowKeyValue, getRows, getTotalRows } from "./helpers.js"
6
+ import {
7
+ getEditedRows,
8
+ getRowKeyValue,
9
+ getRows,
10
+ getTotalRows,
11
+ } from "./helpers.js"
7
12
 
8
13
  const MIN_COLUMN_WIDTH = 72
9
14
  const VIRTUAL_START = 0
@@ -285,6 +290,43 @@ export function selectionClear(entity) {
285
290
  entity.selection.length = 0
286
291
  }
287
292
 
293
+ /**
294
+ * Records the user's edit of a cell, kept apart from the rows until saved. An
295
+ * edit back to the row's own value is no edit at all.
296
+ * @param {DataGridEntity} entity
297
+ * @param {{ rowId: string|number, columnId: string, value: any }} payload
298
+ */
299
+ export function cellChange(entity, { rowId, columnId, value }) {
300
+ const row = entity.rows.find((item) => getRowKeyValue(entity, item) === rowId)
301
+ if (!row) return
302
+
303
+ entity.edits ??= {}
304
+ const edits = (entity.edits[rowId] ??= {})
305
+ if (Object.is(row[columnId], value)) {
306
+ delete edits[columnId]
307
+ } else {
308
+ edits[columnId] = value
309
+ }
310
+ if (!Object.keys(edits).length) delete entity.edits[rowId]
311
+ }
312
+
313
+ /**
314
+ * Drops every unsaved edit.
315
+ * @param {DataGridEntity} entity
316
+ */
317
+ export function editsReset(entity) {
318
+ entity.edits = {}
319
+ }
320
+
321
+ /**
322
+ * Writes the unsaved edits into the rows.
323
+ * @param {DataGridEntity} entity
324
+ */
325
+ export function editsSave(entity) {
326
+ entity.rows = getEditedRows(entity)
327
+ entity.edits = {}
328
+ }
329
+
288
330
  /**
289
331
  * Handles the scroll event to update the virtualized visible range.
290
332
  * @param {DataGridEntity} entity
@@ -394,6 +436,7 @@ function initDataGrid(entity) {
394
436
  }
395
437
  entity.selection ??= []
396
438
  entity.selectionAnchor ??= null
439
+ entity.edits ??= {}
397
440
  entity.isMultiSelect ??= true
398
441
  entity.isVirtualized ??= false
399
442
 
@@ -8,6 +8,57 @@ export function getRowKeyValue(entity, row) {
8
8
  return row?.[entity.rowId]
9
9
  }
10
10
 
11
+ /**
12
+ * A cell's value as shown: the user's unsaved edit if there is one, else the
13
+ * row's own.
14
+ * @param {TableEntity} entity
15
+ * @param {any} row
16
+ * @param {string} columnId
17
+ * @returns {any}
18
+ */
19
+ export function getCellValue(entity, row, columnId) {
20
+ const edits = entity.edits?.[getRowKeyValue(entity, row)]
21
+ return edits && Object.hasOwn(edits, columnId)
22
+ ? edits[columnId]
23
+ : row?.[columnId]
24
+ }
25
+
26
+ /**
27
+ * Whether a cell has an unsaved edit.
28
+ * @param {TableEntity} entity
29
+ * @param {any} row
30
+ * @param {string} columnId
31
+ * @returns {boolean}
32
+ */
33
+ export function isCellEdited(entity, row, columnId) {
34
+ const edits = entity.edits?.[getRowKeyValue(entity, row)]
35
+ return Boolean(edits && Object.hasOwn(edits, columnId))
36
+ }
37
+
38
+ /**
39
+ * How many cells have unsaved edits.
40
+ * @param {TableEntity} entity
41
+ * @returns {number}
42
+ */
43
+ export function getEditCount(entity) {
44
+ return Object.values(entity.edits ?? {}).reduce(
45
+ (count, edits) => count + Object.keys(edits).length,
46
+ 0,
47
+ )
48
+ }
49
+
50
+ /**
51
+ * Every row with its unsaved edits applied, in the rows' own order.
52
+ * @param {TableEntity} entity
53
+ * @returns {any[]}
54
+ */
55
+ export function getEditedRows(entity) {
56
+ return entity.rows.map((row) => {
57
+ const edits = entity.edits?.[getRowKeyValue(entity, row)]
58
+ return edits ? { ...row, ...edits } : row
59
+ })
60
+ }
61
+
11
62
  /**
12
63
  * Gets the processed rows (filtered, searched, sorted, paginated).
13
64
  * @param {TableEntity} entity
@@ -69,7 +120,7 @@ export function getPaginationInfo(entity) {
69
120
  * @returns {"asc"|"desc"|null}
70
121
  */
71
122
  export function getSortDirection(entity, columnId) {
72
- const sort = entity.sorts.find((s) => s.column === columnId)
123
+ const sort = entity.sorts?.find((s) => s.column === columnId)
73
124
  return sort?.direction || null
74
125
  }
75
126
 
@@ -136,7 +187,7 @@ export function isSomeSelected(entity) {
136
187
  * @returns {any[]}
137
188
  */
138
189
  function applyFilters(entity, rows) {
139
- if (!Object.keys(entity.filters).length) {
190
+ if (!Object.keys(entity.filters ?? {}).length) {
140
191
  return rows
141
192
  }
142
193
 
@@ -201,7 +252,7 @@ function applySearch(entity, rows) {
201
252
  * @returns {any[]}
202
253
  */
203
254
  function applySorts(entity, rows) {
204
- if (!entity.sorts.length) {
255
+ if (!entity.sorts?.length) {
205
256
  return rows
206
257
  }
207
258
 
@@ -19,6 +19,9 @@ export const DataGrid = {
19
19
  onPagePrev: () => api.notify(`#${id}:pagePrev`),
20
20
  onPageSizeChange: (pageSize) =>
21
21
  api.notify(`#${id}:pageSizeChange`, pageSize),
22
+ onCellChange: (payload) => api.notify(`#${id}:cellChange`, payload),
23
+ onEditsReset: () => api.notify(`#${id}:editsReset`),
24
+ onEditsSave: () => api.notify(`#${id}:editsSave`),
22
25
  onVirtualMount: (containerEl) =>
23
26
  api.notify(`#${id}:virtualMount`, containerEl),
24
27
  onVirtualScroll: (containerEl) =>
@@ -30,6 +33,10 @@ export const DataGrid = {
30
33
  }
31
34
 
32
35
  export const {
36
+ getCellValue,
37
+ getEditCount,
38
+ getEditedRows,
39
+ isCellEdited,
33
40
  getPaginationInfo,
34
41
  getRows,
35
42
  isSomeSelected,
@@ -1,6 +1,7 @@
1
1
  @import "../../controls/input/style.css";
2
2
  @import "../../controls/select/style.css";
3
3
  @import "../../controls/button/style.css";
4
+ @import "../../controls/checkbox/style.css";
4
5
  @import "../../navigation/pagination/style.css";
5
6
 
6
7
  @import "./base";
@@ -10,16 +10,21 @@ import { classMap } from "@inglorious/web/directives/class-map"
10
10
  import { repeat } from "@inglorious/web/directives/repeat"
11
11
  import { when } from "@inglorious/web/directives/when"
12
12
 
13
+ import { Button } from "../../controls/button/index.js"
14
+ import { Checkbox } from "../../controls/checkbox/index.js"
13
15
  import { Input } from "../../controls/input/index.js"
14
16
  import { Select } from "../../controls/select/index.js"
15
17
  import { Pagination } from "../../navigation/pagination/index.js"
16
18
  import { VirtualList } from "../virtual-list/index.js"
17
19
  import { Filters } from "./filters/index.js"
18
20
  import {
21
+ getCellValue,
22
+ getEditCount,
19
23
  getPaginationInfo,
20
24
  getRowKeyValue,
21
25
  getRows,
22
26
  getSortDirection,
27
+ isCellEdited,
23
28
  } from "./helpers.js"
24
29
 
25
30
  const DIVISOR = 2
@@ -27,6 +32,7 @@ const DIVISOR = 2
27
32
  const PRETTY_PAGE = 1
28
33
  const FIRST_PAGE = 0
29
34
  const LAST_PAGE = 1
35
+ const ONE_EDIT = 1
30
36
 
31
37
  export const DataGrid = {
32
38
  /**
@@ -67,7 +73,7 @@ export const DataGrid = {
67
73
  */
68
74
  renderHeader(props) {
69
75
  return html`<div class="iw-data-grid-header">
70
- ${props.search && this.renderToolbar?.(props)}
76
+ ${when(props.search, () => this.renderToolbar?.(props))}
71
77
  <div class="iw-data-grid-header-row">
72
78
  ${repeat(
73
79
  props.columns,
@@ -108,7 +114,10 @@ export const DataGrid = {
108
114
  @click=${() => column.isSortable && props.onSortChange?.(column.id)}
109
115
  class="iw-data-grid-header-title"
110
116
  >
111
- ${column.title} ${getSortIcon(getSortDirection(props, column.id))}
117
+ ${column.title}
118
+ ${when(column.isSortable, () =>
119
+ getSortIcon(getSortDirection(props, column.id)),
120
+ )}
112
121
  </div>
113
122
 
114
123
  ${when(column.isFilterable, () => Filters.render(props, column))}
@@ -225,7 +234,11 @@ export const DataGrid = {
225
234
  props.columns,
226
235
  (column) => column.id,
227
236
  (column, index) =>
228
- this.renderCell?.(props, { cell: row[column.id], index }),
237
+ this.renderCell?.(props, {
238
+ cell: getCellValue(props, row, column.id),
239
+ index,
240
+ row,
241
+ }),
229
242
  )}
230
243
  </div>`
231
244
  },
@@ -235,25 +248,87 @@ export const DataGrid = {
235
248
  * Override to customize per-column cell structure.
236
249
  * @param {DataGridProps} props The data grid props.
237
250
  * @param {object} payload The payload.
238
- * @param {any} payload.cell The cell data.
251
+ * @param {any} payload.cell The cell data, with any unsaved edit.
239
252
  * @param {number} payload.index The column index.
253
+ * @param {Row} payload.row The row the cell is in.
240
254
  * @returns {TemplateResult} The rendered cell.
241
255
  */
242
- renderCell(props, { cell, index }) {
256
+ renderCell(props, { cell, index, row }) {
243
257
  const column = props.columns[index]
258
+ const isEditable = Boolean(column.isEditable && row)
244
259
 
260
+ // Typing in a cell's editor is not picking its row.
245
261
  return html`<div
246
262
  class="iw-data-grid-cell ${classMap({
247
263
  "iw-data-grid-cell-number": column.type === "number",
248
264
  "iw-data-grid-cell-date": column.type === "date",
249
265
  "iw-data-grid-cell-boolean": column.type === "boolean",
266
+ "iw-data-grid-cell-editable": isEditable,
267
+ "iw-data-grid-cell-edited":
268
+ isEditable && isCellEdited(props, row, column.id),
250
269
  })}"
251
270
  style=${getColumnStyle(column)}
271
+ @click=${isEditable ? (event) => event.stopPropagation() : undefined}
252
272
  >
253
- ${this.renderValue?.(props, { value: cell, column, index })}
273
+ ${when(
274
+ isEditable,
275
+ () => this.renderEditor?.(props, { value: cell, column, row, index }),
276
+ () => this.renderValue?.(props, { value: cell, column, index }),
277
+ )}
254
278
  </div>`
255
279
  },
256
280
 
281
+ /**
282
+ * Renders the field that edits a cell of an `isEditable` column: a
283
+ * drop-down when the column has `options`, a checkbox for booleans, a date
284
+ * or number field for those types, and a text field otherwise.
285
+ * Override to edit some columns differently.
286
+ * @param {DataGridProps} props The data grid props.
287
+ * @param {object} payload The payload.
288
+ * @param {any} payload.value The cell's value, with any unsaved edit.
289
+ * @param {Column} payload.column The column definition.
290
+ * @param {Row} payload.row The row the cell is in.
291
+ * @returns {TemplateResult} The rendered editor.
292
+ */
293
+ renderEditor(props, { value, column, row }) {
294
+ const change = (next) =>
295
+ props.onCellChange?.({
296
+ rowId: getRowKeyValue(props, row),
297
+ columnId: column.id,
298
+ value: next,
299
+ })
300
+
301
+ if (column.options) {
302
+ return Select.render({
303
+ size: "sm",
304
+ ariaLabel: column.title,
305
+ value: value ?? "",
306
+ options: column.options,
307
+ isFullWidth: true,
308
+ onChange: change,
309
+ })
310
+ }
311
+
312
+ if (column.type === "boolean") {
313
+ return Checkbox.render({
314
+ "aria-label": column.title,
315
+ isChecked: Boolean(value),
316
+ onChange: change,
317
+ })
318
+ }
319
+
320
+ const isNumber = column.type === "number"
321
+ return Input.render({
322
+ size: "sm",
323
+ ariaLabel: column.title,
324
+ inputType: isNumber ? "number" : column.type === "date" ? "date" : "text",
325
+ value: value == null ? "" : String(value),
326
+ isFullWidth: true,
327
+ onChange: (text) =>
328
+ change(isNumber ? (text === "" ? null : Number(text)) : text),
329
+ })
330
+ },
331
+
257
332
  /**
258
333
  * Renders the raw cell value for a column.
259
334
  * Override to format values or inject custom content.
@@ -274,22 +349,57 @@ export const DataGrid = {
274
349
  */
275
350
  renderFooter(props) {
276
351
  const pagination = getPaginationInfo(props)
352
+ const editCount = getEditCount(props)
277
353
 
278
- if (!pagination) {
354
+ if (!pagination && !editCount) {
279
355
  return null
280
356
  }
281
357
 
282
358
  return html`<div class="iw-data-grid-footer">
283
- <div class="iw-data-grid-footer-row">
284
- <div>
285
- ${pagination.start + PRETTY_PAGE} to ${pagination.end} of
286
- ${pagination.totalRows} entries
287
- </div>
288
-
289
- ${this.renderPagination?.(props, pagination)}
290
- ${when(pagination.pageSizes, () =>
291
- this.renderPageSize?.(props, pagination),
292
- )}
359
+ ${when(editCount, () => this.renderEdits?.(props, editCount))}
360
+ ${when(
361
+ pagination,
362
+ () =>
363
+ html`<div class="iw-data-grid-footer-row">
364
+ <div>
365
+ ${pagination.start + PRETTY_PAGE} to ${pagination.end} of
366
+ ${pagination.totalRows} entries
367
+ </div>
368
+
369
+ ${this.renderPagination?.(props, pagination)}
370
+ ${when(pagination.pageSizes, () =>
371
+ this.renderPageSize?.(props, pagination),
372
+ )}
373
+ </div>`,
374
+ )}
375
+ </div>`
376
+ },
377
+
378
+ /**
379
+ * Renders the bar that counts the unsaved edits, with buttons to reset or
380
+ * save them. Shown only while there are edits.
381
+ * @param {DataGridProps} props The data grid props.
382
+ * @param {number} editCount How many cells have unsaved edits.
383
+ * @returns {TemplateResult} The rendered bar.
384
+ */
385
+ renderEdits(props, editCount) {
386
+ return html`<div class="iw-data-grid-footer-row iw-data-grid-edits">
387
+ <div role="status">
388
+ ${editCount} ${editCount === ONE_EDIT ? "change" : "changes"}
389
+ </div>
390
+ <div class="iw-data-grid-edits-actions">
391
+ ${Button.render({
392
+ children: "Reset",
393
+ variant: "ghost",
394
+ size: "sm",
395
+ onClick: () => props.onEditsReset?.(),
396
+ })}
397
+ ${Button.render({
398
+ children: props.saveLabel ?? "Save changes",
399
+ color: "primary",
400
+ size: "sm",
401
+ onClick: () => props.onEditsSave?.(),
402
+ })}
293
403
  </div>
294
404
  </div>`
295
405
  },
@@ -145,4 +145,21 @@
145
145
  );
146
146
  }
147
147
  }
148
+
149
+ /* An editor fills its cell, which keeps less padding around it. */
150
+ .iw-data-grid-cell-editable {
151
+ padding: var(--iw-space-2);
152
+ }
153
+
154
+ .iw-data-grid-cell-edited {
155
+ background: color-mix(in srgb, var(--iw-color-warning) 12%, transparent);
156
+ }
157
+
158
+ .iw-data-grid-edits {
159
+ gap: var(--iw-space-3);
160
+
161
+ & + .iw-data-grid-footer-row {
162
+ margin-block-start: var(--iw-space-3);
163
+ }
164
+ }
148
165
  }
@@ -41,20 +41,20 @@ export const PrimitiveSection = {
41
41
  variant: "h4",
42
42
  children: showEmpty ? "Choose a Primitive" : name,
43
43
  }),
44
- category
45
- ? Typography.render({
46
- variant: "body2",
47
- color: "secondary",
48
- children: `Category: ${category}`,
49
- })
50
- : "",
51
- summary
52
- ? Typography.render({
53
- variant: "body1",
54
- color: "secondary",
55
- children: summary,
56
- })
57
- : "",
44
+ when(category, () =>
45
+ Typography.render({
46
+ variant: "body2",
47
+ color: "secondary",
48
+ children: `Category: ${category}`,
49
+ }),
50
+ ),
51
+ when(summary, () =>
52
+ Typography.render({
53
+ variant: "body1",
54
+ color: "secondary",
55
+ children: summary,
56
+ }),
57
+ ),
58
58
  ],
59
59
  }),
60
60
  Card.render({
@@ -99,61 +99,61 @@ export const PrimitiveSection = {
99
99
  `,
100
100
  }),
101
101
  }),
102
- showEmpty
103
- ? ""
104
- : Card.render({
105
- padding: "xl",
106
- className: "iw-primitive-example",
107
- children: Flex.render({
108
- direction: "column",
109
- gap: "md",
110
- children: [
111
- Typography.render({
112
- variant: "h6",
113
- children: "Example",
114
- }),
115
- Grid.render({
116
- minColumnWidth: "18rem",
117
- gap: "lg",
118
- children: [
119
- Flex.render({
120
- direction: "column",
121
- gap: "sm",
122
- children: [
123
- Typography.render({
124
- variant: "body2",
125
- color: "secondary",
126
- children: "Code",
127
- }),
128
- html`<pre class="iw-primitive-code"><code>${
129
- exampleCode ?? "// Example coming soon."
130
- }</code></pre>`,
131
- ],
132
- }),
133
- Flex.render({
134
- direction: "column",
135
- gap: "sm",
136
- children: [
137
- Typography.render({
138
- variant: "body2",
139
- color: "secondary",
140
- children: "Result",
141
- }),
142
- html`<div class="iw-primitive-preview">
143
- ${
144
- examplePreview ??
145
- html`<div class="iw-primitive-preview-empty">
146
- Preview coming soon.
147
- </div>`
148
- }
149
- </div>`,
150
- ],
151
- }),
152
- ],
153
- }),
154
- ],
155
- }),
102
+ when(!showEmpty, () =>
103
+ Card.render({
104
+ padding: "xl",
105
+ className: "iw-primitive-example",
106
+ children: Flex.render({
107
+ direction: "column",
108
+ gap: "md",
109
+ children: [
110
+ Typography.render({
111
+ variant: "h6",
112
+ children: "Example",
113
+ }),
114
+ Grid.render({
115
+ minColumnWidth: "18rem",
116
+ gap: "lg",
117
+ children: [
118
+ Flex.render({
119
+ direction: "column",
120
+ gap: "sm",
121
+ children: [
122
+ Typography.render({
123
+ variant: "body2",
124
+ color: "secondary",
125
+ children: "Code",
126
+ }),
127
+ html`<pre class="iw-primitive-code"><code>${
128
+ exampleCode ?? "// Example coming soon."
129
+ }</code></pre>`,
130
+ ],
131
+ }),
132
+ Flex.render({
133
+ direction: "column",
134
+ gap: "sm",
135
+ children: [
136
+ Typography.render({
137
+ variant: "body2",
138
+ color: "secondary",
139
+ children: "Result",
140
+ }),
141
+ html`<div class="iw-primitive-preview">
142
+ ${
143
+ examplePreview ??
144
+ html`<div class="iw-primitive-preview-empty">
145
+ Preview coming soon.
146
+ </div>`
147
+ }
148
+ </div>`,
149
+ ],
150
+ }),
151
+ ],
152
+ }),
153
+ ],
156
154
  }),
155
+ }),
156
+ ),
157
157
  ],
158
158
  }),
159
159
  })
@@ -20,6 +20,8 @@ export interface InputProps {
20
20
  value?: string
21
21
  placeholder?: string
22
22
  label?: string
23
+ /** Names the field for assistive tech when it has no visible `label`. */
24
+ ariaLabel?: string
23
25
  hint?: string
24
26
  error?: string
25
27
  size?: "sm" | "md" | "lg"
@@ -42,6 +42,13 @@ export interface Column {
42
42
  * - number / CSS length string: fixed width
43
43
  */
44
44
  width?: number | string
45
+ /**
46
+ * Lets the user edit the column's cells: a text, number or date field by
47
+ * its `type`, a checkbox for booleans, or a drop-down with `options`.
48
+ */
49
+ isEditable?: boolean
50
+ /** The values an editable cell can take, picked from a drop-down. */
51
+ options?: SelectOption[]
45
52
  formatter?: string | ((value: any) => any)
46
53
  sortFn?: (a: Row, b: Row) => number
47
54
  filterFn?: (row: Row, filterValue: any) => boolean
@@ -52,7 +59,7 @@ export interface Column {
52
59
  export interface DataGridProps<T extends Row = Row> {
53
60
  id?: string
54
61
  type: string
55
- data: T[]
62
+ rows: T[]
56
63
  columns: Column[]
57
64
  sorts: { column: string; direction: "asc" | "desc" }[]
58
65
  filters: Record<string, any>
@@ -65,6 +72,20 @@ export interface DataGridProps<T extends Row = Row> {
65
72
  pagination?: { page: number; pageSize: number; pageSizes: number[] } | null
66
73
  rowId?: string
67
74
  isMultiSelect: boolean
75
+ /**
76
+ * The user's unsaved edits, by row key and column id, shown over the rows
77
+ * until saved. Sorting, filtering and search go by the saved values.
78
+ */
79
+ edits?: Record<string | number, Record<string, any>>
80
+ /** The label of the button that saves the edits. Defaults to "Save changes". */
81
+ saveLabel?: string
82
+ onCellChange?: (payload: {
83
+ rowId: string | number
84
+ columnId: string
85
+ value: any
86
+ }) => void
87
+ onEditsReset?: () => void
88
+ onEditsSave?: () => void
68
89
  onSortChange?: (columnId: string) => void
69
90
  onFilterChange?: (payload: { columnId: string; value: any }) => void
70
91
  onSearchChange?: (value: string) => void
@@ -122,6 +143,15 @@ export type DataGridType = {
122
143
  rowsToggleAll(entity: DataGridProps): void
123
144
  rowsSelectAll(entity: DataGridProps): void
124
145
  selectionClear(entity: DataGridProps): void
146
+ /** Records an edit of a cell; an edit back to the saved value drops it. */
147
+ cellChange(
148
+ entity: DataGridProps,
149
+ payload: { rowId: string | number; columnId: string; value: any },
150
+ ): void
151
+ /** Drops every unsaved edit. */
152
+ editsReset(entity: DataGridProps): void
153
+ /** Writes the unsaved edits into the rows. */
154
+ editsSave(entity: DataGridProps): void
125
155
  virtualMount(entity: DataGridProps, containerEl: HTMLElement): void
126
156
  virtualScroll(entity: DataGridProps, containerEl: HTMLElement): void
127
157
  render(entity: DataGridProps, api: Api): TemplateResult
@@ -141,9 +171,15 @@ export type DataGridType = {
141
171
  ): TemplateResult
142
172
  renderCell(
143
173
  entity: DataGridProps,
144
- payload: { cell: any; index: number },
174
+ payload: { cell: any; index: number; row?: Row },
145
175
  api: Api,
146
176
  ): TemplateResult
177
+ renderEditor(
178
+ entity: DataGridProps,
179
+ payload: { value: any; column: Column; row: Row; index: number },
180
+ api: Api,
181
+ ): TemplateResult
182
+ renderEdits(entity: DataGridProps, editCount: number): TemplateResult
147
183
  renderValue(
148
184
  entity: DataGridProps,
149
185
  payload: { value: any; column: Column; index: number },
@@ -181,3 +217,21 @@ export declare function getSortDirection(
181
217
  ): "asc" | "desc" | null
182
218
  export declare function isAllSelected(entity: DataGridProps): boolean
183
219
  export declare function isSomeSelected(entity: DataGridProps): boolean
220
+ /** A cell's value as shown: its unsaved edit, or the row's own. */
221
+ export declare function getCellValue(
222
+ entity: DataGridProps,
223
+ row: Row,
224
+ columnId: string,
225
+ ): any
226
+ /** Whether a cell has an unsaved edit. */
227
+ export declare function isCellEdited(
228
+ entity: DataGridProps,
229
+ row: Row,
230
+ columnId: string,
231
+ ): boolean
232
+ /** How many cells have unsaved edits. */
233
+ export declare function getEditCount(entity: DataGridProps): number
234
+ /** Every row with its unsaved edits applied. */
235
+ export declare function getEditedRows<T extends Row = Row>(
236
+ entity: DataGridProps<T>,
237
+ ): T[]