@rebasepro/cms 0.22.0 → 0.24.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.
Files changed (90) hide show
  1. package/dist/{CollectionEditorDialog-CUtfS_49.js → CollectionEditorDialog-p5-I-Apu.js} +181 -97
  2. package/dist/CollectionEditorDialog-p5-I-Apu.js.map +1 -0
  3. package/dist/{PropertyEditView-BpEbC0Q2.js → PropertyEditView-DDB_X-00.js} +55 -23
  4. package/dist/PropertyEditView-DDB_X-00.js.map +1 -0
  5. package/dist/{RouterCollectionsStudioView-EK8Knq5I.js → RouterCollectionsStudioView-SGBV4CMg.js} +34 -10
  6. package/dist/RouterCollectionsStudioView-SGBV4CMg.js.map +1 -0
  7. package/dist/{SchemaChangeDialog-BeP0A4MJ.js → SchemaChangeDialog-CcFSXvkK.js} +33 -12
  8. package/dist/SchemaChangeDialog-CcFSXvkK.js.map +1 -0
  9. package/dist/collection_editor/_cms_internals.d.ts +3 -1
  10. package/dist/collection_editor/liveSchemaClient.d.ts +38 -18
  11. package/dist/collection_editor/relation_kinds.d.ts +11 -0
  12. package/dist/collection_editor/serializable_types.d.ts +2 -0
  13. package/dist/collection_editor/types/config_controller.d.ts +11 -0
  14. package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +8 -1
  15. package/dist/collection_editor/useLiveSchemaEditing.d.ts +5 -5
  16. package/dist/collection_editor/useLocalCollectionsConfigController.d.ts +7 -0
  17. package/dist/collection_editor_ui.js +5 -5
  18. package/dist/components/ClearFilterSortButton.d.ts +7 -1
  19. package/dist/components/CollectionTableBinding/fields/CompactEntityCellField.d.ts +12 -13
  20. package/dist/components/CollectionTableBinding/fields/TableMultipleRelationField.d.ts +6 -3
  21. package/dist/components/CollectionTableBinding/fields/TableReferenceField.d.ts +8 -1
  22. package/dist/components/CollectionTableBinding/fields/TableRelationField.d.ts +8 -1
  23. package/dist/components/CollectionTableBinding/fields/TableRelationSelectorField.d.ts +13 -3
  24. package/dist/components/CollectionTableBinding/fields/VirtualTableSelect.d.ts +10 -0
  25. package/dist/components/CollectionTableBinding/fields/VirtualTableUserSelect.d.ts +10 -0
  26. package/dist/components/CollectionTableBinding/internal/EntityTableCell.d.ts +21 -0
  27. package/dist/components/CollectionTableBinding/internal/TableCellOpener.d.ts +45 -0
  28. package/dist/components/CollectionTableBinding/table_bindings.d.ts +24 -0
  29. package/dist/components/CollectionViewBinding/CollectionViewActions.d.ts +6 -1
  30. package/dist/components/CollectionViewBinding/CollectionViewBinding.d.ts +12 -3
  31. package/dist/components/CollectionViewBinding/FiltersDialog.d.ts +5 -0
  32. package/dist/components/EditViewBinding.d.ts +7 -1
  33. package/dist/components/EntityIdentityBar.d.ts +29 -4
  34. package/dist/components/EntityPreviewBinding.d.ts +8 -1
  35. package/dist/components/EntityPreviewNesting.d.ts +15 -0
  36. package/dist/components/NavigationLoadingErrorView.d.ts +16 -0
  37. package/dist/components/PickerTrigger.d.ts +17 -0
  38. package/dist/components/ReferenceTable/SelectionTableBinding.d.ts +12 -0
  39. package/dist/components/RelationSelector.d.ts +24 -7
  40. package/dist/components/UserSelector.d.ts +8 -1
  41. package/dist/components/common/ActionMenuLabel.d.ts +15 -0
  42. package/dist/components/common/default_entity_actions.d.ts +37 -0
  43. package/dist/data_export/export/additional_fields.d.ts +13 -0
  44. package/dist/data_export/export/export.d.ts +6 -0
  45. package/dist/data_import/components/ImportConversionProblems.d.ts +11 -0
  46. package/dist/data_import/components/index.d.ts +1 -0
  47. package/dist/data_import/utils/csv.d.ts +3 -2
  48. package/dist/data_import/utils/data.d.ts +81 -4
  49. package/dist/data_import/utils/id_column.d.ts +16 -0
  50. package/dist/data_import/utils/import_columns.d.ts +48 -0
  51. package/dist/editor/editor.d.ts +7 -1
  52. package/dist/editor/extensions/Image.d.ts +22 -1
  53. package/dist/editor/hooks/useProseMirror.d.ts +4 -1
  54. package/dist/editor.js +69 -7
  55. package/dist/editor.js.map +1 -1
  56. package/dist/{export-LvIpE7sE.js → export-DCHuPD1a.js} +65 -211
  57. package/dist/export-DCHuPD1a.js.map +1 -0
  58. package/dist/form/form_utils.d.ts +37 -0
  59. package/dist/form/resolve_form_properties.d.ts +26 -0
  60. package/dist/form/validation.d.ts +13 -0
  61. package/dist/{history-DG6Ln7CV.js → history-CBd6SBRs.js} +3 -2
  62. package/dist/{history-DG6Ln7CV.js.map → history-CBd6SBRs.js.map} +1 -1
  63. package/dist/hooks/useEntityDisplay.d.ts +4 -5
  64. package/dist/hooks/useRecordActions.d.ts +28 -6
  65. package/dist/hooks/useReferencePaths.d.ts +21 -0
  66. package/dist/hooks/useStorageReferenceResolver.d.ts +23 -0
  67. package/dist/{import-Cxnmpyjl.js → import-DjQoRrIU.js} +35 -54
  68. package/dist/import-DjQoRrIU.js.map +1 -0
  69. package/dist/index.js +191 -117
  70. package/dist/index.js.map +1 -1
  71. package/dist/preview/components/StorageMarkdown.d.ts +10 -0
  72. package/dist/preview/util.d.ts +29 -0
  73. package/dist/util/caller_scopes.d.ts +15 -0
  74. package/dist/util/entity_actions.d.ts +37 -1
  75. package/dist/util/fit_inline_actions.d.ts +20 -0
  76. package/dist/util/navigation_utils.d.ts +14 -2
  77. package/dist/util/view_mode.d.ts +12 -18
  78. package/dist/{util-CyIcgnv2.js → util-BRvKFMLa.js} +5775 -3665
  79. package/dist/util-BRvKFMLa.js.map +1 -0
  80. package/dist/{util-DB31IoJE.js → util-D7ebttKT.js} +39 -2
  81. package/dist/util-D7ebttKT.js.map +1 -0
  82. package/package.json +9 -9
  83. package/dist/CollectionEditorDialog-CUtfS_49.js.map +0 -1
  84. package/dist/PropertyEditView-BpEbC0Q2.js.map +0 -1
  85. package/dist/RouterCollectionsStudioView-EK8Knq5I.js.map +0 -1
  86. package/dist/SchemaChangeDialog-BeP0A4MJ.js.map +0 -1
  87. package/dist/export-LvIpE7sE.js.map +0 -1
  88. package/dist/import-Cxnmpyjl.js.map +0 -1
  89. package/dist/util-CyIcgnv2.js.map +0 -1
  90. package/dist/util-DB31IoJE.js.map +0 -1
@@ -1,183 +1,10 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { bn as useAdminContext, dn as resolveSelection, fn as selectionQueryToFindParams, mn as walkEntityPages } from "./util-CyIcgnv2.js";
2
+ import { $t as downloadDataAsCsv, On as resolveSelection, Qt as downloadBlob, Rn as useAdminContext, an as unescapeCsvFormula, en as downloadEntitiesExport, in as getEntityJsonExportableData, jn as walkEntityPages, kn as selectionQueryToFindParams, nn as escapeCsvFormula, rn as getEntityCSVExportableData, tn as entryToCSVRow } from "./util-BRvKFMLa.js";
3
3
  import React, { useCallback, useMemo } from "react";
4
4
  import { Alert, BooleanSwitchWithLabel, Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, DownloadIcon, IconButton, Label, RadioGroup, RadioGroupItem, Tooltip, iconSize } from "@rebasepro/ui";
5
5
  import { getDefaultValuesFor } from "@rebasepro/common";
6
6
  import { useData, useTranslation } from "@rebasepro/app";
7
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
- import { getArrayValuesCount, getValueInPath } from "@rebasepro/utils";
9
- //#region src/data_export/export/export.ts
10
- function downloadEntitiesExport({ data, additionalData, properties, propertiesOrder, name, flattenArrays, additionalHeaders, exportType, dateExportType }) {
11
- console.debug("Downloading export", {
12
- dataLength: data.length,
13
- properties,
14
- exportType,
15
- dateExportType
16
- });
17
- if (exportType === "csv") {
18
- const headers = getExportHeaders(properties, propertiesOrder, additionalHeaders, flattenArrays ? getArrayValuesCount(data.map((d) => d.values)) : {});
19
- const exportableData = getEntityCSVExportableData(data, additionalData, properties, headers, dateExportType);
20
- downloadBlob([entryToCSVRow(headers.map((h) => h.label)), ...exportableData.map((entry) => entryToCSVRow(entry))], `${name}.csv`, "text/csv");
21
- } else {
22
- const exportableData = getEntityJsonExportableData(data, additionalData, properties, dateExportType);
23
- downloadBlob([JSON.stringify(exportableData, null, 2)], `${name}.json`, "application/json");
24
- }
25
- }
26
- function getEntityCSVExportableData(data, additionalData, properties, headers, dateExportType) {
27
- const mergedData = data.map((e) => ({
28
- id: e.id,
29
- ...processValuesForExport(e.values, properties, "csv", dateExportType)
30
- }));
31
- if (additionalData) additionalData.forEach((additional, index) => {
32
- mergedData[index] = {
33
- ...mergedData[index],
34
- ...additional
35
- };
36
- });
37
- return mergedData && mergedData.map((entry) => {
38
- return headers.map((header) => getValueInPath(entry, header.key));
39
- });
40
- }
41
- function getEntityJsonExportableData(data, additionalData, properties, dateExportType) {
42
- const mergedData = data.map((e) => ({
43
- id: e.id,
44
- ...processValuesForExport(e.values, properties, "json", dateExportType)
45
- }));
46
- if (additionalData) additionalData.forEach((additional, index) => {
47
- mergedData[index] = {
48
- ...mergedData[index],
49
- ...additional
50
- };
51
- });
52
- return mergedData;
53
- }
54
- function getExportHeaders(properties, propertiesOrder, additionalHeaders, arrayValuesCount) {
55
- const headers = [{
56
- label: "id",
57
- key: "id"
58
- }, ...(propertiesOrder ?? Object.keys(properties)).flatMap((childKey) => {
59
- const property = properties[childKey];
60
- if (!property) {
61
- console.warn("Property not found", childKey, properties);
62
- return [];
63
- }
64
- if (arrayValuesCount && arrayValuesCount[childKey] > 1) return Array.from({ length: arrayValuesCount[childKey] }, (_, i) => getHeaders(property, `${childKey}[${i}]`, "")).flat();
65
- else return getHeaders(property, childKey, "");
66
- })];
67
- if (additionalHeaders) headers.push(...additionalHeaders.map((h) => ({
68
- label: h,
69
- key: h
70
- })));
71
- return headers;
72
- }
73
- /**
74
- * Get headers for property. There could be more than one header per property
75
- * @param property
76
- * @param propertyKey
77
- * @param prefix
78
- */
79
- function getHeaders(property, propertyKey, prefix = "") {
80
- const currentKey = prefix ? `${prefix}.${propertyKey}` : propertyKey;
81
- if (property.type === "map" && property.properties) return Object.entries(property.properties).map(([childKey, p]) => getHeaders(p, childKey, currentKey)).flat();
82
- else return [{
83
- label: currentKey,
84
- key: currentKey
85
- }];
86
- }
87
- /**
88
- * Read whatever a `date` column actually arrived as into a `Date`.
89
- *
90
- * The driver/WebSocket path revives `{ __type: "date" }` into a `Date`; the REST
91
- * path — which is the one the admin is on — leaves an ISO string, and an epoch
92
- * number is what a re-imported export carries. Returns `undefined` for anything
93
- * that is not a date, so the caller can pass the raw value through.
94
- */
95
- function toExportableDate(inputValue) {
96
- if (inputValue instanceof Date) return Number.isNaN(inputValue.getTime()) ? void 0 : inputValue;
97
- if (typeof inputValue === "number" && Number.isFinite(inputValue)) return new Date(inputValue);
98
- if (typeof inputValue === "string" && inputValue.trim() !== "") {
99
- const parsed = new Date(inputValue);
100
- return Number.isNaN(parsed.getTime()) ? void 0 : parsed;
101
- }
102
- }
103
- function processValueForExport(inputValue, property, exportType, dateExportType) {
104
- let value;
105
- if (property.type === "map" && property.properties) value = processValuesForExport(inputValue, property.properties, exportType, dateExportType);
106
- else if (property.type === "array") if (property.of && Array.isArray(inputValue)) if (Array.isArray(property.of)) value = property.of.map((p, i) => processValueForExport(inputValue[i], p, exportType, dateExportType));
107
- else if (property.of.type === "map") value = exportType === "csv" ? inputValue.map((e) => JSON.stringify(e)) : inputValue.map((e) => processValueForExport(e, property.of, exportType, dateExportType));
108
- else value = inputValue.map((e) => processValueForExport(e, property.of, exportType, dateExportType));
109
- else value = inputValue;
110
- else if (property.type === "reference" && inputValue && typeof inputValue === "object" && "isEntityReference" in inputValue && typeof inputValue.isEntityReference === "function" && inputValue.isEntityReference()) {
111
- const ref = inputValue ? inputValue : void 0;
112
- value = ref ? ref.fullPath : null;
113
- } else if (property.type === "date") {
114
- const date = toExportableDate(inputValue);
115
- value = date ? dateExportType === "timestamp" ? date.getTime() : date.toISOString() : inputValue;
116
- } else value = inputValue;
117
- return value;
118
- }
119
- function processValuesForExport(inputValues, properties, exportType, dateExportType) {
120
- const updatedValues = Object.entries(properties).map(([key, property]) => {
121
- const updatedValue = processValueForExport(inputValues && inputValues[key], property, exportType, dateExportType);
122
- if (updatedValue === void 0) return {};
123
- return { [key]: updatedValue };
124
- }).reduce((a, b) => ({
125
- ...a,
126
- ...b
127
- }), {});
128
- return {
129
- ...inputValues,
130
- ...updatedValues
131
- };
132
- }
133
- /**
134
- * Characters that make a spreadsheet read a cell as a formula rather than text.
135
- *
136
- * Quoting is CSV *escaping* and is not enough: Excel, LibreOffice Calc and
137
- * Sheets strip the CSV quoting at parse time and then evaluate a cell whose
138
- * first character is one of these — `=HYPERLINK(…)` exfiltrates neighbouring
139
- * cells on click, `=cmd|'…'!A0` is worse.
140
- */
141
- var FORMULA_TRIGGERS = [
142
- "=",
143
- "+",
144
- "-",
145
- "@",
146
- " ",
147
- "\r"
148
- ];
149
- /**
150
- * Neutralise a cell that a spreadsheet would otherwise evaluate, by prefixing a
151
- * single quote — the standard OWASP mitigation. The apostrophe is consumed by
152
- * the spreadsheet as "treat the rest as text", so the value reads back intact.
153
- */
154
- function escapeCsvFormula(value) {
155
- const first = value.charAt(0);
156
- if (!FORMULA_TRIGGERS.includes(first)) return value;
157
- if ((first === "-" || first === "+") && value.trim() !== "" && Number.isFinite(Number(value))) return value;
158
- return "'" + value;
159
- }
160
- function toCSVCell(v) {
161
- if (v === null || v === void 0) return "";
162
- return "\"" + escapeCsvFormula(Array.isArray(v) ? JSON.stringify(v) : String(v)).replaceAll("\"", "\"\"") + "\"";
163
- }
164
- function entryToCSVRow(entry) {
165
- return entry.map(toCSVCell).join(",") + "\r\n";
166
- }
167
- function downloadBlob(content, filename, contentType) {
168
- const blob = new Blob(content, { type: contentType });
169
- const url = URL.createObjectURL(blob);
170
- const pom = document.createElement("a");
171
- pom.href = url;
172
- pom.setAttribute("download", filename);
173
- pom.click();
174
- }
175
- function downloadDataAsCsv(data, name) {
176
- if (data.length === 0) return;
177
- const headers = Object.keys(data[0]);
178
- downloadBlob([[headers.join(","), ...data.map((row) => headers.map((header) => toCSVCell(row[header])).join(","))].join("\r\n")], `${name}.csv`, "text/csv");
179
- }
180
- //#endregion
181
8
  //#region src/data_export/export/fetch_export_data.ts
182
9
  /**
183
10
  * Rows asked for per request.
@@ -239,20 +66,28 @@ function BasicExportAction({ data, properties, propertiesOrder }) {
239
66
  additionalData: [],
240
67
  properties,
241
68
  propertiesOrder,
242
- name: "export.csv",
69
+ name: "export",
243
70
  flattenArrays,
244
71
  additionalHeaders: [],
245
72
  exportType,
246
73
  dateExportType
247
74
  });
248
75
  handleClose();
249
- }, []);
76
+ }, [
77
+ data,
78
+ properties,
79
+ propertiesOrder,
80
+ flattenArrays,
81
+ exportType,
82
+ dateExportType,
83
+ handleClose
84
+ ]);
250
85
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Tooltip, {
251
86
  title: "Export",
252
87
  asChild: true,
253
88
  children: /* @__PURE__ */ jsx(IconButton, {
89
+ "aria-label": "Export",
254
90
  size: "small",
255
- color: "primary",
256
91
  onClick: handleClickOpen,
257
92
  children: /* @__PURE__ */ jsx(DownloadIcon, { size: iconSize.small })
258
93
  })
@@ -354,8 +189,7 @@ function BasicExportAction({ data, properties, propertiesOrder }) {
354
189
  })] });
355
190
  }
356
191
  //#endregion
357
- //#region src/data_export/export/ExportCollectionAction.tsx
358
- var DOCS_LIMIT = 500;
192
+ //#region src/data_export/export/additional_fields.ts
359
193
  /**
360
194
  * Additional-field builders run per row and may do I/O, so a `Promise.all` over
361
195
  * the whole export is one request per row all at once. That was bounded only by
@@ -368,6 +202,43 @@ async function mapInChunks(items, fn, chunkSize = ADDITIONAL_FIELDS_CONCURRENCY)
368
202
  for (let i = 0; i < items.length; i += chunkSize) results.push(...await Promise.all(items.slice(i, i + chunkSize).map(fn)));
369
203
  return results;
370
204
  }
205
+ /**
206
+ * The values of the export's extra columns, one record per entity: the
207
+ * collection's `exportable.additionalFields` and its `additionalFields` that
208
+ * declare a `value`, both in the same record.
209
+ */
210
+ async function resolveAdditionalExportValues({ entities, exportFields, additionalFields, context }) {
211
+ const resolvedExportColumnsValues = exportFields ? await mapInChunks(entities, async (entity) => {
212
+ return (await Promise.all(exportFields.map(async (column) => {
213
+ return { [column.key]: await column.builder({
214
+ entity,
215
+ context
216
+ }) };
217
+ }))).reduce((a, b) => ({
218
+ ...a,
219
+ ...b
220
+ }), {});
221
+ }) : [];
222
+ const resolvedColumnsValues = additionalFields ? await mapInChunks(entities, async (entity) => {
223
+ return (await Promise.all(additionalFields.map(async (field) => {
224
+ if (!field.value) return {};
225
+ return { [field.key]: await field.value({
226
+ entity,
227
+ context
228
+ }) };
229
+ }))).reduce((a, b) => ({
230
+ ...a,
231
+ ...b
232
+ }), {});
233
+ }) : [];
234
+ return entities.map((_, i) => ({
235
+ ...resolvedExportColumnsValues[i],
236
+ ...resolvedColumnsValues[i]
237
+ }));
238
+ }
239
+ //#endregion
240
+ //#region src/data_export/export/ExportCollectionAction.tsx
241
+ var DOCS_LIMIT = 500;
371
242
  function ExportCollectionAction({ collection, path, collectionEntitiesCount, selectionController, tableController, onAnalyticsEvent, exportAllowed, notAllowedView }) {
372
243
  const { t } = useTranslation();
373
244
  const exportConfig = typeof collection.exportable === "object" ? collection.exportable : void 0;
@@ -407,34 +278,16 @@ function ExportCollectionAction({ collection, path, collectionEntitiesCount, sel
407
278
  const handleClose = useCallback(() => {
408
279
  setOpen(false);
409
280
  }, [setOpen]);
410
- const fetchAdditionalFields = useCallback(async (entities) => {
411
- const additionalExportFields = exportConfig?.additionalFields;
412
- const additionalFields = collection.additionalFields;
413
- const resolvedExportColumnsValues = additionalExportFields ? await mapInChunks(entities, async (entity) => {
414
- return (await Promise.all(additionalExportFields.map(async (column) => {
415
- return { [column.key]: await column.builder({
416
- entity,
417
- context
418
- }) };
419
- }))).reduce((a, b) => ({
420
- ...a,
421
- ...b
422
- }), {});
423
- }) : [];
424
- const resolvedColumnsValues = additionalFields ? await mapInChunks(entities, async (entity) => {
425
- return (await Promise.all(additionalFields.map(async (field) => {
426
- if (!field.value) return {};
427
- return { [field.key]: await field.value({
428
- entity,
429
- context
430
- }) };
431
- }))).reduce((a, b) => ({
432
- ...a,
433
- ...b
434
- }), {});
435
- }) : [];
436
- return [...resolvedExportColumnsValues, ...resolvedColumnsValues];
437
- }, [exportConfig?.additionalFields]);
281
+ const fetchAdditionalFields = useCallback((entities) => resolveAdditionalExportValues({
282
+ entities,
283
+ exportFields: exportConfig?.additionalFields,
284
+ additionalFields: collection.additionalFields,
285
+ context
286
+ }), [
287
+ exportConfig?.additionalFields,
288
+ collection.additionalFields,
289
+ context
290
+ ]);
438
291
  const doDownload = useCallback(async (collection, exportConfig) => {
439
292
  onAnalyticsEvent?.("export_collection", { collection: collection.slug });
440
293
  setDataLoading(true);
@@ -511,8 +364,8 @@ function ExportCollectionAction({ collection, path, collectionEntitiesCount, sel
511
364
  title: "Export",
512
365
  asChild: true,
513
366
  children: /* @__PURE__ */ jsx(IconButton, {
367
+ "aria-label": "Export",
514
368
  size: "small",
515
- color: "primary",
516
369
  onClick: handleClickOpen,
517
370
  children: /* @__PURE__ */ jsx(DownloadIcon, { size: iconSize.small })
518
371
  })
@@ -658,9 +511,10 @@ var export_exports = /* @__PURE__ */ __exportAll({
658
511
  escapeCsvFormula: () => escapeCsvFormula,
659
512
  fetchAllEntitiesForExport: () => fetchAllEntitiesForExport,
660
513
  getEntityCSVExportableData: () => getEntityCSVExportableData,
661
- getEntityJsonExportableData: () => getEntityJsonExportableData
514
+ getEntityJsonExportableData: () => getEntityJsonExportableData,
515
+ unescapeCsvFormula: () => unescapeCsvFormula
662
516
  });
663
517
  //#endregion
664
- export { MAX_EXPORT_ROWS as a, downloadDataAsCsv as c, escapeCsvFormula as d, getEntityCSVExportableData as f, EXPORT_PAGE_SIZE as i, downloadEntitiesExport as l, ExportCollectionAction as n, fetchAllEntitiesForExport as o, getEntityJsonExportableData as p, BasicExportAction as r, downloadBlob as s, export_exports as t, entryToCSVRow as u };
518
+ export { MAX_EXPORT_ROWS as a, EXPORT_PAGE_SIZE as i, ExportCollectionAction as n, fetchAllEntitiesForExport as o, BasicExportAction as r, export_exports as t };
665
519
 
666
- //# sourceMappingURL=export-LvIpE7sE.js.map
520
+ //# sourceMappingURL=export-DCHuPD1a.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-DCHuPD1a.js","names":[],"sources":["../src/data_export/export/fetch_export_data.ts","../src/data_export/export/BasicExportAction.tsx","../src/data_export/export/additional_fields.ts","../src/data_export/export/ExportCollectionAction.tsx","../src/data_export/export/index.ts"],"sourcesContent":["import { Entity, FindParams } from \"@rebasepro/types\";\nimport { EntityPageAccessor, walkEntityPages } from \"../../util/entity_pages\";\n\n/**\n * Rows asked for per request.\n *\n * Kept at or below the server's `MAX_LIST_LIMIT`, which is now the largest page\n * a read will serve *and refuses* anything larger — it used to clamp silently,\n * and asking for 100 000 while receiving 1 000 looked exactly like a collection\n * with 1 000 rows in it. The walk still never trusts the page size it asked for:\n * it advances by the rows it was actually handed.\n */\nexport const EXPORT_PAGE_SIZE = 500;\n\n/**\n * Most rows one browser-side export will materialise.\n *\n * Everything here is built in memory and handed to a Blob, so there is a\n * ceiling whether or not it is named. Naming it means going over it **throws**\n * instead of writing a short file that reads like the whole collection.\n */\nexport const MAX_EXPORT_ROWS = 100_000;\n\nexport interface FetchAllForExportParams<M extends Record<string, unknown>> {\n /** The collection accessor to read through — `dataClient.collection(path)`. */\n accessor: EntityPageAccessor<M>;\n /** Query to export. The window (`limit` / `offset` / `page`) is owned by the walk. */\n params?: Omit<FindParams<M>, \"limit\" | \"offset\" | \"page\">;\n pageSize?: number;\n maxRows?: number;\n /** Called after every page with the rows so far, and the total when the server reports one. */\n onProgress?: (loaded: number, total: number | undefined) => void;\n}\n\n/**\n * Read **every** row matching a query, for an export file.\n *\n * The export used to hand back the 50 rows an unwindowed `find({})` resolves to\n * as `<collection>.csv`, with nothing saying it was a sample, next to a dialog\n * warning that the collection had 100 000 documents. The walk is in\n * {@link walkEntityPages}; what belongs to export is the ceiling — an export is\n * built in memory and handed to a Blob — and what to say on hitting it.\n *\n * @throws When more than `maxRows` rows match.\n */\nexport async function fetchAllEntitiesForExport<M extends Record<string, unknown>>({\n accessor,\n params,\n pageSize = EXPORT_PAGE_SIZE,\n maxRows = MAX_EXPORT_ROWS,\n onProgress\n}: FetchAllForExportParams<M>): Promise<Entity<M>[]> {\n return walkEntityPages<M>({\n accessor,\n params,\n pageSize,\n maxRows,\n onProgress,\n onCeiling: (max) =>\n `This export would contain more than ${max.toLocaleString()} rows, which is more than the ` +\n \"browser can build in one file. Filter the collection down, or export it from the server.\"\n });\n}\n","\nimport React, { useCallback } from \"react\";\n\nimport { useTranslation } from \"@rebasepro/app\";\nimport { Entity, Properties } from \"@rebasepro/types\";\nimport {\n BooleanSwitchWithLabel,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n DownloadIcon,\n IconButton,\n iconSize,\n Label,\n RadioGroup,\n RadioGroupItem,\n Tooltip,\n Typography\n} from \"@rebasepro/ui\";\nimport { downloadEntitiesExport } from \"./export\";\n\nexport type BasicExportActionProps = {\n data: Entity<any>[];\n properties: Properties;\n propertiesOrder?: string[];\n}\n\nexport function BasicExportAction({\n data,\n properties,\n propertiesOrder\n}: BasicExportActionProps) {\n\n const { t } = useTranslation();\n const dateRef = React.useRef<Date>(new Date());\n const [flattenArrays, setFlattenArrays] = React.useState<boolean>(true);\n const [exportType, setExportType] = React.useState<\"csv\" | \"json\">(\"csv\");\n const [dateExportType, setDateExportType] = React.useState<\"timestamp\" | \"string\">(\"string\");\n\n const [open, setOpen] = React.useState(false);\n\n const handleClickOpen = useCallback(() => {\n setOpen(true);\n }, [setOpen]);\n\n const handleClose = useCallback(() => {\n setOpen(false);\n }, [setOpen]);\n\n const onOkClicked = useCallback(() => {\n downloadEntitiesExport({\n data,\n additionalData: [],\n properties,\n propertiesOrder,\n // The writer appends the extension for the chosen format.\n name: \"export\",\n flattenArrays,\n additionalHeaders: [],\n exportType,\n dateExportType\n });\n handleClose();\n }, [data, properties, propertiesOrder, flattenArrays, exportType, dateExportType, handleClose]);\n\n return <>\n\n <Tooltip title={\"Export\"}\n asChild={true}>\n <IconButton aria-label={\"Export\"}\n size={\"small\"}\n onClick={handleClickOpen}>\n <DownloadIcon\n size={iconSize.small}/>\n </IconButton>\n </Tooltip>\n\n <Dialog\n open={open}\n onOpenChange={setOpen}\n maxWidth={\"xl\"}>\n\n <DialogTitle variant={\"h6\"}>{t(\"export_data\")}</DialogTitle>\n\n <DialogContent className={\"flex flex-col gap-4 my-4\"}>\n\n <div>{t(\"download_table_csv\")}</div>\n\n <div className={\"flex flex-row gap-4\"}>\n <div className={\"p-4 flex flex-col\"}>\n <RadioGroup value={exportType} onValueChange={(v) => setExportType(v as \"csv\" | \"json\")}>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"csv\" id=\"radio-csv\"/>\n <Label htmlFor=\"radio-csv\">{t(\"csv\")}</Label>\n </div>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"json\" id=\"radio-json\"/>\n <Label htmlFor=\"radio-json\">{t(\"json\")}</Label>\n </div>\n </RadioGroup>\n </div>\n\n <div className={\"p-4 flex flex-col\"}>\n <RadioGroup value={dateExportType} onValueChange={(v) => setDateExportType(v as \"timestamp\" | \"string\")}>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"timestamp\" id=\"radio-timestamp\"/>\n <Label htmlFor=\"radio-timestamp\">{t(\"dates_as_timestamps\")} ({dateRef.current.getTime()})</Label>\n </div>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"string\" id=\"radio-string\"/>\n <Label htmlFor=\"radio-string\">{t(\"dates_as_strings\")} ({dateRef.current.toISOString()})</Label>\n </div>\n </RadioGroup>\n </div>\n </div>\n\n <BooleanSwitchWithLabel\n size={\"small\"}\n disabled={exportType !== \"csv\"}\n value={flattenArrays}\n onValueChange={setFlattenArrays}\n label={t(\"flatten_arrays\")}/>\n\n </DialogContent>\n\n <DialogActions>\n\n <Button onClick={handleClose}\n variant={\"text\"}>\n {t(\"cancel\")}\n </Button>\n\n <Button onClick={onOkClicked}>\n {t(\"download\")}\n </Button>\n\n </DialogActions>\n\n </Dialog>\n\n </>;\n}\n","import { Entity } from \"@rebasepro/types\";\nimport { AdditionalFieldDelegate, ExportMappingFunction, RebaseContext } from \"@rebasepro/cms-types\";\n\n/**\n * Additional-field builders run per row and may do I/O, so a `Promise.all` over\n * the whole export is one request per row all at once. That was bounded only by\n * the 50-row cap the export used to have; now that the read is paginated, it is\n * bounded here instead.\n */\nconst ADDITIONAL_FIELDS_CONCURRENCY = 50;\n\nasync function mapInChunks<T, R>(items: T[], fn: (item: T) => Promise<R>, chunkSize = ADDITIONAL_FIELDS_CONCURRENCY): Promise<R[]> {\n const results: R[] = [];\n for (let i = 0; i < items.length; i += chunkSize) {\n results.push(...await Promise.all(items.slice(i, i + chunkSize).map(fn)));\n }\n return results;\n}\n\n/**\n * The values of the export's extra columns, one record per entity: the\n * collection's `exportable.additionalFields` and its `additionalFields` that\n * declare a `value`, both in the same record.\n */\nexport async function resolveAdditionalExportValues<M extends Record<string, unknown>>({\n entities,\n exportFields,\n additionalFields,\n context\n}: {\n entities: Entity<M>[];\n exportFields: ExportMappingFunction[] | undefined;\n additionalFields: AdditionalFieldDelegate<M>[] | undefined;\n context: RebaseContext;\n}): Promise<Record<string, unknown>[]> {\n\n const resolvedExportColumnsValues: Record<string, unknown>[] = exportFields\n ? await mapInChunks(entities, async (entity) => {\n return (await Promise.all(exportFields.map(async (column) => {\n return {\n [column.key]: await column.builder({\n entity,\n context\n })\n };\n }))).reduce((a, b) => ({ ...a,\n...b }), {});\n })\n : [];\n\n const resolvedColumnsValues: Record<string, unknown>[] = additionalFields\n ? await mapInChunks(entities, async (entity) => {\n return (await Promise.all(additionalFields\n .map(async (field) => {\n if (!field.value)\n return {};\n return {\n [field.key]: await field.value({\n entity,\n context\n })\n };\n }))).reduce((a, b) => ({ ...a,\n...b }), {});\n })\n : [];\n // One record per entity, index for index — the export merges\n // `additionalData[i]` into row `i`.\n return entities.map((_, i) => ({\n ...resolvedExportColumnsValues[i],\n ...resolvedColumnsValues[i]\n }));\n}\n","\nimport React, { useCallback, useMemo } from \"react\";\n\nimport {\n useAuthController,\n useCustomizationController,\n useData,\n useTranslation\n} from \"@rebasepro/app\";\nimport { useAdminContext } from \"../../hooks\";\nimport { Entity, User } from \"@rebasepro/types\";\nimport { CollectionActionsProps, ExportConfig, RebaseContext, AdminCollection, SelectionQuery } from \"@rebasepro/cms-types\";\nimport { getDefaultValuesFor } from \"@rebasepro/common\";\nimport {\n Alert,\n BooleanSwitchWithLabel,\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n DownloadIcon,\n IconButton,\n iconSize,\n Label,\n RadioGroup,\n RadioGroupItem,\n Tooltip\n} from \"@rebasepro/ui\";\nimport { downloadEntitiesExport } from \"./export\";\nimport { fetchAllEntitiesForExport, MAX_EXPORT_ROWS } from \"./fetch_export_data\";\nimport { resolveAdditionalExportValues } from \"./additional_fields\";\nimport { resolveSelection, selectionQueryToFindParams } from \"../../selection\";\n\nconst DOCS_LIMIT = 500;\n\nexport function ExportCollectionAction<M extends Record<string, unknown>, USER extends User>({\n collection,\n path,\n collectionEntitiesCount,\n selectionController,\n tableController,\n onAnalyticsEvent,\n exportAllowed,\n notAllowedView\n}: CollectionActionsProps<M, USER, AdminCollection<M>> & {\n exportAllowed?: (props: { collectionEntitiesCount: number, path: string, collection: AdminCollection }) => boolean;\n notAllowedView?: React.ReactNode;\n onAnalyticsEvent?: (event: string, params?: any) => void;\n}) {\n\n const { t } = useTranslation();\n\n const exportConfig = typeof collection.exportable === \"object\" ? collection.exportable : undefined;\n\n const dateRef = React.useRef<Date>(new Date());\n\n const [includeUndefinedValues, setIncludeUndefinedValues] = React.useState<boolean>(false);\n const [flattenArrays, setFlattenArrays] = React.useState<boolean>(true);\n const [exportType, setExportType] = React.useState<\"csv\" | \"json\">(\"csv\");\n const [dateExportType, setDateExportType] = React.useState<\"timestamp\" | \"string\">(\"string\");\n\n const context = useAdminContext<USER>();\n const dataClient = useData();\n\n // A selection narrows the export to it; with none, the export is the view\n // — filter, search and sort included.\n const exportSelection = selectionController?.hasSelection ? selectionController.selection : undefined;\n\n const liveQuery: SelectionQuery<M> = useMemo(() => ({\n path,\n filterValues: tableController?.filterValues,\n searchString: tableController?.searchString,\n sortBy: tableController?.sortBy\n }), [path, tableController?.filterValues, tableController?.searchString, tableController?.sortBy]);\n\n // Said before the download starts, not discovered halfway through it: the\n // walk refuses rather than writing a short file, so the dialog has to name\n // the ceiling it is about to hit.\n const rowsToExport = exportSelection ? selectionController?.selectedCount : collectionEntitiesCount;\n const tooManyToExport = rowsToExport !== undefined && rowsToExport > MAX_EXPORT_ROWS;\n\n const canExport = !exportAllowed || exportAllowed({\n collectionEntitiesCount: collectionEntitiesCount ?? 0,\n path,\n collection\n });\n\n const [dataLoading, setDataLoading] = React.useState<boolean>(false);\n const [dataLoadingError, setDataLoadingError] = React.useState<Error | undefined>();\n const [progress, setProgress] = React.useState<{ loaded: number, total?: number }>({ loaded: 0 });\n\n const [open, setOpen] = React.useState(false);\n\n const handleClickOpen = useCallback(() => {\n setOpen(true);\n }, [setOpen]);\n\n const handleClose = useCallback(() => {\n setOpen(false);\n }, [setOpen]);\n\n const fetchAdditionalFields = useCallback((entities: Entity<M>[]) => resolveAdditionalExportValues({\n entities,\n exportFields: exportConfig?.additionalFields,\n additionalFields: collection.additionalFields,\n context: context as RebaseContext\n }), [exportConfig?.additionalFields, collection.additionalFields, context]);\n\n const doDownload = useCallback(async (collection: AdminCollection<M>,\n exportConfig: ExportConfig<any> | undefined): Promise<boolean> => {\n\n onAnalyticsEvent?.(\"export_collection\", {\n collection: collection.slug\n });\n setDataLoading(true);\n setDataLoadingError(undefined);\n setProgress({ loaded: 0 });\n try {\n const onProgress = (loaded: number, total: number | undefined) => setProgress({ loaded, total });\n const accessor = dataClient.collection(path) as { find: (params?: any) => Promise<any> };\n\n // What the user is looking at, or what they picked out of it.\n //\n // Neither used to be true: the export read the collection with no\n // params at all, so a filtered, searched table of 40 rows exported\n // every row in the table — and ticking rows first changed nothing.\n const data = exportSelection\n ? await resolveSelection<M>({\n selection: exportSelection,\n accessor,\n maxRows: MAX_EXPORT_ROWS,\n onProgress\n })\n // Paginated, not `find({})`: an absent limit resolves to 50 rows\n // server-side, so the export used to be the first page of the\n // collection under a filename that read like all of it.\n : await fetchAllEntitiesForExport<M>({\n accessor,\n params: selectionQueryToFindParams(liveQuery),\n onProgress\n });\n const additionalData = await fetchAdditionalFields(data);\n const additionalHeaders = [\n ...exportConfig?.additionalFields?.map(column => column.key) ?? [],\n ...collection.additionalFields?.map(field => field.key) ?? []\n ];\n\n const defaultValues = includeUndefinedValues ? getDefaultValuesFor(collection.properties) : undefined;\n const dataWithDefaults = defaultValues\n ? data.map(entity => ({\n ...entity,\n values: { ...defaultValues,\n...entity.values }\n }))\n : data;\n downloadEntitiesExport({\n data: dataWithDefaults,\n additionalData,\n properties: collection.properties,\n propertiesOrder: collection.propertiesOrder,\n name: collection.name,\n flattenArrays,\n additionalHeaders,\n exportType,\n dateExportType\n });\n onAnalyticsEvent?.(\"export_collection_success\", {\n collection: collection.slug\n });\n return true;\n } catch (e) {\n console.error(\"Error loading export data\", e);\n setDataLoadingError(e as Error);\n return false;\n } finally {\n setDataLoading(false);\n }\n\n }, [onAnalyticsEvent, dataClient, path, exportSelection, liveQuery, fetchAdditionalFields, includeUndefinedValues, flattenArrays, exportType, dateExportType]);\n\n const onOkClicked = useCallback(() => {\n // The dialog stays open until the walk finishes: it is the only place a\n // multi-page export reports progress, or reports that it failed.\n doDownload(collection, exportConfig).then((downloaded) => {\n if (downloaded) handleClose();\n });\n }, [doDownload, collection, exportConfig, handleClose]);\n\n return <>\n\n <Tooltip title={\"Export\"}\n asChild={true}>\n <IconButton aria-label={\"Export\"}\n size={\"small\"}\n onClick={handleClickOpen}>\n <DownloadIcon size={iconSize.small}/>\n </IconButton>\n </Tooltip>\n\n <Dialog\n open={open}\n onOpenChange={setOpen}\n maxWidth={\"xl\"}>\n\n <DialogTitle variant={\"h6\"}>{t(\"export_data\")}</DialogTitle>\n\n <DialogContent className={\"flex flex-col gap-4 my-4\"}>\n\n {/* What is about to be downloaded, in as many words. The export\n now follows the selection, or the view's filter and search —\n so \"the content of this table\" would be describing something\n it no longer does. */}\n <div>{exportSelection\n ? (rowsToExport === undefined\n ? (t(\"export_selection_all_matching\", { collection: collection.name })\n ?? `Download every ${collection.name} matching the current filter as a ${exportType.toUpperCase()}`)\n : (t(\"export_selection_count\", {\n total: rowsToExport.toLocaleString(),\n format: exportType.toUpperCase()\n }) ?? `Download the ${rowsToExport.toLocaleString()} selected rows as a ${exportType.toUpperCase()}`))\n : t(\"download_table_csv\")}</div>\n\n {!exportSelection && collectionEntitiesCount !== undefined && collectionEntitiesCount > DOCS_LIMIT && !tooManyToExport &&\n <Alert color={\"warning\"}>\n <div>\n {t(\"large_number_of_documents\", { count: collectionEntitiesCount.toString() })}\n </div>\n </Alert>}\n\n {tooManyToExport &&\n <Alert color={\"error\"}>\n <div>\n {t(\"too_many_documents_to_export\", {\n count: (collectionEntitiesCount ?? 0).toString(),\n limit: MAX_EXPORT_ROWS.toString()\n })}\n </div>\n </Alert>}\n\n <div className={\"flex flex-row gap-4\"}>\n <div className={\"p-4 flex flex-col\"}>\n <RadioGroup value={exportType} onValueChange={(v) => setExportType(v as \"csv\" | \"json\")}>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"csv\" id=\"radio-csv\"/>\n <Label htmlFor=\"radio-csv\">{t(\"csv\")}</Label>\n </div>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"json\" id=\"radio-json\"/>\n <Label htmlFor=\"radio-json\">{t(\"json\")}</Label>\n </div>\n </RadioGroup>\n </div>\n\n <div className={\"p-4 flex flex-col\"}>\n <RadioGroup value={dateExportType} onValueChange={(v) => setDateExportType(v as \"timestamp\" | \"string\")}>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"timestamp\" id=\"radio-timestamp\"/>\n <Label htmlFor=\"radio-timestamp\">{t(\"dates_as_timestamps\")} ({dateRef.current.getTime()})</Label>\n </div>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"string\" id=\"radio-string\"/>\n <Label htmlFor=\"radio-string\">{t(\"dates_as_strings\")} ({dateRef.current.toISOString()})</Label>\n </div>\n </RadioGroup>\n </div>\n </div>\n\n <BooleanSwitchWithLabel\n size={\"small\"}\n disabled={exportType !== \"csv\"}\n value={flattenArrays}\n onValueChange={setFlattenArrays}\n label={t(\"flatten_arrays\")}/>\n\n <BooleanSwitchWithLabel\n size={\"small\"}\n value={includeUndefinedValues}\n onValueChange={setIncludeUndefinedValues}\n label={t(\"include_undefined_values\")}/>\n\n {dataLoadingError && <Alert color={\"error\"}>\n <div>{dataLoadingError.message}</div>\n </Alert>}\n\n {!canExport && notAllowedView}\n\n </DialogContent>\n\n <DialogActions>\n\n {dataLoading && <CircularProgress size={\"smallest\"}/>}\n\n {dataLoading && <Label>\n {progress.total !== undefined\n ? `${progress.loaded} / ${progress.total}`\n : `${progress.loaded}`}\n </Label>}\n\n <Button onClick={handleClose}\n variant={\"text\"}>\n {t(\"cancel\")}\n </Button>\n\n <Button onClick={onOkClicked}\n disabled={dataLoading || !canExport || tooManyToExport}>\n {t(\"download\")}\n </Button>\n\n </DialogActions>\n\n </Dialog>\n\n </>;\n}\n","export * from \"./export\";\nexport * from \"./fetch_export_data\";\nexport * from \"./BasicExportAction\";\nexport * from \"./ExportCollectionAction\";\n"],"mappings":"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"}
@@ -3,8 +3,45 @@ import type { Entity, EntityStatus, EntityValues } from "@rebasepro/types";
3
3
  import type { AuthController } from "@rebasepro/cms-types";
4
4
  import { z } from "zod";
5
5
  export declare function extractTouchedValues(values: unknown, touched: Record<string, boolean>): Record<string, unknown>;
6
+ /**
7
+ * The edit to carry or back up: each top-level property the user touched,
8
+ * **whole** — the same unit a save writes.
9
+ *
10
+ * `extractTouchedValues` builds a tree of only the touched paths, which is a
11
+ * diff, and a diff cannot say "this item is gone": a list that shrank from
12
+ * three items to one, or a key-value map with a key deleted, came back whole
13
+ * when the diff was merged onto the stored record. Carrying the property whole
14
+ * and overlaying it (see {@link overlayEdit}) shows exactly what the user left.
15
+ *
16
+ * Which properties count is still decided by the touched tree with its ghost
17
+ * containers removed, so a field merely focused carries nothing.
18
+ */
19
+ export declare function getTouchedPropertyValues<M extends Record<string, unknown>>(values: Partial<M>, touched: Record<string, boolean>): Partial<M>;
20
+ /**
21
+ * What a form shows when an edit is laid over `base`: each property the edit
22
+ * carries replaces the base's, whole.
23
+ *
24
+ * Not `mergeDeep`, which merged arrays of maps index by index and kept the base
25
+ * items past the edit's length, and kept map keys the edit had dropped — so a
26
+ * deleted section or key came back when the edit changed layout or a draft was
27
+ * restored, in a form that then read as clean.
28
+ */
29
+ export declare function overlayEdit<M extends Record<string, unknown>>(base: Partial<M>, edit: Partial<M>): Partial<M>;
6
30
  export declare function removeEmptyContainers(obj: unknown): unknown;
7
31
  export declare function getChanges<T extends object>(source: Partial<T>, comparison: Partial<T>): Partial<T>;
32
+ /**
33
+ * What an update of a stored record sends: every top-level property whose value
34
+ * differs from the stored one — whole.
35
+ *
36
+ * Whole, because that is the unit an update replaces. It sets each property it
37
+ * carries, so the value sent *is* the property's new value: a map sent as the
38
+ * one key that changed would erase every key left out, a key-value map with a
39
+ * key removed would arrive empty, and a geopoint with one coordinate moved is
40
+ * refused for lacking the other. {@link getChanges} walks into plain objects to
41
+ * find what changed inside them, which is right for comparing a draft with the
42
+ * form and wrong for a write.
43
+ */
44
+ export declare function getChangedProperties<M extends Record<string, unknown>>(values: Partial<M>, storedValues: Partial<M>): Partial<M>;
8
45
  /**
9
46
  * What the local-changes backup still has to offer, given what the form is
10
47
  * already showing — the answer the "unsaved local changes" banner is asking for.
@@ -0,0 +1,26 @@
1
+ import type { Properties, Property } from "@rebasepro/types";
2
+ import type { ResolvePropertyProps } from "@rebasepro/common";
3
+ /**
4
+ * A property as the record form shows it and judges it, given the values the
5
+ * form holds now: its `dynamicProps` applied, then its `conditions`.
6
+ *
7
+ * One function for both, so what a field looks like and what the save checks
8
+ * cannot drift apart. They had: the field was resolved through `dynamicProps`
9
+ * and nothing else, and the save was judged against the raw declaration — so a
10
+ * `dynamicProps` validation was never checked, and a `conditions` rule written
11
+ * by the collection editor (and taught by the docs) did nothing anywhere.
12
+ */
13
+ export declare function resolveFormProperty<M extends Record<string, unknown> = Record<string, unknown>>(props: ResolvePropertyProps<M>): Property | null;
14
+ /**
15
+ * The properties the form validates on submit: each resolved against `values`
16
+ * (see {@link resolveFormProperty}), inside maps too, and only those the user
17
+ * can change.
18
+ */
19
+ export declare function resolvePropertiesForValidation<M extends Record<string, unknown>>({ properties, propertyKey, isNew, ...props }: Omit<ResolvePropertyProps<M>, "property"> & {
20
+ properties: Properties;
21
+ /**
22
+ * Whether the record is being created. A field marked
23
+ * `admin.filledByServer` is not required then: the server fills it.
24
+ */
25
+ isNew?: boolean;
26
+ }): Properties;
@@ -18,4 +18,17 @@ interface PropertyContext<P extends Property> {
18
18
  export declare function getEntitySchema<M extends Record<string, unknown>>(entityId: string | number | undefined, properties: Properties, customFieldValidator?: CustomFieldValidator): z.ZodObject<Record<string, ZodTypeAny>>;
19
19
  export declare function mapPropertyToZod(propertyContext: PropertyContext<Property>): ZodTypeAny;
20
20
  export declare function getZodMapObjectSchema({ property, entityId, customFieldValidator, name }: PropertyContext<MapProperty>): ZodTypeAny;
21
+ /**
22
+ * The values as they are to be written: every `trim`, `lowercase` and
23
+ * `uppercase` a string property declares applied to its value, inside maps and
24
+ * arrays too.
25
+ *
26
+ * They are documented as changing the value that is written, and only the
27
+ * panel applies them — the server checks what it receives. Validation judges
28
+ * the transformed value, so without this the form passed "my-slug" and sent
29
+ * " My-Slug ", which a `matches` on the server then refused.
30
+ *
31
+ * Returns `values` itself when nothing changes.
32
+ */
33
+ export declare function applyValueTransforms<M extends Record<string, unknown>>(values: M, properties: Properties): M;
21
34
  export {};
@@ -1,4 +1,4 @@
1
- import { Mr as getPropertyInPath$1, Pn as PropertyPreview, tt as useHistory } from "./util-CyIcgnv2.js";
1
+ import { Qn as PropertyPreview, Zr as getPropertyInPath$1, tt as useHistory } from "./util-BRvKFMLa.js";
2
2
  import * as React$1 from "react";
3
3
  import { useEffect, useRef, useState } from "react";
4
4
  import { ArrowLeftIcon, Chip, ErrorBoundary, HistoryIcon, IconButton, Label, Tooltip, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
@@ -263,6 +263,7 @@ function EntityHistoryView({ entity, collection, formContext, refreshToken }) {
263
263
  title: t("history_revert") ?? "Revert to this version",
264
264
  className: "m-2 grow-0 self-start",
265
265
  children: /* @__PURE__ */ jsx(IconButton, {
266
+ "aria-label": t("history_revert") ?? "Revert to this version",
266
267
  onClick: () => {
267
268
  if (dirty) snackbarController.open({
268
269
  message: t("history_revert_dirty") ?? "Please save or discard your changes before reverting",
@@ -310,4 +311,4 @@ function EntityHistoryView({ entity, collection, formContext, refreshToken }) {
310
311
  //#endregion
311
312
  export { EntityHistoryView };
312
313
 
313
- //# sourceMappingURL=history-DG6Ln7CV.js.map
314
+ //# sourceMappingURL=history-CBd6SBRs.js.map