@nubitio/crud 0.5.14 → 0.5.16
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/dist/index.cjs +68 -140
- package/dist/index.mjs +69 -141
- package/dist/style.css +10 -468
- package/package.json +3 -6
package/dist/index.cjs
CHANGED
|
@@ -28,7 +28,6 @@ let react_dom = require("react-dom");
|
|
|
28
28
|
let _nubitio_ui = require("@nubitio/ui");
|
|
29
29
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
30
30
|
let _nubitio_core = require("@nubitio/core");
|
|
31
|
-
let react_dropzone = require("react-dropzone");
|
|
32
31
|
let _tanstack_react_query = require("@tanstack/react-query");
|
|
33
32
|
//#region packages/crud/crud/defineResource.ts
|
|
34
33
|
const stringResourceCache = /* @__PURE__ */ new Map();
|
|
@@ -1243,9 +1242,6 @@ const enumTypeModule = {
|
|
|
1243
1242
|
};
|
|
1244
1243
|
//#endregion
|
|
1245
1244
|
//#region packages/crud/form/FileUploadField.tsx
|
|
1246
|
-
function cx$1(...values) {
|
|
1247
|
-
return values.filter(Boolean).join(" ");
|
|
1248
|
-
}
|
|
1249
1245
|
function resolveMediaPath(media) {
|
|
1250
1246
|
if (!media) return null;
|
|
1251
1247
|
const path = media["path"];
|
|
@@ -1271,27 +1267,6 @@ function resolveMediaIri(uploadUrl, media) {
|
|
|
1271
1267
|
function isImageMimeType(mimeType) {
|
|
1272
1268
|
return !!mimeType && mimeType.startsWith("image/");
|
|
1273
1269
|
}
|
|
1274
|
-
function buildDropzoneAccept(accept) {
|
|
1275
|
-
if (!accept || accept === "*/*" || accept === "*") return void 0;
|
|
1276
|
-
if (accept === "image/*") return {
|
|
1277
|
-
"image/png": [".png"],
|
|
1278
|
-
"image/jpeg": [".jpg", ".jpeg"],
|
|
1279
|
-
"image/webp": [".webp"],
|
|
1280
|
-
"image/gif": [".gif"]
|
|
1281
|
-
};
|
|
1282
|
-
if (accept.includes(",")) return accept.split(",").reduce((acc, token) => {
|
|
1283
|
-
const trimmed = token.trim();
|
|
1284
|
-
if (!trimmed) return acc;
|
|
1285
|
-
if (trimmed.startsWith(".")) {
|
|
1286
|
-
acc["application/octet-stream"] = [...acc["application/octet-stream"] ?? [], trimmed];
|
|
1287
|
-
return acc;
|
|
1288
|
-
}
|
|
1289
|
-
acc[trimmed] = [];
|
|
1290
|
-
return acc;
|
|
1291
|
-
}, {});
|
|
1292
|
-
if (accept.startsWith(".")) return { "application/octet-stream": [accept] };
|
|
1293
|
-
return { [accept]: [] };
|
|
1294
|
-
}
|
|
1295
1270
|
async function uploadMediaFile(file, uploadUrl, httpClient) {
|
|
1296
1271
|
const body = new FormData();
|
|
1297
1272
|
body.append("file", file);
|
|
@@ -1371,17 +1346,6 @@ function FileUploadField({ field, disabled = false, readOnly = false, invalid =
|
|
|
1371
1346
|
t,
|
|
1372
1347
|
uploadUrl
|
|
1373
1348
|
]);
|
|
1374
|
-
const { getRootProps, getInputProps, isDragActive, open } = (0, react_dropzone.useDropzone)({
|
|
1375
|
-
accept: buildDropzoneAccept(field.accept),
|
|
1376
|
-
disabled: disabled || readOnly || status === "uploading",
|
|
1377
|
-
multiple: false,
|
|
1378
|
-
noClick: !!(previewUrl || fileName),
|
|
1379
|
-
noKeyboard: !!(previewUrl || fileName),
|
|
1380
|
-
onDrop: (acceptedFiles) => {
|
|
1381
|
-
const file = acceptedFiles[0];
|
|
1382
|
-
if (file) uploadFile(file);
|
|
1383
|
-
}
|
|
1384
|
-
});
|
|
1385
1349
|
const handleClear = () => {
|
|
1386
1350
|
revokeLocalPreview();
|
|
1387
1351
|
setPreviewUrl(null);
|
|
@@ -1391,104 +1355,35 @@ function FileUploadField({ field, disabled = false, readOnly = false, invalid =
|
|
|
1391
1355
|
setErrorMessage(null);
|
|
1392
1356
|
onCleared(field.name);
|
|
1393
1357
|
};
|
|
1394
|
-
const
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
className: "nb-form__file-upload-file-link",
|
|
1424
|
-
href: fileUrl,
|
|
1425
|
-
target: "_blank",
|
|
1426
|
-
rel: "noreferrer",
|
|
1427
|
-
onClick: (event) => event.stopPropagation(),
|
|
1428
|
-
children: t("form.fileUploadOpen")
|
|
1429
|
-
})]
|
|
1430
|
-
})]
|
|
1431
|
-
}),
|
|
1432
|
-
status === "uploading" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1433
|
-
className: "nb-form__file-upload-overlay",
|
|
1434
|
-
"aria-live": "polite",
|
|
1435
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1436
|
-
className: "nb-form__file-upload-spinner",
|
|
1437
|
-
"aria-hidden": "true"
|
|
1438
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("form.fileUploading") })]
|
|
1439
|
-
}),
|
|
1440
|
-
isInteractive && status !== "uploading" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1441
|
-
className: "nb-form__file-upload-actions",
|
|
1442
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1443
|
-
type: "button",
|
|
1444
|
-
className: "nb-form__file-upload-action",
|
|
1445
|
-
onClick: (event) => {
|
|
1446
|
-
event.stopPropagation();
|
|
1447
|
-
open();
|
|
1448
|
-
},
|
|
1449
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
|
|
1450
|
-
className: "ph ph-arrows-clockwise",
|
|
1451
|
-
"aria-hidden": "true"
|
|
1452
|
-
}), t("form.fileUploadReplace")]
|
|
1453
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1454
|
-
type: "button",
|
|
1455
|
-
className: "nb-form__file-upload-action nb-form__file-upload-action--danger",
|
|
1456
|
-
onClick: (event) => {
|
|
1457
|
-
event.stopPropagation();
|
|
1458
|
-
handleClear();
|
|
1459
|
-
},
|
|
1460
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
|
|
1461
|
-
className: "ph ph-trash",
|
|
1462
|
-
"aria-hidden": "true"
|
|
1463
|
-
}), t("form.fileUploadRemove")]
|
|
1464
|
-
})]
|
|
1465
|
-
})
|
|
1466
|
-
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1467
|
-
className: "nb-form__file-upload-placeholder",
|
|
1468
|
-
children: [
|
|
1469
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1470
|
-
className: "nb-form__file-upload-icon",
|
|
1471
|
-
"aria-hidden": "true",
|
|
1472
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { className: `ph ${placeholderIcon}` })
|
|
1473
|
-
}),
|
|
1474
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1475
|
-
className: "nb-form__file-upload-title",
|
|
1476
|
-
children: placeholderTitle
|
|
1477
|
-
}),
|
|
1478
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1479
|
-
className: "nb-form__file-upload-hint",
|
|
1480
|
-
children: placeholderHint
|
|
1481
|
-
})
|
|
1482
|
-
]
|
|
1483
|
-
})]
|
|
1484
|
-
}), errorMessage && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1485
|
-
className: "nb-form__file-upload-error",
|
|
1486
|
-
role: "alert",
|
|
1487
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
|
|
1488
|
-
className: "ph ph-warning-circle",
|
|
1489
|
-
"aria-hidden": "true"
|
|
1490
|
-
}), errorMessage]
|
|
1491
|
-
})]
|
|
1358
|
+
const value = {
|
|
1359
|
+
fileName,
|
|
1360
|
+
fileUrl,
|
|
1361
|
+
previewUrl
|
|
1362
|
+
};
|
|
1363
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.FileDropzone, {
|
|
1364
|
+
accept: field.accept,
|
|
1365
|
+
disabled,
|
|
1366
|
+
readOnly,
|
|
1367
|
+
invalid,
|
|
1368
|
+
image: imageMode,
|
|
1369
|
+
value,
|
|
1370
|
+
uploading: status === "uploading",
|
|
1371
|
+
error: errorMessage,
|
|
1372
|
+
inputId: `nb-form-${field.name}`,
|
|
1373
|
+
inputLabel: field.label,
|
|
1374
|
+
labels: {
|
|
1375
|
+
dropPrompt: t("form.fileUploadDrop"),
|
|
1376
|
+
prompt: t("form.fileUploadPrompt"),
|
|
1377
|
+
imagePrompt: t("form.imageUploadPrompt"),
|
|
1378
|
+
hint: t("form.fileUploadHint"),
|
|
1379
|
+
imageHint: t("form.imageUploadHint"),
|
|
1380
|
+
uploading: t("form.fileUploading"),
|
|
1381
|
+
replace: t("form.fileUploadReplace"),
|
|
1382
|
+
remove: t("form.fileUploadRemove"),
|
|
1383
|
+
open: t("form.fileUploadOpen")
|
|
1384
|
+
},
|
|
1385
|
+
onFileSelect: (file) => void uploadFile(file),
|
|
1386
|
+
onClear: handleClear
|
|
1492
1387
|
});
|
|
1493
1388
|
}
|
|
1494
1389
|
function isImageFileField(field) {
|
|
@@ -3671,6 +3566,7 @@ function normalizeFormData(data, fields, adapter = HydraAdapter, prependDataByFi
|
|
|
3671
3566
|
else row[field.name] = null;
|
|
3672
3567
|
}
|
|
3673
3568
|
if (field.type === "entity") normalizeEntityField(row, field, adapter, prependDataByField);
|
|
3569
|
+
if (field.type !== "entity" && field.type !== "file" && (Array.isArray(row[field.name]) || row[field.name] !== null && typeof row[field.name] === "object")) delete row[field.name];
|
|
3674
3570
|
});
|
|
3675
3571
|
return row;
|
|
3676
3572
|
}
|
|
@@ -4304,9 +4200,20 @@ function validateField(field, value, formData, t) {
|
|
|
4304
4200
|
}
|
|
4305
4201
|
return null;
|
|
4306
4202
|
}
|
|
4203
|
+
const DETAIL_CURRENCY_COL_WIDTH = 112;
|
|
4204
|
+
const DETAIL_NUMBER_COL_WIDTH = 96;
|
|
4205
|
+
function resolveDetailColWidth(field, colWidths) {
|
|
4206
|
+
if (colWidths[field.name] !== void 0) return colWidths[field.name];
|
|
4207
|
+
if (field.width !== void 0) {
|
|
4208
|
+
const parsed = typeof field.width === "number" ? field.width : Number.parseInt(String(field.width), 10);
|
|
4209
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
4210
|
+
}
|
|
4211
|
+
if (field.type === "currency") return DETAIL_CURRENCY_COL_WIDTH;
|
|
4212
|
+
if (field.type === "number") return DETAIL_NUMBER_COL_WIDTH;
|
|
4213
|
+
}
|
|
4307
4214
|
function DetailColumnGroup({ allowDeleting, fields, colWidths }) {
|
|
4308
4215
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("colgroup", { children: [fields.map((field) => {
|
|
4309
|
-
const width =
|
|
4216
|
+
const width = resolveDetailColWidth(field, colWidths);
|
|
4310
4217
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: width ? { width } : void 0 }, field.name);
|
|
4311
4218
|
}), allowDeleting && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { className: "nb-form__col-actions-col" })] });
|
|
4312
4219
|
}
|
|
@@ -4316,7 +4223,6 @@ function DetailSummaryFooter({ allowDeleting, colWidths, fields, rows, scrollRef
|
|
|
4316
4223
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4317
4224
|
ref: scrollRef,
|
|
4318
4225
|
className: "nb-form__detail-summary-wrap",
|
|
4319
|
-
"aria-hidden": "true",
|
|
4320
4226
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
4321
4227
|
className: "nb-form__detail-table nb-form__detail-summary-table",
|
|
4322
4228
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetailColumnGroup, {
|
|
@@ -4328,11 +4234,16 @@ function DetailSummaryFooter({ allowDeleting, colWidths, fields, rows, scrollRef
|
|
|
4328
4234
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [fields.map((field) => {
|
|
4329
4235
|
const item = itemsByColumn.get(field.name);
|
|
4330
4236
|
const align = item?.align ?? field.align;
|
|
4237
|
+
const alignItems = align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start";
|
|
4238
|
+
const colWidth = item ? resolveDetailColWidth(field, colWidths) : void 0;
|
|
4331
4239
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
4332
|
-
style:
|
|
4240
|
+
style: {
|
|
4241
|
+
...align ? { textAlign: align } : void 0,
|
|
4242
|
+
...colWidth ? { minWidth: colWidth } : void 0
|
|
4243
|
+
},
|
|
4333
4244
|
children: item && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4334
4245
|
className: "nb-form__detail-summary-cell",
|
|
4335
|
-
style: {
|
|
4246
|
+
style: { alignItems },
|
|
4336
4247
|
children: [item.label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4337
4248
|
className: "nb-form__detail-summary-label",
|
|
4338
4249
|
children: item.label
|
|
@@ -7221,6 +7132,22 @@ function useSmartCrudFields(fields, activeOperation, formData, roles) {
|
|
|
7221
7132
|
};
|
|
7222
7133
|
}
|
|
7223
7134
|
//#endregion
|
|
7135
|
+
//#region packages/crud/crud/applyFormDetailFormFieldOverrides.ts
|
|
7136
|
+
/**
|
|
7137
|
+
* Hides the header form field that mirrors `formDetail.propertyName`.
|
|
7138
|
+
* OneToMany collections serialized as object arrays must not render as a
|
|
7139
|
+
* plain text input ([object Object]) when line items are edited via formDetail.
|
|
7140
|
+
*/
|
|
7141
|
+
function applyFormDetailFormFieldOverrides(fields, formDetail) {
|
|
7142
|
+
const propertyName = formDetail?.propertyName?.trim();
|
|
7143
|
+
if (!propertyName) return fields;
|
|
7144
|
+
return fields.map((field) => field.name === propertyName ? {
|
|
7145
|
+
...field,
|
|
7146
|
+
visibleOnForm: false,
|
|
7147
|
+
hidden: true
|
|
7148
|
+
} : field);
|
|
7149
|
+
}
|
|
7150
|
+
//#endregion
|
|
7224
7151
|
//#region packages/crud/crud/fieldValidation.ts
|
|
7225
7152
|
var SmartCrudFieldContractError = class extends Error {
|
|
7226
7153
|
issues;
|
|
@@ -7663,6 +7590,7 @@ function SmartCrudPage({ resource, fieldOverrides, formRef, onSelectionChanged,
|
|
|
7663
7590
|
const { activeOperation, formData, handleFormDataChange, startCreate, startEdit, resetOperation } = useSmartCrudOperation(void 0, routingState);
|
|
7664
7591
|
const roles = useSmartCrudRoles();
|
|
7665
7592
|
const { gridFields, processedFields, computedValues } = useSmartCrudFields(fields, activeOperation, formData, (0, react.useMemo)(() => roles ?? [], [roles]));
|
|
7593
|
+
const formFields = (0, react.useMemo)(() => applyFormDetailFormFieldOverrides(processedFields, resolvedBaseResource.formDetail), [processedFields, resolvedBaseResource.formDetail]);
|
|
7666
7594
|
(0, _nubitio_core.useMercureSubscription)(resource.apiUrl, () => {
|
|
7667
7595
|
effectiveGridRef.current?.refresh();
|
|
7668
7596
|
}, resolvedBaseResource.mercure !== false);
|
|
@@ -7672,7 +7600,7 @@ function SmartCrudPage({ resource, fieldOverrides, formRef, onSelectionChanged,
|
|
|
7672
7600
|
...!hasManualFields ? { fields: gridFields } : {},
|
|
7673
7601
|
apiUrl: normalizedApiUrl,
|
|
7674
7602
|
fields: hasManualFields ? buildFields(resource.fields) : gridFields,
|
|
7675
|
-
formFields
|
|
7603
|
+
formFields,
|
|
7676
7604
|
formLayout: resolvedBaseResource.formLayout ?? inferredFormLayout,
|
|
7677
7605
|
_supportedOperations: supportedOperations
|
|
7678
7606
|
}), [
|
|
@@ -7681,7 +7609,7 @@ function SmartCrudPage({ resource, fieldOverrides, formRef, onSelectionChanged,
|
|
|
7681
7609
|
hasManualFields,
|
|
7682
7610
|
inferredFormLayout,
|
|
7683
7611
|
normalizedApiUrl,
|
|
7684
|
-
|
|
7612
|
+
formFields,
|
|
7685
7613
|
resolvedBaseResource,
|
|
7686
7614
|
resource.fields,
|
|
7687
7615
|
supportedOperations
|
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import React, { createContext, forwardRef, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
2
2
|
import { Route, useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
|
-
import { AppDialog, AppDropdown, Badge, Button, ConfirmDialog, DatePicker, DateRangePicker, Drawer, EmptyState, IconButton, Skeleton, Timeline, TimelineItem } from "@nubitio/ui";
|
|
4
|
+
import { AppDialog, AppDropdown, Badge, Button, ConfirmDialog, DatePicker, DateRangePicker, Drawer, EmptyState, FileDropzone, IconButton, Skeleton, Timeline, TimelineItem } from "@nubitio/ui";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { createCrudEvents, createScopedEventBus, getCoreCurrency, getCoreLocale, getCoreTimezone, useCoreHttpClient, useCoreRuntime, useCoreTranslation, useEvents, useMercureSubscription } from "@nubitio/core";
|
|
7
|
-
import { useDropzone } from "react-dropzone";
|
|
8
7
|
import { useQueryClient } from "@tanstack/react-query";
|
|
9
8
|
//#region packages/crud/crud/defineResource.ts
|
|
10
9
|
const stringResourceCache = /* @__PURE__ */ new Map();
|
|
@@ -1219,9 +1218,6 @@ const enumTypeModule = {
|
|
|
1219
1218
|
};
|
|
1220
1219
|
//#endregion
|
|
1221
1220
|
//#region packages/crud/form/FileUploadField.tsx
|
|
1222
|
-
function cx$1(...values) {
|
|
1223
|
-
return values.filter(Boolean).join(" ");
|
|
1224
|
-
}
|
|
1225
1221
|
function resolveMediaPath(media) {
|
|
1226
1222
|
if (!media) return null;
|
|
1227
1223
|
const path = media["path"];
|
|
@@ -1247,27 +1243,6 @@ function resolveMediaIri(uploadUrl, media) {
|
|
|
1247
1243
|
function isImageMimeType(mimeType) {
|
|
1248
1244
|
return !!mimeType && mimeType.startsWith("image/");
|
|
1249
1245
|
}
|
|
1250
|
-
function buildDropzoneAccept(accept) {
|
|
1251
|
-
if (!accept || accept === "*/*" || accept === "*") return void 0;
|
|
1252
|
-
if (accept === "image/*") return {
|
|
1253
|
-
"image/png": [".png"],
|
|
1254
|
-
"image/jpeg": [".jpg", ".jpeg"],
|
|
1255
|
-
"image/webp": [".webp"],
|
|
1256
|
-
"image/gif": [".gif"]
|
|
1257
|
-
};
|
|
1258
|
-
if (accept.includes(",")) return accept.split(",").reduce((acc, token) => {
|
|
1259
|
-
const trimmed = token.trim();
|
|
1260
|
-
if (!trimmed) return acc;
|
|
1261
|
-
if (trimmed.startsWith(".")) {
|
|
1262
|
-
acc["application/octet-stream"] = [...acc["application/octet-stream"] ?? [], trimmed];
|
|
1263
|
-
return acc;
|
|
1264
|
-
}
|
|
1265
|
-
acc[trimmed] = [];
|
|
1266
|
-
return acc;
|
|
1267
|
-
}, {});
|
|
1268
|
-
if (accept.startsWith(".")) return { "application/octet-stream": [accept] };
|
|
1269
|
-
return { [accept]: [] };
|
|
1270
|
-
}
|
|
1271
1246
|
async function uploadMediaFile(file, uploadUrl, httpClient) {
|
|
1272
1247
|
const body = new FormData();
|
|
1273
1248
|
body.append("file", file);
|
|
@@ -1347,17 +1322,6 @@ function FileUploadField({ field, disabled = false, readOnly = false, invalid =
|
|
|
1347
1322
|
t,
|
|
1348
1323
|
uploadUrl
|
|
1349
1324
|
]);
|
|
1350
|
-
const { getRootProps, getInputProps, isDragActive, open } = useDropzone({
|
|
1351
|
-
accept: buildDropzoneAccept(field.accept),
|
|
1352
|
-
disabled: disabled || readOnly || status === "uploading",
|
|
1353
|
-
multiple: false,
|
|
1354
|
-
noClick: !!(previewUrl || fileName),
|
|
1355
|
-
noKeyboard: !!(previewUrl || fileName),
|
|
1356
|
-
onDrop: (acceptedFiles) => {
|
|
1357
|
-
const file = acceptedFiles[0];
|
|
1358
|
-
if (file) uploadFile(file);
|
|
1359
|
-
}
|
|
1360
|
-
});
|
|
1361
1325
|
const handleClear = () => {
|
|
1362
1326
|
revokeLocalPreview();
|
|
1363
1327
|
setPreviewUrl(null);
|
|
@@ -1367,104 +1331,35 @@ function FileUploadField({ field, disabled = false, readOnly = false, invalid =
|
|
|
1367
1331
|
setErrorMessage(null);
|
|
1368
1332
|
onCleared(field.name);
|
|
1369
1333
|
};
|
|
1370
|
-
const
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
return /* @__PURE__ */
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
className: "nb-form__file-upload-file-link",
|
|
1400
|
-
href: fileUrl,
|
|
1401
|
-
target: "_blank",
|
|
1402
|
-
rel: "noreferrer",
|
|
1403
|
-
onClick: (event) => event.stopPropagation(),
|
|
1404
|
-
children: t("form.fileUploadOpen")
|
|
1405
|
-
})]
|
|
1406
|
-
})]
|
|
1407
|
-
}),
|
|
1408
|
-
status === "uploading" && /* @__PURE__ */ jsxs("div", {
|
|
1409
|
-
className: "nb-form__file-upload-overlay",
|
|
1410
|
-
"aria-live": "polite",
|
|
1411
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
1412
|
-
className: "nb-form__file-upload-spinner",
|
|
1413
|
-
"aria-hidden": "true"
|
|
1414
|
-
}), /* @__PURE__ */ jsx("span", { children: t("form.fileUploading") })]
|
|
1415
|
-
}),
|
|
1416
|
-
isInteractive && status !== "uploading" && /* @__PURE__ */ jsxs("div", {
|
|
1417
|
-
className: "nb-form__file-upload-actions",
|
|
1418
|
-
children: [/* @__PURE__ */ jsxs("button", {
|
|
1419
|
-
type: "button",
|
|
1420
|
-
className: "nb-form__file-upload-action",
|
|
1421
|
-
onClick: (event) => {
|
|
1422
|
-
event.stopPropagation();
|
|
1423
|
-
open();
|
|
1424
|
-
},
|
|
1425
|
-
children: [/* @__PURE__ */ jsx("i", {
|
|
1426
|
-
className: "ph ph-arrows-clockwise",
|
|
1427
|
-
"aria-hidden": "true"
|
|
1428
|
-
}), t("form.fileUploadReplace")]
|
|
1429
|
-
}), /* @__PURE__ */ jsxs("button", {
|
|
1430
|
-
type: "button",
|
|
1431
|
-
className: "nb-form__file-upload-action nb-form__file-upload-action--danger",
|
|
1432
|
-
onClick: (event) => {
|
|
1433
|
-
event.stopPropagation();
|
|
1434
|
-
handleClear();
|
|
1435
|
-
},
|
|
1436
|
-
children: [/* @__PURE__ */ jsx("i", {
|
|
1437
|
-
className: "ph ph-trash",
|
|
1438
|
-
"aria-hidden": "true"
|
|
1439
|
-
}), t("form.fileUploadRemove")]
|
|
1440
|
-
})]
|
|
1441
|
-
})
|
|
1442
|
-
] }) : /* @__PURE__ */ jsxs("div", {
|
|
1443
|
-
className: "nb-form__file-upload-placeholder",
|
|
1444
|
-
children: [
|
|
1445
|
-
/* @__PURE__ */ jsx("span", {
|
|
1446
|
-
className: "nb-form__file-upload-icon",
|
|
1447
|
-
"aria-hidden": "true",
|
|
1448
|
-
children: /* @__PURE__ */ jsx("i", { className: `ph ${placeholderIcon}` })
|
|
1449
|
-
}),
|
|
1450
|
-
/* @__PURE__ */ jsx("span", {
|
|
1451
|
-
className: "nb-form__file-upload-title",
|
|
1452
|
-
children: placeholderTitle
|
|
1453
|
-
}),
|
|
1454
|
-
/* @__PURE__ */ jsx("span", {
|
|
1455
|
-
className: "nb-form__file-upload-hint",
|
|
1456
|
-
children: placeholderHint
|
|
1457
|
-
})
|
|
1458
|
-
]
|
|
1459
|
-
})]
|
|
1460
|
-
}), errorMessage && /* @__PURE__ */ jsxs("span", {
|
|
1461
|
-
className: "nb-form__file-upload-error",
|
|
1462
|
-
role: "alert",
|
|
1463
|
-
children: [/* @__PURE__ */ jsx("i", {
|
|
1464
|
-
className: "ph ph-warning-circle",
|
|
1465
|
-
"aria-hidden": "true"
|
|
1466
|
-
}), errorMessage]
|
|
1467
|
-
})]
|
|
1334
|
+
const value = {
|
|
1335
|
+
fileName,
|
|
1336
|
+
fileUrl,
|
|
1337
|
+
previewUrl
|
|
1338
|
+
};
|
|
1339
|
+
return /* @__PURE__ */ jsx(FileDropzone, {
|
|
1340
|
+
accept: field.accept,
|
|
1341
|
+
disabled,
|
|
1342
|
+
readOnly,
|
|
1343
|
+
invalid,
|
|
1344
|
+
image: imageMode,
|
|
1345
|
+
value,
|
|
1346
|
+
uploading: status === "uploading",
|
|
1347
|
+
error: errorMessage,
|
|
1348
|
+
inputId: `nb-form-${field.name}`,
|
|
1349
|
+
inputLabel: field.label,
|
|
1350
|
+
labels: {
|
|
1351
|
+
dropPrompt: t("form.fileUploadDrop"),
|
|
1352
|
+
prompt: t("form.fileUploadPrompt"),
|
|
1353
|
+
imagePrompt: t("form.imageUploadPrompt"),
|
|
1354
|
+
hint: t("form.fileUploadHint"),
|
|
1355
|
+
imageHint: t("form.imageUploadHint"),
|
|
1356
|
+
uploading: t("form.fileUploading"),
|
|
1357
|
+
replace: t("form.fileUploadReplace"),
|
|
1358
|
+
remove: t("form.fileUploadRemove"),
|
|
1359
|
+
open: t("form.fileUploadOpen")
|
|
1360
|
+
},
|
|
1361
|
+
onFileSelect: (file) => void uploadFile(file),
|
|
1362
|
+
onClear: handleClear
|
|
1468
1363
|
});
|
|
1469
1364
|
}
|
|
1470
1365
|
function isImageFileField(field) {
|
|
@@ -3647,6 +3542,7 @@ function normalizeFormData(data, fields, adapter = HydraAdapter, prependDataByFi
|
|
|
3647
3542
|
else row[field.name] = null;
|
|
3648
3543
|
}
|
|
3649
3544
|
if (field.type === "entity") normalizeEntityField(row, field, adapter, prependDataByField);
|
|
3545
|
+
if (field.type !== "entity" && field.type !== "file" && (Array.isArray(row[field.name]) || row[field.name] !== null && typeof row[field.name] === "object")) delete row[field.name];
|
|
3650
3546
|
});
|
|
3651
3547
|
return row;
|
|
3652
3548
|
}
|
|
@@ -4280,9 +4176,20 @@ function validateField(field, value, formData, t) {
|
|
|
4280
4176
|
}
|
|
4281
4177
|
return null;
|
|
4282
4178
|
}
|
|
4179
|
+
const DETAIL_CURRENCY_COL_WIDTH = 112;
|
|
4180
|
+
const DETAIL_NUMBER_COL_WIDTH = 96;
|
|
4181
|
+
function resolveDetailColWidth(field, colWidths) {
|
|
4182
|
+
if (colWidths[field.name] !== void 0) return colWidths[field.name];
|
|
4183
|
+
if (field.width !== void 0) {
|
|
4184
|
+
const parsed = typeof field.width === "number" ? field.width : Number.parseInt(String(field.width), 10);
|
|
4185
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
4186
|
+
}
|
|
4187
|
+
if (field.type === "currency") return DETAIL_CURRENCY_COL_WIDTH;
|
|
4188
|
+
if (field.type === "number") return DETAIL_NUMBER_COL_WIDTH;
|
|
4189
|
+
}
|
|
4283
4190
|
function DetailColumnGroup({ allowDeleting, fields, colWidths }) {
|
|
4284
4191
|
return /* @__PURE__ */ jsxs("colgroup", { children: [fields.map((field) => {
|
|
4285
|
-
const width =
|
|
4192
|
+
const width = resolveDetailColWidth(field, colWidths);
|
|
4286
4193
|
return /* @__PURE__ */ jsx("col", { style: width ? { width } : void 0 }, field.name);
|
|
4287
4194
|
}), allowDeleting && /* @__PURE__ */ jsx("col", { className: "nb-form__col-actions-col" })] });
|
|
4288
4195
|
}
|
|
@@ -4292,7 +4199,6 @@ function DetailSummaryFooter({ allowDeleting, colWidths, fields, rows, scrollRef
|
|
|
4292
4199
|
return /* @__PURE__ */ jsx("div", {
|
|
4293
4200
|
ref: scrollRef,
|
|
4294
4201
|
className: "nb-form__detail-summary-wrap",
|
|
4295
|
-
"aria-hidden": "true",
|
|
4296
4202
|
children: /* @__PURE__ */ jsxs("table", {
|
|
4297
4203
|
className: "nb-form__detail-table nb-form__detail-summary-table",
|
|
4298
4204
|
children: [/* @__PURE__ */ jsx(DetailColumnGroup, {
|
|
@@ -4304,11 +4210,16 @@ function DetailSummaryFooter({ allowDeleting, colWidths, fields, rows, scrollRef
|
|
|
4304
4210
|
children: /* @__PURE__ */ jsxs("tr", { children: [fields.map((field) => {
|
|
4305
4211
|
const item = itemsByColumn.get(field.name);
|
|
4306
4212
|
const align = item?.align ?? field.align;
|
|
4213
|
+
const alignItems = align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start";
|
|
4214
|
+
const colWidth = item ? resolveDetailColWidth(field, colWidths) : void 0;
|
|
4307
4215
|
return /* @__PURE__ */ jsx("td", {
|
|
4308
|
-
style:
|
|
4216
|
+
style: {
|
|
4217
|
+
...align ? { textAlign: align } : void 0,
|
|
4218
|
+
...colWidth ? { minWidth: colWidth } : void 0
|
|
4219
|
+
},
|
|
4309
4220
|
children: item && /* @__PURE__ */ jsxs("div", {
|
|
4310
4221
|
className: "nb-form__detail-summary-cell",
|
|
4311
|
-
style: {
|
|
4222
|
+
style: { alignItems },
|
|
4312
4223
|
children: [item.label && /* @__PURE__ */ jsx("span", {
|
|
4313
4224
|
className: "nb-form__detail-summary-label",
|
|
4314
4225
|
children: item.label
|
|
@@ -7197,6 +7108,22 @@ function useSmartCrudFields(fields, activeOperation, formData, roles) {
|
|
|
7197
7108
|
};
|
|
7198
7109
|
}
|
|
7199
7110
|
//#endregion
|
|
7111
|
+
//#region packages/crud/crud/applyFormDetailFormFieldOverrides.ts
|
|
7112
|
+
/**
|
|
7113
|
+
* Hides the header form field that mirrors `formDetail.propertyName`.
|
|
7114
|
+
* OneToMany collections serialized as object arrays must not render as a
|
|
7115
|
+
* plain text input ([object Object]) when line items are edited via formDetail.
|
|
7116
|
+
*/
|
|
7117
|
+
function applyFormDetailFormFieldOverrides(fields, formDetail) {
|
|
7118
|
+
const propertyName = formDetail?.propertyName?.trim();
|
|
7119
|
+
if (!propertyName) return fields;
|
|
7120
|
+
return fields.map((field) => field.name === propertyName ? {
|
|
7121
|
+
...field,
|
|
7122
|
+
visibleOnForm: false,
|
|
7123
|
+
hidden: true
|
|
7124
|
+
} : field);
|
|
7125
|
+
}
|
|
7126
|
+
//#endregion
|
|
7200
7127
|
//#region packages/crud/crud/fieldValidation.ts
|
|
7201
7128
|
var SmartCrudFieldContractError = class extends Error {
|
|
7202
7129
|
issues;
|
|
@@ -7639,6 +7566,7 @@ function SmartCrudPage({ resource, fieldOverrides, formRef, onSelectionChanged,
|
|
|
7639
7566
|
const { activeOperation, formData, handleFormDataChange, startCreate, startEdit, resetOperation } = useSmartCrudOperation(void 0, routingState);
|
|
7640
7567
|
const roles = useSmartCrudRoles();
|
|
7641
7568
|
const { gridFields, processedFields, computedValues } = useSmartCrudFields(fields, activeOperation, formData, useMemo(() => roles ?? [], [roles]));
|
|
7569
|
+
const formFields = useMemo(() => applyFormDetailFormFieldOverrides(processedFields, resolvedBaseResource.formDetail), [processedFields, resolvedBaseResource.formDetail]);
|
|
7642
7570
|
useMercureSubscription(resource.apiUrl, () => {
|
|
7643
7571
|
effectiveGridRef.current?.refresh();
|
|
7644
7572
|
}, resolvedBaseResource.mercure !== false);
|
|
@@ -7648,7 +7576,7 @@ function SmartCrudPage({ resource, fieldOverrides, formRef, onSelectionChanged,
|
|
|
7648
7576
|
...!hasManualFields ? { fields: gridFields } : {},
|
|
7649
7577
|
apiUrl: normalizedApiUrl,
|
|
7650
7578
|
fields: hasManualFields ? buildFields(resource.fields) : gridFields,
|
|
7651
|
-
formFields
|
|
7579
|
+
formFields,
|
|
7652
7580
|
formLayout: resolvedBaseResource.formLayout ?? inferredFormLayout,
|
|
7653
7581
|
_supportedOperations: supportedOperations
|
|
7654
7582
|
}), [
|
|
@@ -7657,7 +7585,7 @@ function SmartCrudPage({ resource, fieldOverrides, formRef, onSelectionChanged,
|
|
|
7657
7585
|
hasManualFields,
|
|
7658
7586
|
inferredFormLayout,
|
|
7659
7587
|
normalizedApiUrl,
|
|
7660
|
-
|
|
7588
|
+
formFields,
|
|
7661
7589
|
resolvedBaseResource,
|
|
7662
7590
|
resource.fields,
|
|
7663
7591
|
supportedOperations
|
package/dist/style.css
CHANGED
|
@@ -1705,471 +1705,6 @@ html[data-density=compact] .nb-datagrid .nb-badge {
|
|
|
1705
1705
|
box-shadow: none;
|
|
1706
1706
|
}
|
|
1707
1707
|
}
|
|
1708
|
-
.nb-form__file-upload {
|
|
1709
|
-
display: flex;
|
|
1710
|
-
flex-direction: column;
|
|
1711
|
-
gap: var(--space-1);
|
|
1712
|
-
width: 100%;
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
.nb-form__file-upload-zone {
|
|
1716
|
-
align-items: center;
|
|
1717
|
-
background: var(--surface-1);
|
|
1718
|
-
border: 1px dashed var(--border-color);
|
|
1719
|
-
border-radius: var(--radius-lg);
|
|
1720
|
-
box-sizing: border-box;
|
|
1721
|
-
cursor: pointer;
|
|
1722
|
-
display: flex;
|
|
1723
|
-
justify-content: center;
|
|
1724
|
-
min-height: 112px;
|
|
1725
|
-
overflow: hidden;
|
|
1726
|
-
position: relative;
|
|
1727
|
-
transition: border-color var(--transition-base), background var(--transition-base), box-shadow var(--transition-base);
|
|
1728
|
-
width: 100%;
|
|
1729
|
-
}
|
|
1730
|
-
.nb-form__file-upload-zone:hover:not(.nb-form__file-upload-zone--disabled):not(.nb-form__file-upload-zone--filled) {
|
|
1731
|
-
background: color-mix(in srgb, var(--accent-color) 4%, var(--surface-1));
|
|
1732
|
-
border-color: var(--accent-color);
|
|
1733
|
-
}
|
|
1734
|
-
.nb-form__file-upload-zone--active {
|
|
1735
|
-
background: color-mix(in srgb, var(--accent-color) 8%, var(--surface-1));
|
|
1736
|
-
border-color: var(--accent-color);
|
|
1737
|
-
}
|
|
1738
|
-
.nb-form__file-upload-zone--filled {
|
|
1739
|
-
border-style: solid;
|
|
1740
|
-
cursor: default;
|
|
1741
|
-
min-height: 88px;
|
|
1742
|
-
}
|
|
1743
|
-
.nb-form__file-upload-zone--uploading {
|
|
1744
|
-
pointer-events: none;
|
|
1745
|
-
}
|
|
1746
|
-
.nb-form__file-upload-zone--disabled {
|
|
1747
|
-
cursor: not-allowed;
|
|
1748
|
-
opacity: 0.72;
|
|
1749
|
-
}
|
|
1750
|
-
.nb-form__file-upload-zone:focus-visible {
|
|
1751
|
-
box-shadow: 0 0 0 3px var(--focus-ring-color);
|
|
1752
|
-
outline: none;
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
.nb-form__file-upload--image .nb-form__file-upload-zone {
|
|
1756
|
-
min-height: 168px;
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
.nb-form__file-upload--image .nb-form__file-upload-zone--filled {
|
|
1760
|
-
min-height: 180px;
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
.nb-form__file-upload--invalid .nb-form__file-upload-zone {
|
|
1764
|
-
border-color: var(--error-color);
|
|
1765
|
-
}
|
|
1766
|
-
|
|
1767
|
-
.nb-form__file-upload-placeholder {
|
|
1768
|
-
align-items: center;
|
|
1769
|
-
display: flex;
|
|
1770
|
-
flex-direction: column;
|
|
1771
|
-
gap: var(--space-2);
|
|
1772
|
-
max-width: 320px;
|
|
1773
|
-
padding: var(--space-4);
|
|
1774
|
-
text-align: center;
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
.nb-form__file-upload-icon {
|
|
1778
|
-
align-items: center;
|
|
1779
|
-
background: color-mix(in srgb, var(--accent-color) 10%, transparent);
|
|
1780
|
-
border-radius: 999px;
|
|
1781
|
-
color: var(--accent-color);
|
|
1782
|
-
display: inline-flex;
|
|
1783
|
-
font-size: 24px;
|
|
1784
|
-
height: 48px;
|
|
1785
|
-
justify-content: center;
|
|
1786
|
-
width: 48px;
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
.nb-form__file-upload-title {
|
|
1790
|
-
color: var(--text-primary);
|
|
1791
|
-
font-size: var(--font-size-sm);
|
|
1792
|
-
font-weight: var(--font-weight-semibold);
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
.nb-form__file-upload-hint {
|
|
1796
|
-
color: var(--text-tertiary);
|
|
1797
|
-
font-size: var(--font-size-xs);
|
|
1798
|
-
line-height: var(--line-height-tight);
|
|
1799
|
-
}
|
|
1800
|
-
|
|
1801
|
-
.nb-form__file-upload-preview {
|
|
1802
|
-
display: block;
|
|
1803
|
-
height: 100%;
|
|
1804
|
-
max-height: 220px;
|
|
1805
|
-
object-fit: contain;
|
|
1806
|
-
width: 100%;
|
|
1807
|
-
}
|
|
1808
|
-
|
|
1809
|
-
.nb-form__file-upload-file {
|
|
1810
|
-
align-items: center;
|
|
1811
|
-
display: flex;
|
|
1812
|
-
gap: var(--space-3);
|
|
1813
|
-
max-width: 100%;
|
|
1814
|
-
padding: var(--space-3) var(--space-4);
|
|
1815
|
-
width: 100%;
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
.nb-form__file-upload-file-icon {
|
|
1819
|
-
align-items: center;
|
|
1820
|
-
background: var(--surface-0);
|
|
1821
|
-
border: 1px solid var(--border-subtle);
|
|
1822
|
-
border-radius: var(--radius-md);
|
|
1823
|
-
color: var(--accent-color);
|
|
1824
|
-
display: inline-flex;
|
|
1825
|
-
flex: 0 0 auto;
|
|
1826
|
-
font-size: 22px;
|
|
1827
|
-
height: 44px;
|
|
1828
|
-
justify-content: center;
|
|
1829
|
-
width: 44px;
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
.nb-form__file-upload-file-meta {
|
|
1833
|
-
display: flex;
|
|
1834
|
-
flex: 1 1 auto;
|
|
1835
|
-
flex-direction: column;
|
|
1836
|
-
gap: 2px;
|
|
1837
|
-
min-width: 0;
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
.nb-form__file-upload-file-name {
|
|
1841
|
-
color: var(--text-primary);
|
|
1842
|
-
font-size: var(--font-size-sm);
|
|
1843
|
-
font-weight: var(--font-weight-medium);
|
|
1844
|
-
overflow: hidden;
|
|
1845
|
-
text-overflow: ellipsis;
|
|
1846
|
-
white-space: nowrap;
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
.nb-form__file-upload-file-link {
|
|
1850
|
-
color: var(--accent-color);
|
|
1851
|
-
font-size: var(--font-size-xs);
|
|
1852
|
-
text-decoration: none;
|
|
1853
|
-
}
|
|
1854
|
-
.nb-form__file-upload-file-link:hover {
|
|
1855
|
-
text-decoration: underline;
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
.nb-form__file-upload-overlay {
|
|
1859
|
-
align-items: center;
|
|
1860
|
-
background: rgba(0, 0, 0, 0.42);
|
|
1861
|
-
color: #fff;
|
|
1862
|
-
display: flex;
|
|
1863
|
-
flex-direction: column;
|
|
1864
|
-
font-size: var(--font-size-sm);
|
|
1865
|
-
gap: var(--space-2);
|
|
1866
|
-
inset: 0;
|
|
1867
|
-
justify-content: center;
|
|
1868
|
-
position: absolute;
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
|
-
.nb-form__file-upload-spinner {
|
|
1872
|
-
animation: nb-form-file-spin 700ms linear infinite;
|
|
1873
|
-
border: 2px solid rgba(255, 255, 255, 0.35);
|
|
1874
|
-
border-radius: 999px;
|
|
1875
|
-
border-top-color: #fff;
|
|
1876
|
-
height: 24px;
|
|
1877
|
-
width: 24px;
|
|
1878
|
-
}
|
|
1879
|
-
|
|
1880
|
-
@keyframes nb-form-file-spin {
|
|
1881
|
-
to {
|
|
1882
|
-
transform: rotate(360deg);
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
.nb-form__file-upload-actions {
|
|
1886
|
-
align-items: center;
|
|
1887
|
-
background: linear-gradient(to top, rgba(0, 0, 0, 0.58), transparent);
|
|
1888
|
-
bottom: 0;
|
|
1889
|
-
display: flex;
|
|
1890
|
-
gap: var(--space-2);
|
|
1891
|
-
inset-inline: 0;
|
|
1892
|
-
justify-content: center;
|
|
1893
|
-
opacity: 0;
|
|
1894
|
-
padding: var(--space-3);
|
|
1895
|
-
position: absolute;
|
|
1896
|
-
transition: opacity var(--transition-base);
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
.nb-form__file-upload-zone--filled:hover .nb-form__file-upload-actions,
|
|
1900
|
-
.nb-form__file-upload-zone--filled:focus-within .nb-form__file-upload-actions {
|
|
1901
|
-
opacity: 1;
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
.nb-form__file-upload-action {
|
|
1905
|
-
align-items: center;
|
|
1906
|
-
background: var(--surface-1);
|
|
1907
|
-
border: 1px solid var(--border-subtle);
|
|
1908
|
-
border-radius: var(--radius-md);
|
|
1909
|
-
color: var(--text-primary);
|
|
1910
|
-
cursor: pointer;
|
|
1911
|
-
display: inline-flex;
|
|
1912
|
-
font: inherit;
|
|
1913
|
-
font-size: var(--font-size-xs);
|
|
1914
|
-
font-weight: var(--font-weight-medium);
|
|
1915
|
-
gap: var(--space-1);
|
|
1916
|
-
min-height: 28px;
|
|
1917
|
-
padding: 0 var(--space-2);
|
|
1918
|
-
transition: background var(--transition-base), border-color var(--transition-base), color var(--transition-base);
|
|
1919
|
-
}
|
|
1920
|
-
.nb-form__file-upload-action:hover {
|
|
1921
|
-
border-color: var(--accent-color);
|
|
1922
|
-
color: var(--accent-color);
|
|
1923
|
-
}
|
|
1924
|
-
.nb-form__file-upload-action--danger:hover {
|
|
1925
|
-
border-color: var(--error-color);
|
|
1926
|
-
color: var(--error-color);
|
|
1927
|
-
}
|
|
1928
|
-
.nb-form__file-upload-action:focus-visible {
|
|
1929
|
-
box-shadow: 0 0 0 2px var(--focus-ring-color);
|
|
1930
|
-
outline: none;
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
|
-
.nb-form__file-upload-error {
|
|
1934
|
-
align-items: center;
|
|
1935
|
-
color: var(--error-color);
|
|
1936
|
-
display: inline-flex;
|
|
1937
|
-
font-size: var(--font-size-xs);
|
|
1938
|
-
gap: var(--space-1);
|
|
1939
|
-
}
|
|
1940
|
-
.nb-form__file-upload {
|
|
1941
|
-
display: flex;
|
|
1942
|
-
flex-direction: column;
|
|
1943
|
-
gap: var(--space-1);
|
|
1944
|
-
width: 100%;
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
|
-
.nb-form__file-upload-zone {
|
|
1948
|
-
align-items: center;
|
|
1949
|
-
background: var(--surface-1);
|
|
1950
|
-
border: 1px dashed var(--border-color);
|
|
1951
|
-
border-radius: var(--radius-lg);
|
|
1952
|
-
box-sizing: border-box;
|
|
1953
|
-
cursor: pointer;
|
|
1954
|
-
display: flex;
|
|
1955
|
-
justify-content: center;
|
|
1956
|
-
min-height: 112px;
|
|
1957
|
-
overflow: hidden;
|
|
1958
|
-
position: relative;
|
|
1959
|
-
transition: border-color var(--transition-base), background var(--transition-base), box-shadow var(--transition-base);
|
|
1960
|
-
width: 100%;
|
|
1961
|
-
}
|
|
1962
|
-
.nb-form__file-upload-zone:hover:not(.nb-form__file-upload-zone--disabled):not(.nb-form__file-upload-zone--filled) {
|
|
1963
|
-
background: color-mix(in srgb, var(--accent-color) 4%, var(--surface-1));
|
|
1964
|
-
border-color: var(--accent-color);
|
|
1965
|
-
}
|
|
1966
|
-
.nb-form__file-upload-zone--active {
|
|
1967
|
-
background: color-mix(in srgb, var(--accent-color) 8%, var(--surface-1));
|
|
1968
|
-
border-color: var(--accent-color);
|
|
1969
|
-
}
|
|
1970
|
-
.nb-form__file-upload-zone--filled {
|
|
1971
|
-
border-style: solid;
|
|
1972
|
-
cursor: default;
|
|
1973
|
-
min-height: 88px;
|
|
1974
|
-
}
|
|
1975
|
-
.nb-form__file-upload-zone--uploading {
|
|
1976
|
-
pointer-events: none;
|
|
1977
|
-
}
|
|
1978
|
-
.nb-form__file-upload-zone--disabled {
|
|
1979
|
-
cursor: not-allowed;
|
|
1980
|
-
opacity: 0.72;
|
|
1981
|
-
}
|
|
1982
|
-
.nb-form__file-upload-zone:focus-visible {
|
|
1983
|
-
box-shadow: 0 0 0 3px var(--focus-ring-color);
|
|
1984
|
-
outline: none;
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
.nb-form__file-upload--image .nb-form__file-upload-zone {
|
|
1988
|
-
min-height: 168px;
|
|
1989
|
-
}
|
|
1990
|
-
|
|
1991
|
-
.nb-form__file-upload--image .nb-form__file-upload-zone--filled {
|
|
1992
|
-
min-height: 180px;
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
.nb-form__file-upload--invalid .nb-form__file-upload-zone {
|
|
1996
|
-
border-color: var(--error-color);
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
.nb-form__file-upload-placeholder {
|
|
2000
|
-
align-items: center;
|
|
2001
|
-
display: flex;
|
|
2002
|
-
flex-direction: column;
|
|
2003
|
-
gap: var(--space-2);
|
|
2004
|
-
max-width: 320px;
|
|
2005
|
-
padding: var(--space-4);
|
|
2006
|
-
text-align: center;
|
|
2007
|
-
}
|
|
2008
|
-
|
|
2009
|
-
.nb-form__file-upload-icon {
|
|
2010
|
-
align-items: center;
|
|
2011
|
-
background: color-mix(in srgb, var(--accent-color) 10%, transparent);
|
|
2012
|
-
border-radius: 999px;
|
|
2013
|
-
color: var(--accent-color);
|
|
2014
|
-
display: inline-flex;
|
|
2015
|
-
font-size: 24px;
|
|
2016
|
-
height: 48px;
|
|
2017
|
-
justify-content: center;
|
|
2018
|
-
width: 48px;
|
|
2019
|
-
}
|
|
2020
|
-
|
|
2021
|
-
.nb-form__file-upload-title {
|
|
2022
|
-
color: var(--text-primary);
|
|
2023
|
-
font-size: var(--font-size-sm);
|
|
2024
|
-
font-weight: var(--font-weight-semibold);
|
|
2025
|
-
}
|
|
2026
|
-
|
|
2027
|
-
.nb-form__file-upload-hint {
|
|
2028
|
-
color: var(--text-tertiary);
|
|
2029
|
-
font-size: var(--font-size-xs);
|
|
2030
|
-
line-height: var(--line-height-tight);
|
|
2031
|
-
}
|
|
2032
|
-
|
|
2033
|
-
.nb-form__file-upload-preview {
|
|
2034
|
-
display: block;
|
|
2035
|
-
height: 100%;
|
|
2036
|
-
max-height: 220px;
|
|
2037
|
-
object-fit: contain;
|
|
2038
|
-
width: 100%;
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
.nb-form__file-upload-file {
|
|
2042
|
-
align-items: center;
|
|
2043
|
-
display: flex;
|
|
2044
|
-
gap: var(--space-3);
|
|
2045
|
-
max-width: 100%;
|
|
2046
|
-
padding: var(--space-3) var(--space-4);
|
|
2047
|
-
width: 100%;
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2050
|
-
.nb-form__file-upload-file-icon {
|
|
2051
|
-
align-items: center;
|
|
2052
|
-
background: var(--surface-0);
|
|
2053
|
-
border: 1px solid var(--border-subtle);
|
|
2054
|
-
border-radius: var(--radius-md);
|
|
2055
|
-
color: var(--accent-color);
|
|
2056
|
-
display: inline-flex;
|
|
2057
|
-
flex: 0 0 auto;
|
|
2058
|
-
font-size: 22px;
|
|
2059
|
-
height: 44px;
|
|
2060
|
-
justify-content: center;
|
|
2061
|
-
width: 44px;
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
.nb-form__file-upload-file-meta {
|
|
2065
|
-
display: flex;
|
|
2066
|
-
flex: 1 1 auto;
|
|
2067
|
-
flex-direction: column;
|
|
2068
|
-
gap: 2px;
|
|
2069
|
-
min-width: 0;
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
.nb-form__file-upload-file-name {
|
|
2073
|
-
color: var(--text-primary);
|
|
2074
|
-
font-size: var(--font-size-sm);
|
|
2075
|
-
font-weight: var(--font-weight-medium);
|
|
2076
|
-
overflow: hidden;
|
|
2077
|
-
text-overflow: ellipsis;
|
|
2078
|
-
white-space: nowrap;
|
|
2079
|
-
}
|
|
2080
|
-
|
|
2081
|
-
.nb-form__file-upload-file-link {
|
|
2082
|
-
color: var(--accent-color);
|
|
2083
|
-
font-size: var(--font-size-xs);
|
|
2084
|
-
text-decoration: none;
|
|
2085
|
-
}
|
|
2086
|
-
.nb-form__file-upload-file-link:hover {
|
|
2087
|
-
text-decoration: underline;
|
|
2088
|
-
}
|
|
2089
|
-
|
|
2090
|
-
.nb-form__file-upload-overlay {
|
|
2091
|
-
align-items: center;
|
|
2092
|
-
background: rgba(0, 0, 0, 0.42);
|
|
2093
|
-
color: #fff;
|
|
2094
|
-
display: flex;
|
|
2095
|
-
flex-direction: column;
|
|
2096
|
-
font-size: var(--font-size-sm);
|
|
2097
|
-
gap: var(--space-2);
|
|
2098
|
-
inset: 0;
|
|
2099
|
-
justify-content: center;
|
|
2100
|
-
position: absolute;
|
|
2101
|
-
}
|
|
2102
|
-
|
|
2103
|
-
.nb-form__file-upload-spinner {
|
|
2104
|
-
animation: nb-form-file-spin 700ms linear infinite;
|
|
2105
|
-
border: 2px solid rgba(255, 255, 255, 0.35);
|
|
2106
|
-
border-radius: 999px;
|
|
2107
|
-
border-top-color: #fff;
|
|
2108
|
-
height: 24px;
|
|
2109
|
-
width: 24px;
|
|
2110
|
-
}
|
|
2111
|
-
|
|
2112
|
-
@keyframes nb-form-file-spin {
|
|
2113
|
-
to {
|
|
2114
|
-
transform: rotate(360deg);
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
.nb-form__file-upload-actions {
|
|
2118
|
-
align-items: center;
|
|
2119
|
-
background: linear-gradient(to top, rgba(0, 0, 0, 0.58), transparent);
|
|
2120
|
-
bottom: 0;
|
|
2121
|
-
display: flex;
|
|
2122
|
-
gap: var(--space-2);
|
|
2123
|
-
inset-inline: 0;
|
|
2124
|
-
justify-content: center;
|
|
2125
|
-
opacity: 0;
|
|
2126
|
-
padding: var(--space-3);
|
|
2127
|
-
position: absolute;
|
|
2128
|
-
transition: opacity var(--transition-base);
|
|
2129
|
-
}
|
|
2130
|
-
|
|
2131
|
-
.nb-form__file-upload-zone--filled:hover .nb-form__file-upload-actions,
|
|
2132
|
-
.nb-form__file-upload-zone--filled:focus-within .nb-form__file-upload-actions {
|
|
2133
|
-
opacity: 1;
|
|
2134
|
-
}
|
|
2135
|
-
|
|
2136
|
-
.nb-form__file-upload-action {
|
|
2137
|
-
align-items: center;
|
|
2138
|
-
background: var(--surface-1);
|
|
2139
|
-
border: 1px solid var(--border-subtle);
|
|
2140
|
-
border-radius: var(--radius-md);
|
|
2141
|
-
color: var(--text-primary);
|
|
2142
|
-
cursor: pointer;
|
|
2143
|
-
display: inline-flex;
|
|
2144
|
-
font: inherit;
|
|
2145
|
-
font-size: var(--font-size-xs);
|
|
2146
|
-
font-weight: var(--font-weight-medium);
|
|
2147
|
-
gap: var(--space-1);
|
|
2148
|
-
min-height: 28px;
|
|
2149
|
-
padding: 0 var(--space-2);
|
|
2150
|
-
transition: background var(--transition-base), border-color var(--transition-base), color var(--transition-base);
|
|
2151
|
-
}
|
|
2152
|
-
.nb-form__file-upload-action:hover {
|
|
2153
|
-
border-color: var(--accent-color);
|
|
2154
|
-
color: var(--accent-color);
|
|
2155
|
-
}
|
|
2156
|
-
.nb-form__file-upload-action--danger:hover {
|
|
2157
|
-
border-color: var(--error-color);
|
|
2158
|
-
color: var(--error-color);
|
|
2159
|
-
}
|
|
2160
|
-
.nb-form__file-upload-action:focus-visible {
|
|
2161
|
-
box-shadow: 0 0 0 2px var(--focus-ring-color);
|
|
2162
|
-
outline: none;
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
|
-
.nb-form__file-upload-error {
|
|
2166
|
-
align-items: center;
|
|
2167
|
-
color: var(--error-color);
|
|
2168
|
-
display: inline-flex;
|
|
2169
|
-
font-size: var(--font-size-xs);
|
|
2170
|
-
gap: var(--space-1);
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2173
1708
|
.nb-form {
|
|
2174
1709
|
color: var(--text-primary);
|
|
2175
1710
|
display: flex;
|
|
@@ -2926,6 +2461,7 @@ html[data-density=compact] .nb-datagrid .nb-badge {
|
|
|
2926
2461
|
font-size: var(--font-size-sm);
|
|
2927
2462
|
font-weight: var(--font-weight-semibold);
|
|
2928
2463
|
min-height: 38px;
|
|
2464
|
+
overflow: visible;
|
|
2929
2465
|
padding: 8px;
|
|
2930
2466
|
vertical-align: middle;
|
|
2931
2467
|
}
|
|
@@ -2949,7 +2485,8 @@ html[data-density=compact] .nb-datagrid .nb-badge {
|
|
|
2949
2485
|
background: var(--surface-2);
|
|
2950
2486
|
border-top: 0;
|
|
2951
2487
|
flex: 0 0 auto;
|
|
2952
|
-
overflow:
|
|
2488
|
+
overflow-x: auto;
|
|
2489
|
+
overflow-y: hidden;
|
|
2953
2490
|
scrollbar-width: none;
|
|
2954
2491
|
}
|
|
2955
2492
|
.nb-form__detail-summary-wrap::-webkit-scrollbar {
|
|
@@ -2962,10 +2499,12 @@ html[data-density=compact] .nb-datagrid .nb-badge {
|
|
|
2962
2499
|
}
|
|
2963
2500
|
|
|
2964
2501
|
.nb-form__detail-summary-cell {
|
|
2965
|
-
align-items:
|
|
2502
|
+
align-items: flex-end;
|
|
2966
2503
|
display: inline-flex;
|
|
2967
|
-
|
|
2504
|
+
flex-direction: column;
|
|
2505
|
+
gap: 2px;
|
|
2968
2506
|
min-width: 100%;
|
|
2507
|
+
overflow: visible;
|
|
2969
2508
|
}
|
|
2970
2509
|
|
|
2971
2510
|
.nb-form__detail-summary-label {
|
|
@@ -2978,9 +2517,12 @@ html[data-density=compact] .nb-datagrid .nb-badge {
|
|
|
2978
2517
|
|
|
2979
2518
|
.nb-form__detail-summary-value {
|
|
2980
2519
|
color: var(--text-primary);
|
|
2520
|
+
display: block;
|
|
2981
2521
|
font-size: var(--font-size-md);
|
|
2982
2522
|
font-weight: var(--font-weight-bold);
|
|
2523
|
+
max-width: none;
|
|
2983
2524
|
white-space: nowrap;
|
|
2525
|
+
width: max-content;
|
|
2984
2526
|
}
|
|
2985
2527
|
|
|
2986
2528
|
.nb-form__detail-table td .nb-form__control,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nubitio/crud",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Declarative CRUD engine with field DSL, forms, datagrids, RBAC, conditional logic and pluggable adapters (Hydra/REST).",
|
|
6
6
|
"license": "MIT",
|
|
@@ -56,10 +56,7 @@
|
|
|
56
56
|
"react-dom": "^19.0.0",
|
|
57
57
|
"react-i18next": "^14.0.0",
|
|
58
58
|
"react-router-dom": "^6.0.0",
|
|
59
|
-
"@nubitio/core": "^0.5.
|
|
60
|
-
"@nubitio/ui": "^0.5.
|
|
61
|
-
},
|
|
62
|
-
"dependencies": {
|
|
63
|
-
"react-dropzone": "^15.0.0"
|
|
59
|
+
"@nubitio/core": "^0.5.16",
|
|
60
|
+
"@nubitio/ui": "^0.5.16"
|
|
64
61
|
}
|
|
65
62
|
}
|