@nubitio/crud 0.5.14 → 0.5.15

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 CHANGED
@@ -3671,6 +3671,7 @@ function normalizeFormData(data, fields, adapter = HydraAdapter, prependDataByFi
3671
3671
  else row[field.name] = null;
3672
3672
  }
3673
3673
  if (field.type === "entity") normalizeEntityField(row, field, adapter, prependDataByField);
3674
+ 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
3675
  });
3675
3676
  return row;
3676
3677
  }
@@ -4304,9 +4305,20 @@ function validateField(field, value, formData, t) {
4304
4305
  }
4305
4306
  return null;
4306
4307
  }
4308
+ const DETAIL_CURRENCY_COL_WIDTH = 112;
4309
+ const DETAIL_NUMBER_COL_WIDTH = 96;
4310
+ function resolveDetailColWidth(field, colWidths) {
4311
+ if (colWidths[field.name] !== void 0) return colWidths[field.name];
4312
+ if (field.width !== void 0) {
4313
+ const parsed = typeof field.width === "number" ? field.width : Number.parseInt(String(field.width), 10);
4314
+ return Number.isFinite(parsed) ? parsed : void 0;
4315
+ }
4316
+ if (field.type === "currency") return DETAIL_CURRENCY_COL_WIDTH;
4317
+ if (field.type === "number") return DETAIL_NUMBER_COL_WIDTH;
4318
+ }
4307
4319
  function DetailColumnGroup({ allowDeleting, fields, colWidths }) {
4308
4320
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("colgroup", { children: [fields.map((field) => {
4309
- const width = colWidths[field.name] ?? field.width;
4321
+ const width = resolveDetailColWidth(field, colWidths);
4310
4322
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: width ? { width } : void 0 }, field.name);
4311
4323
  }), allowDeleting && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { className: "nb-form__col-actions-col" })] });
4312
4324
  }
@@ -4316,7 +4328,6 @@ function DetailSummaryFooter({ allowDeleting, colWidths, fields, rows, scrollRef
4316
4328
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4317
4329
  ref: scrollRef,
4318
4330
  className: "nb-form__detail-summary-wrap",
4319
- "aria-hidden": "true",
4320
4331
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
4321
4332
  className: "nb-form__detail-table nb-form__detail-summary-table",
4322
4333
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetailColumnGroup, {
@@ -4328,11 +4339,16 @@ function DetailSummaryFooter({ allowDeleting, colWidths, fields, rows, scrollRef
4328
4339
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [fields.map((field) => {
4329
4340
  const item = itemsByColumn.get(field.name);
4330
4341
  const align = item?.align ?? field.align;
4342
+ const alignItems = align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start";
4343
+ const colWidth = item ? resolveDetailColWidth(field, colWidths) : void 0;
4331
4344
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
4332
- style: align ? { textAlign: align } : void 0,
4345
+ style: {
4346
+ ...align ? { textAlign: align } : void 0,
4347
+ ...colWidth ? { minWidth: colWidth } : void 0
4348
+ },
4333
4349
  children: item && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4334
4350
  className: "nb-form__detail-summary-cell",
4335
- style: { justifyContent: align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start" },
4351
+ style: { alignItems },
4336
4352
  children: [item.label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4337
4353
  className: "nb-form__detail-summary-label",
4338
4354
  children: item.label
@@ -7221,6 +7237,22 @@ function useSmartCrudFields(fields, activeOperation, formData, roles) {
7221
7237
  };
7222
7238
  }
7223
7239
  //#endregion
7240
+ //#region packages/crud/crud/applyFormDetailFormFieldOverrides.ts
7241
+ /**
7242
+ * Hides the header form field that mirrors `formDetail.propertyName`.
7243
+ * OneToMany collections serialized as object arrays must not render as a
7244
+ * plain text input ([object Object]) when line items are edited via formDetail.
7245
+ */
7246
+ function applyFormDetailFormFieldOverrides(fields, formDetail) {
7247
+ const propertyName = formDetail?.propertyName?.trim();
7248
+ if (!propertyName) return fields;
7249
+ return fields.map((field) => field.name === propertyName ? {
7250
+ ...field,
7251
+ visibleOnForm: false,
7252
+ hidden: true
7253
+ } : field);
7254
+ }
7255
+ //#endregion
7224
7256
  //#region packages/crud/crud/fieldValidation.ts
7225
7257
  var SmartCrudFieldContractError = class extends Error {
7226
7258
  issues;
@@ -7663,6 +7695,7 @@ function SmartCrudPage({ resource, fieldOverrides, formRef, onSelectionChanged,
7663
7695
  const { activeOperation, formData, handleFormDataChange, startCreate, startEdit, resetOperation } = useSmartCrudOperation(void 0, routingState);
7664
7696
  const roles = useSmartCrudRoles();
7665
7697
  const { gridFields, processedFields, computedValues } = useSmartCrudFields(fields, activeOperation, formData, (0, react.useMemo)(() => roles ?? [], [roles]));
7698
+ const formFields = (0, react.useMemo)(() => applyFormDetailFormFieldOverrides(processedFields, resolvedBaseResource.formDetail), [processedFields, resolvedBaseResource.formDetail]);
7666
7699
  (0, _nubitio_core.useMercureSubscription)(resource.apiUrl, () => {
7667
7700
  effectiveGridRef.current?.refresh();
7668
7701
  }, resolvedBaseResource.mercure !== false);
@@ -7672,7 +7705,7 @@ function SmartCrudPage({ resource, fieldOverrides, formRef, onSelectionChanged,
7672
7705
  ...!hasManualFields ? { fields: gridFields } : {},
7673
7706
  apiUrl: normalizedApiUrl,
7674
7707
  fields: hasManualFields ? buildFields(resource.fields) : gridFields,
7675
- formFields: processedFields,
7708
+ formFields,
7676
7709
  formLayout: resolvedBaseResource.formLayout ?? inferredFormLayout,
7677
7710
  _supportedOperations: supportedOperations
7678
7711
  }), [
@@ -7681,7 +7714,7 @@ function SmartCrudPage({ resource, fieldOverrides, formRef, onSelectionChanged,
7681
7714
  hasManualFields,
7682
7715
  inferredFormLayout,
7683
7716
  normalizedApiUrl,
7684
- processedFields,
7717
+ formFields,
7685
7718
  resolvedBaseResource,
7686
7719
  resource.fields,
7687
7720
  supportedOperations
package/dist/index.mjs CHANGED
@@ -3647,6 +3647,7 @@ function normalizeFormData(data, fields, adapter = HydraAdapter, prependDataByFi
3647
3647
  else row[field.name] = null;
3648
3648
  }
3649
3649
  if (field.type === "entity") normalizeEntityField(row, field, adapter, prependDataByField);
3650
+ 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
3651
  });
3651
3652
  return row;
3652
3653
  }
@@ -4280,9 +4281,20 @@ function validateField(field, value, formData, t) {
4280
4281
  }
4281
4282
  return null;
4282
4283
  }
4284
+ const DETAIL_CURRENCY_COL_WIDTH = 112;
4285
+ const DETAIL_NUMBER_COL_WIDTH = 96;
4286
+ function resolveDetailColWidth(field, colWidths) {
4287
+ if (colWidths[field.name] !== void 0) return colWidths[field.name];
4288
+ if (field.width !== void 0) {
4289
+ const parsed = typeof field.width === "number" ? field.width : Number.parseInt(String(field.width), 10);
4290
+ return Number.isFinite(parsed) ? parsed : void 0;
4291
+ }
4292
+ if (field.type === "currency") return DETAIL_CURRENCY_COL_WIDTH;
4293
+ if (field.type === "number") return DETAIL_NUMBER_COL_WIDTH;
4294
+ }
4283
4295
  function DetailColumnGroup({ allowDeleting, fields, colWidths }) {
4284
4296
  return /* @__PURE__ */ jsxs("colgroup", { children: [fields.map((field) => {
4285
- const width = colWidths[field.name] ?? field.width;
4297
+ const width = resolveDetailColWidth(field, colWidths);
4286
4298
  return /* @__PURE__ */ jsx("col", { style: width ? { width } : void 0 }, field.name);
4287
4299
  }), allowDeleting && /* @__PURE__ */ jsx("col", { className: "nb-form__col-actions-col" })] });
4288
4300
  }
@@ -4292,7 +4304,6 @@ function DetailSummaryFooter({ allowDeleting, colWidths, fields, rows, scrollRef
4292
4304
  return /* @__PURE__ */ jsx("div", {
4293
4305
  ref: scrollRef,
4294
4306
  className: "nb-form__detail-summary-wrap",
4295
- "aria-hidden": "true",
4296
4307
  children: /* @__PURE__ */ jsxs("table", {
4297
4308
  className: "nb-form__detail-table nb-form__detail-summary-table",
4298
4309
  children: [/* @__PURE__ */ jsx(DetailColumnGroup, {
@@ -4304,11 +4315,16 @@ function DetailSummaryFooter({ allowDeleting, colWidths, fields, rows, scrollRef
4304
4315
  children: /* @__PURE__ */ jsxs("tr", { children: [fields.map((field) => {
4305
4316
  const item = itemsByColumn.get(field.name);
4306
4317
  const align = item?.align ?? field.align;
4318
+ const alignItems = align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start";
4319
+ const colWidth = item ? resolveDetailColWidth(field, colWidths) : void 0;
4307
4320
  return /* @__PURE__ */ jsx("td", {
4308
- style: align ? { textAlign: align } : void 0,
4321
+ style: {
4322
+ ...align ? { textAlign: align } : void 0,
4323
+ ...colWidth ? { minWidth: colWidth } : void 0
4324
+ },
4309
4325
  children: item && /* @__PURE__ */ jsxs("div", {
4310
4326
  className: "nb-form__detail-summary-cell",
4311
- style: { justifyContent: align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start" },
4327
+ style: { alignItems },
4312
4328
  children: [item.label && /* @__PURE__ */ jsx("span", {
4313
4329
  className: "nb-form__detail-summary-label",
4314
4330
  children: item.label
@@ -7197,6 +7213,22 @@ function useSmartCrudFields(fields, activeOperation, formData, roles) {
7197
7213
  };
7198
7214
  }
7199
7215
  //#endregion
7216
+ //#region packages/crud/crud/applyFormDetailFormFieldOverrides.ts
7217
+ /**
7218
+ * Hides the header form field that mirrors `formDetail.propertyName`.
7219
+ * OneToMany collections serialized as object arrays must not render as a
7220
+ * plain text input ([object Object]) when line items are edited via formDetail.
7221
+ */
7222
+ function applyFormDetailFormFieldOverrides(fields, formDetail) {
7223
+ const propertyName = formDetail?.propertyName?.trim();
7224
+ if (!propertyName) return fields;
7225
+ return fields.map((field) => field.name === propertyName ? {
7226
+ ...field,
7227
+ visibleOnForm: false,
7228
+ hidden: true
7229
+ } : field);
7230
+ }
7231
+ //#endregion
7200
7232
  //#region packages/crud/crud/fieldValidation.ts
7201
7233
  var SmartCrudFieldContractError = class extends Error {
7202
7234
  issues;
@@ -7639,6 +7671,7 @@ function SmartCrudPage({ resource, fieldOverrides, formRef, onSelectionChanged,
7639
7671
  const { activeOperation, formData, handleFormDataChange, startCreate, startEdit, resetOperation } = useSmartCrudOperation(void 0, routingState);
7640
7672
  const roles = useSmartCrudRoles();
7641
7673
  const { gridFields, processedFields, computedValues } = useSmartCrudFields(fields, activeOperation, formData, useMemo(() => roles ?? [], [roles]));
7674
+ const formFields = useMemo(() => applyFormDetailFormFieldOverrides(processedFields, resolvedBaseResource.formDetail), [processedFields, resolvedBaseResource.formDetail]);
7642
7675
  useMercureSubscription(resource.apiUrl, () => {
7643
7676
  effectiveGridRef.current?.refresh();
7644
7677
  }, resolvedBaseResource.mercure !== false);
@@ -7648,7 +7681,7 @@ function SmartCrudPage({ resource, fieldOverrides, formRef, onSelectionChanged,
7648
7681
  ...!hasManualFields ? { fields: gridFields } : {},
7649
7682
  apiUrl: normalizedApiUrl,
7650
7683
  fields: hasManualFields ? buildFields(resource.fields) : gridFields,
7651
- formFields: processedFields,
7684
+ formFields,
7652
7685
  formLayout: resolvedBaseResource.formLayout ?? inferredFormLayout,
7653
7686
  _supportedOperations: supportedOperations
7654
7687
  }), [
@@ -7657,7 +7690,7 @@ function SmartCrudPage({ resource, fieldOverrides, formRef, onSelectionChanged,
7657
7690
  hasManualFields,
7658
7691
  inferredFormLayout,
7659
7692
  normalizedApiUrl,
7660
- processedFields,
7693
+ formFields,
7661
7694
  resolvedBaseResource,
7662
7695
  resource.fields,
7663
7696
  supportedOperations
package/dist/style.css CHANGED
@@ -2926,6 +2926,7 @@ html[data-density=compact] .nb-datagrid .nb-badge {
2926
2926
  font-size: var(--font-size-sm);
2927
2927
  font-weight: var(--font-weight-semibold);
2928
2928
  min-height: 38px;
2929
+ overflow: visible;
2929
2930
  padding: 8px;
2930
2931
  vertical-align: middle;
2931
2932
  }
@@ -2949,7 +2950,8 @@ html[data-density=compact] .nb-datagrid .nb-badge {
2949
2950
  background: var(--surface-2);
2950
2951
  border-top: 0;
2951
2952
  flex: 0 0 auto;
2952
- overflow: hidden;
2953
+ overflow-x: auto;
2954
+ overflow-y: hidden;
2953
2955
  scrollbar-width: none;
2954
2956
  }
2955
2957
  .nb-form__detail-summary-wrap::-webkit-scrollbar {
@@ -2962,10 +2964,12 @@ html[data-density=compact] .nb-datagrid .nb-badge {
2962
2964
  }
2963
2965
 
2964
2966
  .nb-form__detail-summary-cell {
2965
- align-items: baseline;
2967
+ align-items: flex-end;
2966
2968
  display: inline-flex;
2967
- gap: var(--space-2);
2969
+ flex-direction: column;
2970
+ gap: 2px;
2968
2971
  min-width: 100%;
2972
+ overflow: visible;
2969
2973
  }
2970
2974
 
2971
2975
  .nb-form__detail-summary-label {
@@ -2978,9 +2982,12 @@ html[data-density=compact] .nb-datagrid .nb-badge {
2978
2982
 
2979
2983
  .nb-form__detail-summary-value {
2980
2984
  color: var(--text-primary);
2985
+ display: block;
2981
2986
  font-size: var(--font-size-md);
2982
2987
  font-weight: var(--font-weight-bold);
2988
+ max-width: none;
2983
2989
  white-space: nowrap;
2990
+ width: max-content;
2984
2991
  }
2985
2992
 
2986
2993
  .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.14",
3
+ "version": "0.5.15",
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,8 +56,8 @@
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.14",
60
- "@nubitio/ui": "^0.5.14"
59
+ "@nubitio/core": "^0.5.15",
60
+ "@nubitio/ui": "^0.5.15"
61
61
  },
62
62
  "dependencies": {
63
63
  "react-dropzone": "^15.0.0"