@craftainer/crud-views 0.1.0 → 0.2.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/README.md +22 -0
- package/dist/index.d.mts +7 -3
- package/dist/index.mjs +62 -53
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,6 +52,28 @@ const client = createClient({ baseUrl: "https://api.example.com" });
|
|
|
52
52
|
</CrudProvider>;
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
### Inline edit and ETags
|
|
56
|
+
|
|
57
|
+
The list endpoint returns no ETags, so the table's inline edit takes
|
|
58
|
+
`ifMatch(row)` from the app. It may return a string, `undefined` (the PATCH
|
|
59
|
+
goes without `If-Match`) or a promise of either, the same shape as
|
|
60
|
+
`ResourceForm`'s `etag`. `fetchEtagById` stands for the app's own lookup, for
|
|
61
|
+
example fetching the row through the SDK and reading its cached ETag:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
<ResourceList
|
|
65
|
+
resource="heroes"
|
|
66
|
+
editable
|
|
67
|
+
ifMatch={async (row) =>
|
|
68
|
+
(await fetchEtagById(row["id"] as number)) ?? undefined
|
|
69
|
+
}
|
|
70
|
+
/>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The edit waits for the promise and stays in a saving state (the cell stays
|
|
74
|
+
in edit mode and the table is `aria-busy`); a rejection sends nothing and shows "Could not save: ..." like
|
|
75
|
+
any other save error.
|
|
76
|
+
|
|
55
77
|
## Development
|
|
56
78
|
|
|
57
79
|
Open the repository in the devcontainer, then run
|
package/dist/index.d.mts
CHANGED
|
@@ -958,9 +958,12 @@ interface DataTableListProps {
|
|
|
958
958
|
* The ETag of a row, sent as `If-Match` on an inline edit (a stale one fails
|
|
959
959
|
* with 412 and the view reloads the row). The list endpoint returns no ETags,
|
|
960
960
|
* so the app supplies them (for example from its SDK cache); without this
|
|
961
|
-
* prop the edit is unconditional.
|
|
961
|
+
* prop the edit is unconditional. May return a promise (same shape as
|
|
962
|
+
* `ResourceFormProps.etag`): the edit waits for it, stays in a saving state
|
|
963
|
+
* meanwhile, and is not sent if it rejects (the error is shown like any save
|
|
964
|
+
* error). `undefined` sends the PATCH without `If-Match`.
|
|
962
965
|
*/
|
|
963
|
-
readonly ifMatch?: ((row: Row$1) => string | undefined) | undefined;
|
|
966
|
+
readonly ifMatch?: ((row: Row$1) => string | undefined | Promise<string | undefined>) | undefined;
|
|
964
967
|
readonly pageSizeOptions?: readonly number[] | undefined;
|
|
965
968
|
readonly onRowClick?: ((row: Row$1) => void) | undefined;
|
|
966
969
|
readonly renderers?: RendererOverrides | undefined;
|
|
@@ -1003,7 +1006,8 @@ interface ResourceListProps {
|
|
|
1003
1006
|
readonly titleField?: string | undefined;
|
|
1004
1007
|
/** Table only: edit cells in place. */
|
|
1005
1008
|
readonly editable?: boolean | undefined;
|
|
1006
|
-
|
|
1009
|
+
/** Table only: a row's ETag for inline edits; may be async. See `DataTableListProps.ifMatch`. */
|
|
1010
|
+
readonly ifMatch?: ((row: Row) => string | undefined | Promise<string | undefined>) | undefined;
|
|
1007
1011
|
readonly sortableOnly?: boolean | undefined;
|
|
1008
1012
|
/** Show the sort dropdown. Default: for the card variants, which have no header to click. */
|
|
1009
1013
|
readonly sortDropdown?: boolean | undefined;
|
package/dist/index.mjs
CHANGED
|
@@ -4016,6 +4016,7 @@ function DataTableList({ ready, state, columns, sortableOnly = false, configurab
|
|
|
4016
4016
|
const update = ready.hooks.useUpdate();
|
|
4017
4017
|
const [hidden, setHidden] = useState(/* @__PURE__ */ new Set());
|
|
4018
4018
|
const [notice, setNotice] = useState(null);
|
|
4019
|
+
const [saving, setSaving] = useState(0);
|
|
4019
4020
|
const shown = useMemo(() => resolveColumns(ready, columns).filter((c) => !sortableOnly || c.field.sortable), [
|
|
4020
4021
|
ready,
|
|
4021
4022
|
columns,
|
|
@@ -4072,14 +4073,19 @@ function DataTableList({ ready, state, columns, sortableOnly = false, configurab
|
|
|
4072
4073
|
if (canEdit(field, editable, idField) && value !== void 0 && !same(value, previous[field.name])) patch[field.name] = value;
|
|
4073
4074
|
}
|
|
4074
4075
|
if (Object.keys(patch).length === 0) return previous;
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4076
|
+
setSaving((n) => n + 1);
|
|
4077
|
+
try {
|
|
4078
|
+
const tag = await ifMatch?.(previous);
|
|
4079
|
+
const saved = await update.mutateAsync({
|
|
4080
|
+
id: previous[idField],
|
|
4081
|
+
patch,
|
|
4082
|
+
...tag === void 0 ? {} : { ifMatch: tag }
|
|
4083
|
+
});
|
|
4084
|
+
setNotice(null);
|
|
4085
|
+
return saved;
|
|
4086
|
+
} finally {
|
|
4087
|
+
setSaving((n) => n - 1);
|
|
4088
|
+
}
|
|
4083
4089
|
}
|
|
4084
4090
|
function saveFailed(error) {
|
|
4085
4091
|
if (error instanceof PreconditionFailed) {
|
|
@@ -4092,51 +4098,54 @@ function DataTableList({ ready, state, columns, sortableOnly = false, configurab
|
|
|
4092
4098
|
error: list.error,
|
|
4093
4099
|
onRetry: () => void list.refetch()
|
|
4094
4100
|
});
|
|
4095
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4101
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
4102
|
+
"aria-busy": saving > 0,
|
|
4103
|
+
children: [
|
|
4104
|
+
configurableColumns && /* @__PURE__ */ jsx(ColumnsMenu, {
|
|
4105
|
+
columns: shown,
|
|
4106
|
+
hidden,
|
|
4107
|
+
onChange: setHidden
|
|
4108
|
+
}),
|
|
4109
|
+
notice !== null && /* @__PURE__ */ jsx(Alert, {
|
|
4110
|
+
severity: "warning",
|
|
4111
|
+
onClose: () => setNotice(null),
|
|
4112
|
+
sx: { mb: 1 },
|
|
4113
|
+
children: notice
|
|
4114
|
+
}),
|
|
4115
|
+
/* @__PURE__ */ jsx(DataGrid, {
|
|
4116
|
+
"aria-label": resource.title,
|
|
4117
|
+
autoHeight: true,
|
|
4118
|
+
rows,
|
|
4119
|
+
columns: gridColumns,
|
|
4120
|
+
getRowId: (row) => row[idField],
|
|
4121
|
+
loading: list.isFetching,
|
|
4122
|
+
columnVisibilityModel: visibility,
|
|
4123
|
+
paginationMode: "server",
|
|
4124
|
+
sortingMode: "server",
|
|
4125
|
+
filterMode: "server",
|
|
4126
|
+
rowCount,
|
|
4127
|
+
localeText: { paginationDisplayedRows: ({ from, to }) => hasNextPage ? `${from}–${to}` : `${from}–${to} of ${rowCount}` },
|
|
4128
|
+
paginationModel: {
|
|
4129
|
+
page: page - 1,
|
|
4130
|
+
pageSize
|
|
4131
|
+
},
|
|
4132
|
+
pageSizeOptions: sizes,
|
|
4133
|
+
onPaginationModelChange: (model) => model.pageSize === pageSize ? state.setPage(model.page + 1) : state.setPageSize(model.pageSize),
|
|
4134
|
+
sortModel,
|
|
4135
|
+
onSortModelChange: (model) => state.setSort(model.map((m) => ({
|
|
4136
|
+
field: m.field,
|
|
4137
|
+
desc: m.sort === "desc"
|
|
4138
|
+
}))),
|
|
4139
|
+
disableColumnFilter: true,
|
|
4140
|
+
disableColumnMenu: true,
|
|
4141
|
+
disableRowSelectionOnClick: true,
|
|
4142
|
+
processRowUpdate: save,
|
|
4143
|
+
onProcessRowUpdateError: saveFailed,
|
|
4144
|
+
onRowClick: (p) => onRowClick?.(p.row),
|
|
4145
|
+
...gridProps
|
|
4146
|
+
})
|
|
4147
|
+
]
|
|
4148
|
+
});
|
|
4140
4149
|
}
|
|
4141
4150
|
//#endregion
|
|
4142
4151
|
//#region src/list/LoadMoreList.tsx
|