@asteby/metacore-runtime-react 25.1.5 → 26.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/dist/dialogs/dynamic-record.d.ts.map +1 -1
- package/dist/dialogs/dynamic-record.js +17 -6
- package/dist/dynamic-columns.d.ts.map +1 -1
- package/dist/dynamic-columns.js +2 -2
- package/dist/dynamic-relation.d.ts +9 -0
- package/dist/dynamic-relation.d.ts.map +1 -1
- package/dist/dynamic-relation.js +15 -2
- package/dist/dynamic-relations.d.ts.map +1 -1
- package/dist/dynamic-relations.js +6 -2
- package/dist/dynamic-select-field.d.ts +10 -7
- package/dist/dynamic-select-field.d.ts.map +1 -1
- package/dist/dynamic-select-field.js +17 -10
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/dynamic-relation-readonly.test.tsx +92 -0
- package/src/dialogs/dynamic-record.tsx +20 -9
- package/src/dynamic-columns.tsx +8 -1
- package/src/dynamic-relation.tsx +26 -0
- package/src/dynamic-relations.tsx +6 -0
- package/src/dynamic-select-field.tsx +25 -18
- package/src/types.ts +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 26.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ee5f7e8: feat(ui): InitialsAvatar — deterministic initials fallback for imageless references
|
|
8
|
+
|
|
9
|
+
New shared `InitialsAvatar` primitive: when a reference/option has no image it now
|
|
10
|
+
shows 1–2 uppercase initials on a background color derived deterministically from
|
|
11
|
+
the name (stable per name via the existing `optionColor` hash → curated palette),
|
|
12
|
+
instead of an empty placeholder box.
|
|
13
|
+
|
|
14
|
+
Wired into the three surfaces through ONE component so they never diverge: the
|
|
15
|
+
relation picker (`OptionThumb`/`OptionLead`), the dynamic-table relation cell
|
|
16
|
+
(`RelationCell`), and the read-only detail dialog. Existing image, icon, and color
|
|
17
|
+
rendering is unchanged; the avatar is purely the imageless fallback. Respects the
|
|
18
|
+
existing sizes (24px table, 22px detail, the picker's).
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- a8f94ba: fix(runtime-react): render `dynamic_select` por `type`, no solo por `ref`/`widget`
|
|
23
|
+
|
|
24
|
+
El renderer editable del `DynamicRecordDialog` solo pintaba el picker cuando
|
|
25
|
+
`getFieldRef(field)` o `field.widget === 'dynamic_select'`. Un campo con
|
|
26
|
+
`type: 'dynamic_select'` cuya fuente vive en `optionsConfig.source` (sin `ref`
|
|
27
|
+
FK — p. ej. `currency_code` → `currencies` / `POSOrgCurrency`) degradaba a un
|
|
28
|
+
input de texto libre. Se agrega `field.type === 'dynamic_select'` a la
|
|
29
|
+
condición; `DynamicSelectField` ya resuelve la fuente vía `resolveOptionsSource`
|
|
30
|
+
(ref o `optionsConfig.source`).
|
|
31
|
+
|
|
32
|
+
- Updated dependencies [ee5f7e8]
|
|
33
|
+
- @asteby/metacore-ui@2.11.0
|
|
34
|
+
|
|
35
|
+
## 25.2.0
|
|
36
|
+
|
|
37
|
+
### Minor Changes
|
|
38
|
+
|
|
39
|
+
- 62f273a: DynamicRelation/DynamicRelations: honor a `readonly` flag on the kernel relation metadata (`RelationMeta.readonly`, camelCase alias `readOnly`). When a relation is read-only the panel forces canCreate/canEdit/canDelete = false regardless of the perms the host passes, hiding the "Agregar" (Plus) button and the per-row edit (Pencil) / delete (Trash2) controls. Backwards compatible: when the flag is absent/false the panel keeps deriving its controls from the host props.
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- 62f273a: normalize-submit: prefer the explicit `nullable` field flag served by the kernel (v0.77.1+) when deciding to null an empty reference, falling back to the type-based heuristic for older hosts that don't serve the flag.
|
|
44
|
+
|
|
3
45
|
## 25.1.5
|
|
4
46
|
|
|
5
47
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-record.d.ts","sourceRoot":"","sources":["../../src/dialogs/dynamic-record.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"dynamic-record.d.ts","sourceRoot":"","sources":["../../src/dialogs/dynamic-record.tsx"],"names":[],"mappings":"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"}
|
|
@@ -629,6 +629,9 @@ function RelationViewValue({ field, value, record }) {
|
|
|
629
629
|
image: image ? getImageUrl(image) : null,
|
|
630
630
|
color: resolved?.color ?? null,
|
|
631
631
|
icon: resolved?.icon ?? null,
|
|
632
|
+
// Carry the label so an imageless reference falls back to its initials
|
|
633
|
+
// avatar (shared InitialsAvatar via OptionLead) instead of a blank lead.
|
|
634
|
+
label: label ?? '',
|
|
632
635
|
};
|
|
633
636
|
return (_jsxs("div", { className: "flex items-center gap-2 py-1", children: [_jsx(OptionLead, { option: lead, size: 24 }), _jsx("span", { className: "text-sm", children: label ?? '—' })] }));
|
|
634
637
|
}
|
|
@@ -876,11 +879,19 @@ export function EditField({ field, value, onChange, record }) {
|
|
|
876
879
|
if (field.type === 'image' || field.widget === 'upload') {
|
|
877
880
|
return _jsx(ImageUploadField, { field: field, value: value, onChange: onChange });
|
|
878
881
|
}
|
|
879
|
-
//
|
|
880
|
-
// `widget: 'dynamic_select'
|
|
881
|
-
//
|
|
882
|
-
//
|
|
883
|
-
|
|
882
|
+
// Dynamic pickers: a `ref` (kernel-derived belongs_to target), an explicit
|
|
883
|
+
// `widget: 'dynamic_select'`, OR a `type: 'dynamic_select'` (the kernel maps
|
|
884
|
+
// any column carrying an `optionsConfig` source — e.g. `currencies` /
|
|
885
|
+
// `POSOrgCurrency` — to this type) renders the SDK's async searchable picker,
|
|
886
|
+
// with option thumbnails and the inline-create "+", against the options
|
|
887
|
+
// endpoint resolved by `resolveOptionsSource` (ref → /api/options/<ref>,
|
|
888
|
+
// else optionsConfig.source → /api/options/<source>). Without the `type`
|
|
889
|
+
// check a source-backed picker with no `ref` silently degraded to a raw text
|
|
890
|
+
// input. Static inline `options` are handled by the enum <Select> branch below.
|
|
891
|
+
if ((getFieldRef(field) ||
|
|
892
|
+
field.type === 'dynamic_select' ||
|
|
893
|
+
field.widget === 'dynamic_select') &&
|
|
894
|
+
!field.options?.length) {
|
|
884
895
|
return (_jsx(DynamicSelectField, { field: field, value: value, onChange: onChange,
|
|
885
896
|
// Seed the trigger with the related record's NAME (from the
|
|
886
897
|
// backend-injected FK sibling, key without `_id`) so an existing
|
|
@@ -1016,6 +1027,6 @@ function SearchField({ field, value, onChange }) {
|
|
|
1016
1027
|
setSelectedLabel(itemLabel);
|
|
1017
1028
|
setOpen(false);
|
|
1018
1029
|
setQuery('');
|
|
1019
|
-
}, children: [isSelected && _jsx(Check, { className: "mr-2 h-3.5 w-3.5 shrink-0 text-primary" }),
|
|
1030
|
+
}, children: [isSelected && _jsx(Check, { className: "mr-2 h-3.5 w-3.5 shrink-0 text-primary" }), _jsx(OptionThumb, { image: item.image, name: itemLabel, size: 20 }), _jsxs("div", { className: "flex flex-col min-w-0 ml-2", children: [_jsx("span", { className: "truncate", children: itemLabel }), item.description && (_jsx("span", { className: "text-[11px] text-muted-foreground truncate", children: item.description }))] })] }, itemValue));
|
|
1020
1031
|
}) })) })] }) })] }));
|
|
1021
1032
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,UAAU,CAAA;AAwC9C,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE9D,OAAO,KAAK,EAER,iBAAiB,EACpB,MAAM,wBAAwB,CAAA;AAE/B,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AA0BD;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,gBAAgB,EAAE,cAAc,MAAM,KAAG,MACzB,CAAA;AAQrD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,gBAAgB,KAAG,MAAM,GAAG,SAG5D,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAC7B,KAAK,gBAAgB,EACrB,OAAO,OAAO,EACd,WAAW,MAAM,EACjB,SAAS,MAAM,KAChB,MAyBF,CAAA;AAyCD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAMlE,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAiB5D,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OACqB,CAAA;AAwFhF;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,KAAG,MAGnE,CAAA;AAED,6EAA6E;AAC7E,eAAO,MAAM,eAAe,2DAA4D,CAAA;AAExF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,GAClB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA6C5C;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAWtE,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAOtE,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,GAAI,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAG,MAOzE,CAAA;AAoJD;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAqmBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
|
package/dist/dynamic-columns.js
CHANGED
|
@@ -17,7 +17,7 @@ import { format } from 'date-fns';
|
|
|
17
17
|
import { es, enUS } from 'date-fns/locale';
|
|
18
18
|
import * as icons from 'lucide-react';
|
|
19
19
|
import { MoreHorizontal } from 'lucide-react';
|
|
20
|
-
import { Avatar, AvatarFallback, AvatarImage, Badge, Button, Checkbox, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@asteby/metacore-ui';
|
|
20
|
+
import { Avatar, AvatarFallback, AvatarImage, Badge, Button, Checkbox, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, InitialsAvatar, } from '@asteby/metacore-ui';
|
|
21
21
|
import { DataTableColumnHeader, FilterableColumnHeader, } from '@asteby/metacore-ui/data-table';
|
|
22
22
|
import { generateBadgeStyles, getInitials, relationChipStyles, } from '@asteby/metacore-ui/lib';
|
|
23
23
|
import { Progress } from './dialogs/_primitives';
|
|
@@ -376,7 +376,7 @@ const RelationCell = ({ col, row, getImageUrl }) => {
|
|
|
376
376
|
// alive yet stay visually distinct from option/status badges. Inline style
|
|
377
377
|
// (hex-derived) bypasses the host tailwind safelist.
|
|
378
378
|
const chipStyles = relationChipStyles(display, { isDark });
|
|
379
|
-
return (_jsxs("span", { className: "inline-flex max-w-[220px] items-center gap-1.5 rounded-md px-2 py-0.5 text-sm font-medium", style: chipStyles, title: subtitle ? `${display} · ${subtitle}` : display, children: [image
|
|
379
|
+
return (_jsxs("span", { className: "inline-flex max-w-[220px] items-center gap-1.5 rounded-md px-2 py-0.5 text-sm font-medium", style: chipStyles, title: subtitle ? `${display} · ${subtitle}` : display, children: [image ? (_jsx(RelationThumbnail, { src: image, alt: display, getImageUrl: getImageUrl, size: subtitle ? 24 : 18 })) : (_jsx(InitialsAvatar, { name: display, size: subtitle ? 24 : 18, rounded: "sm" })), subtitle ? (_jsxs("span", { className: "flex flex-col leading-tight min-w-0", children: [_jsx("span", { className: "truncate", children: display }), _jsx("span", { className: "truncate text-[0.7rem] opacity-70", children: subtitle })] })) : (_jsx("span", { className: "truncate", children: display }))] }));
|
|
380
380
|
};
|
|
381
381
|
/**
|
|
382
382
|
* Renders a SAP-style polymorphic source-document reference as a navigable
|
|
@@ -31,6 +31,15 @@ interface CommonProps {
|
|
|
31
31
|
canCreate?: boolean;
|
|
32
32
|
canDelete?: boolean;
|
|
33
33
|
canEdit?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Relación de solo lectura. Cuando es true fuerza canCreate/canEdit/canDelete
|
|
36
|
+
* = false (AND con lo que pase el host: readonly siempre gana), escondiendo el
|
|
37
|
+
* botón "Agregar", el ícono editar (Pencil) y el de eliminar (Trash2). Tolera
|
|
38
|
+
* el alias camelCase `readOnly`.
|
|
39
|
+
*/
|
|
40
|
+
readonly?: boolean;
|
|
41
|
+
/** Alias camelCase de `readonly`. */
|
|
42
|
+
readOnly?: boolean;
|
|
34
43
|
/** Strings traducibles. */
|
|
35
44
|
strings?: Partial<DynamicRelationStrings>;
|
|
36
45
|
/** Wrapper className. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-relation.d.ts","sourceRoot":"","sources":["../src/dynamic-relation.tsx"],"names":[],"mappings":"AA+DA,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EACH,kBAAkB,EAClB,uBAAuB,EACvB,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,aAAa,EACb,wBAAwB,EACxB,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,cAAc,GACjB,MAAM,4BAA4B,CAAA;AAEnC,MAAM,WAAW,sBAAsB;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,wBAAwB,EAAE,MAAM,CAAA;IAChC,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,MAAM,CAAA;IACzB,uBAAuB,EAAE,MAAM,CAAA;IAC/B,WAAW,EAAE,MAAM,CAAA;CACtB;AAiBD,UAAU,WAAW;IACjB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,2BAA2B;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACzC,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,6BAA8B,SAAQ,WAAW;IAC9D,IAAI,EAAE,aAAa,CAAA;IACnB,yFAAyF;IACzF,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,8BAA+B,SAAQ,WAAW;IAC/D,IAAI,EAAE,cAAc,CAAA;IACpB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAA;IAClB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,MAAM,oBAAoB,GAC1B,6BAA6B,GAC7B,8BAA8B,CAAA;AAEpC,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,+BAK1D"}
|
|
1
|
+
{"version":3,"file":"dynamic-relation.d.ts","sourceRoot":"","sources":["../src/dynamic-relation.tsx"],"names":[],"mappings":"AA+DA,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EACH,kBAAkB,EAClB,uBAAuB,EACvB,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,aAAa,EACb,wBAAwB,EACxB,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,cAAc,GACjB,MAAM,4BAA4B,CAAA;AAEnC,MAAM,WAAW,sBAAsB;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,wBAAwB,EAAE,MAAM,CAAA;IAChC,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,MAAM,CAAA;IACzB,uBAAuB,EAAE,MAAM,CAAA;IAC/B,WAAW,EAAE,MAAM,CAAA;CACtB;AAiBD,UAAU,WAAW;IACjB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,2BAA2B;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACzC,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,6BAA8B,SAAQ,WAAW;IAC9D,IAAI,EAAE,aAAa,CAAA;IACnB,yFAAyF;IACzF,KAAK,EAAE,MAAM,CAAA;IACb,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,8BAA+B,SAAQ,WAAW;IAC/D,IAAI,EAAE,cAAc,CAAA;IACpB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAA;IAClB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,MAAM,oBAAoB,GAC1B,6BAA6B,GAC7B,8BAA8B,CAAA;AAEpC,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,+BAK1D"}
|
package/dist/dynamic-relation.js
CHANGED
|
@@ -39,7 +39,15 @@ export function DynamicRelation(props) {
|
|
|
39
39
|
}
|
|
40
40
|
return _jsx(OneToManyRelation, { ...props });
|
|
41
41
|
}
|
|
42
|
-
function OneToManyRelation({ kind, model, foreignKey, parentId, filters, endpoint, hiddenColumns = [], canCreate = true, canDelete = true, canEdit = true, strings, className, onChange, }) {
|
|
42
|
+
function OneToManyRelation({ kind, model, foreignKey, parentId, filters, endpoint, hiddenColumns = [], canCreate = true, canDelete = true, canEdit = true, readonly, readOnly, strings, className, onChange, }) {
|
|
43
|
+
// Read-only relation always wins over host-passed perms (AND). Tolerates the
|
|
44
|
+
// camelCase alias the same way lock_rows/lockRows does.
|
|
45
|
+
const isReadonly = readonly === true || readOnly === true;
|
|
46
|
+
if (isReadonly) {
|
|
47
|
+
canCreate = false;
|
|
48
|
+
canDelete = false;
|
|
49
|
+
canEdit = false;
|
|
50
|
+
}
|
|
43
51
|
const api = useApi();
|
|
44
52
|
const getImageUrl = useImageUrl();
|
|
45
53
|
const timeZone = useTimeZone();
|
|
@@ -187,7 +195,12 @@ function OneToManyRelation({ kind, model, foreignKey, parentId, filters, endpoin
|
|
|
187
195
|
}) }, headerGroup.id))) }), _jsx(TableBody, { children: table.getRowModel().rows.map((row, idx) => (_jsx(TableRow, { children: row.getVisibleCells().map((cell) => (_jsx(TableCell, { style: cell.column.columnDef.size ? { width: cell.column.columnDef.size } : undefined, className: "py-2", children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))) }, relationRowKey(row.original, idx, foreignKey)))) })] }) })), _jsx(Dialog, { open: formOpen, onOpenChange: (open) => { setFormOpen(open); if (!open)
|
|
188
196
|
setEditingRow(null); }, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: editingRow ? labels.editLabel : labels.addLabel }) }), _jsx(DynamicForm, { fields: formFields, initialValues: editingRow || undefined, onSubmit: handleSubmit, onCancel: () => { setFormOpen(false); setEditingRow(null); }, submitLabel: labels.saveLabel, cancelLabel: labels.cancelLabel, disabled: submitting })] }) }), _jsx(AlertDialog, { open: !!rowToDelete, onOpenChange: (open) => !open && setRowToDelete(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: labels.confirmRemoveTitle }), _jsx(AlertDialogDescription, { children: labels.confirmRemoveDescription })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: submitting, children: labels.cancelLabel }), _jsx(AlertDialogAction, { onClick: (e) => { e.preventDefault(); handleDelete(); }, className: "bg-red-600 hover:bg-red-700", disabled: submitting, children: labels.removeLabel })] })] }) })] }));
|
|
189
197
|
}
|
|
190
|
-
function ManyToManyRelation({ kind, through, references, foreignKey, referencesKey, parentId, filters, pivotEndpoint, referencesEndpoint, displayKey, canCreate = true, canDelete = true, strings, className, onChange, }) {
|
|
198
|
+
function ManyToManyRelation({ kind, through, references, foreignKey, referencesKey, parentId, filters, pivotEndpoint, referencesEndpoint, displayKey, canCreate = true, canDelete = true, readonly, readOnly, strings, className, onChange, }) {
|
|
199
|
+
// Read-only relation always wins over host-passed perms (AND).
|
|
200
|
+
if (readonly === true || readOnly === true) {
|
|
201
|
+
canCreate = false;
|
|
202
|
+
canDelete = false;
|
|
203
|
+
}
|
|
191
204
|
const api = useApi();
|
|
192
205
|
const { getMetadata, setMetadata: cacheMetadata } = useMetadataCache();
|
|
193
206
|
const labels = { ...DEFAULT_STRINGS, ...(strings || {}) };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-relations.d.ts","sourceRoot":"","sources":["../src/dynamic-relations.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,MAAM,WAAW,qBAAqB;IAClC;;;OAGG;IACH,MAAM,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAA;IACzE,gEAAgE;IAChE,SAAS,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,CAAA;IAC5C;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACzC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAA;CAC9C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC3B,MAAM,EAAE;IAAE,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EACnD,WAAW,SAAO,GACnB,MAAM,GAAG,MAAM,GAAG,SAAS,CAM7B;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAChC,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE,aAAa,GAAG,OAAO,CAAC,EACrD,QAAQ,EAAE,MAAM,GAAG,MAAM,GAC1B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxB;AAOD,wBAAgB,gBAAgB,CAAC,EAC7B,MAAM,EACN,SAAS,EACT,WAAkB,EAClB,SAAS,EACT,cAAc,EACd,SAAgB,EAChB,SAAgB,EAChB,OAAc,EACd,OAAO,EACP,QAAQ,GACX,EAAE,qBAAqB,
|
|
1
|
+
{"version":3,"file":"dynamic-relations.d.ts","sourceRoot":"","sources":["../src/dynamic-relations.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,MAAM,WAAW,qBAAqB;IAClC;;;OAGG;IACH,MAAM,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAA;IACzE,gEAAgE;IAChE,SAAS,EAAE,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,CAAA;IAC5C;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACzC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAA;CAC9C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC3B,MAAM,EAAE;IAAE,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EACnD,WAAW,SAAO,GACnB,MAAM,GAAG,MAAM,GAAG,SAAS,CAM7B;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAChC,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE,aAAa,GAAG,OAAO,CAAC,EACrD,QAAQ,EAAE,MAAM,GAAG,MAAM,GAC1B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxB;AAOD,wBAAgB,gBAAgB,CAAC,EAC7B,MAAM,EACN,SAAS,EACT,WAAkB,EAClB,SAAS,EACT,cAAc,EACd,SAAgB,EAChB,SAAgB,EAChB,OAAc,EACd,OAAO,EACP,QAAQ,GACX,EAAE,qBAAqB,sCAkEvB"}
|
|
@@ -62,6 +62,10 @@ export function DynamicRelations({ record, relations, parentIdKey = 'id', classN
|
|
|
62
62
|
...(strings || {}),
|
|
63
63
|
...(rel.label ? { title: rel.label } : {}),
|
|
64
64
|
};
|
|
65
|
+
// A relation flagged read-only in the kernel metadata forces the
|
|
66
|
+
// panel's mutation controls off regardless of the host perms.
|
|
67
|
+
// Tolerates the camelCase alias.
|
|
68
|
+
const relReadonly = rel.readonly === true || rel.readOnly === true;
|
|
65
69
|
if (rel.kind === 'many_to_many') {
|
|
66
70
|
return (_jsx(DynamicRelation, { kind: "many_to_many", through: rel.through,
|
|
67
71
|
// The pivot's reference table is unknown from
|
|
@@ -69,8 +73,8 @@ export function DynamicRelations({ record, relations, parentIdKey = 'id', classN
|
|
|
69
73
|
// the panel degrades to the pivot list. Hosts with a
|
|
70
74
|
// declared `references` should render DynamicRelation
|
|
71
75
|
// directly. (one_to_many is the common detail-page case.)
|
|
72
|
-
references: rel.through, foreignKey: rel.foreign_key, parentId: parentId, filters: filters, className: panelClassName, canCreate: canCreate, canDelete: canDelete, strings: panelStrings, onChange: onChange ? () => onChange(rel) : undefined }, relationKey(rel, idx)));
|
|
76
|
+
references: rel.through, foreignKey: rel.foreign_key, parentId: parentId, filters: filters, className: panelClassName, canCreate: canCreate, canDelete: canDelete, readonly: relReadonly, strings: panelStrings, onChange: onChange ? () => onChange(rel) : undefined }, relationKey(rel, idx)));
|
|
73
77
|
}
|
|
74
|
-
return (_jsx(DynamicRelation, { kind: "one_to_many", model: rel.through, foreignKey: rel.foreign_key, parentId: parentId, filters: filters, className: panelClassName, canCreate: canCreate, canDelete: canDelete, canEdit: canEdit, strings: panelStrings, onChange: onChange ? () => onChange(rel) : undefined }, relationKey(rel, idx)));
|
|
78
|
+
return (_jsx(DynamicRelation, { kind: "one_to_many", model: rel.through, foreignKey: rel.foreign_key, parentId: parentId, filters: filters, className: panelClassName, canCreate: canCreate, canDelete: canDelete, canEdit: canEdit, readonly: relReadonly, strings: panelStrings, onChange: onChange ? () => onChange(rel) : undefined }, relationKey(rel, idx)));
|
|
75
79
|
}) }));
|
|
76
80
|
}
|
|
@@ -7,14 +7,17 @@ import type { ActionFieldDef } from './types';
|
|
|
7
7
|
*/
|
|
8
8
|
export declare const DEFAULT_DEPENDS_HINT = "Selecciona primero el campo del que depende";
|
|
9
9
|
/**
|
|
10
|
-
* Small square thumbnail for an option's `image`.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* Small square thumbnail for an option's `image`. When the option has no image
|
|
11
|
+
* it falls back to a deterministic initials avatar (shared `InitialsAvatar`)
|
|
12
|
+
* derived from `name`, so an imageless reference reads as a colored badge rather
|
|
13
|
+
* than an empty placeholder — and rows/triggers stay aligned. `size` is in
|
|
14
|
+
* pixels (kept small — 20–24px — so the picker reads as a list, not a gallery).
|
|
15
|
+
* Inline style for the box dimensions: arbitrary Tailwind classes from a
|
|
16
|
+
* federated addon don't always survive the host's class scan.
|
|
15
17
|
*/
|
|
16
|
-
export declare function OptionThumb({ image, size }: {
|
|
18
|
+
export declare function OptionThumb({ image, name, size, }: {
|
|
17
19
|
image?: string | null;
|
|
20
|
+
name?: string | null;
|
|
18
21
|
size?: number;
|
|
19
22
|
}): import("react").JSX.Element;
|
|
20
23
|
/**
|
|
@@ -23,7 +26,7 @@ export declare function OptionThumb({ image, size }: {
|
|
|
23
26
|
* Returns null when the option carries none, so plain text options stay plain.
|
|
24
27
|
*/
|
|
25
28
|
export declare function OptionLead({ option, size, }: {
|
|
26
|
-
option?: Pick<ResolvedOption, 'image' | 'color' | 'icon'> | null;
|
|
29
|
+
option?: Pick<ResolvedOption, 'image' | 'color' | 'icon' | 'label'> | null;
|
|
27
30
|
size?: number;
|
|
28
31
|
}): import("react").JSX.Element | null;
|
|
29
32
|
export interface DynamicSelectFieldProps {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-select-field.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dynamic-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-select-field.tsx"],"names":[],"mappings":"AAwCA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,gDAAgD,CAAA;AAEjF;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EACxB,KAAK,EACL,IAAI,EACJ,IAAS,GACZ,EAAE;IACC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,+BAoBA;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACvB,MAAM,EACN,IAAS,GACZ,EAAE;IACC,MAAM,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,CAAA;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,sCA4BA;AAqBD,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,GAAG,CAAA;IACV,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;IAC1B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,cAAc,GAAG,IAAI,CAAA;IAClC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,kBAAkB,CAAC,EAC/B,KAAK,EACL,KAAK,EACL,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,WAAW,EACX,QAAgB,GACnB,EAAE,uBAAuB,+BAwOzB;AAED,eAAe,kBAAkB,CAAA"}
|
|
@@ -23,8 +23,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
23
23
|
// value). A dedicated `?ids=` lookup is a follow-up; create flows — the common
|
|
24
24
|
// case — start empty and never hit this.
|
|
25
25
|
import { useEffect, useRef, useState } from 'react';
|
|
26
|
-
import { Button, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, Popover, PopoverContent, PopoverTrigger, } from '@asteby/metacore-ui/primitives';
|
|
27
|
-
import { Check, ChevronsUpDown,
|
|
26
|
+
import { Button, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, Popover, PopoverContent, PopoverTrigger, InitialsAvatar, } from '@asteby/metacore-ui/primitives';
|
|
27
|
+
import { Check, ChevronsUpDown, Loader2, Plus } from 'lucide-react';
|
|
28
28
|
import { resolveColorCss } from '@asteby/metacore-ui/lib';
|
|
29
29
|
import { DynamicIcon } from './dynamic-icon';
|
|
30
30
|
import { useOptionsResolver } from './use-options-resolver';
|
|
@@ -36,16 +36,18 @@ import { getDependsOn, getFieldRef, resolveOptionsSource } from './dynamic-form-
|
|
|
36
36
|
*/
|
|
37
37
|
export const DEFAULT_DEPENDS_HINT = 'Selecciona primero el campo del que depende';
|
|
38
38
|
/**
|
|
39
|
-
* Small square thumbnail for an option's `image`.
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
39
|
+
* Small square thumbnail for an option's `image`. When the option has no image
|
|
40
|
+
* it falls back to a deterministic initials avatar (shared `InitialsAvatar`)
|
|
41
|
+
* derived from `name`, so an imageless reference reads as a colored badge rather
|
|
42
|
+
* than an empty placeholder — and rows/triggers stay aligned. `size` is in
|
|
43
|
+
* pixels (kept small — 20–24px — so the picker reads as a list, not a gallery).
|
|
44
|
+
* Inline style for the box dimensions: arbitrary Tailwind classes from a
|
|
45
|
+
* federated addon don't always survive the host's class scan.
|
|
44
46
|
*/
|
|
45
|
-
export function OptionThumb({ image, size = 20 }) {
|
|
47
|
+
export function OptionThumb({ image, name, size = 20, }) {
|
|
46
48
|
const box = { width: size, height: size };
|
|
47
49
|
if (!image) {
|
|
48
|
-
return
|
|
50
|
+
return _jsx(InitialsAvatar, { name: name, size: size, rounded: "sm" });
|
|
49
51
|
}
|
|
50
52
|
return (_jsx("img", { src: image, alt: "", "aria-hidden": true, loading: "lazy", className: "shrink-0 rounded-sm object-cover", style: box,
|
|
51
53
|
// A broken image url shouldn't leave a torn-icon glyph; collapse to
|
|
@@ -63,13 +65,18 @@ export function OptionLead({ option, size = 20, }) {
|
|
|
63
65
|
if (!option)
|
|
64
66
|
return null;
|
|
65
67
|
if (option.image)
|
|
66
|
-
return _jsx(OptionThumb, { image: option.image, size: size });
|
|
68
|
+
return _jsx(OptionThumb, { image: option.image, name: option.label, size: size });
|
|
67
69
|
if (option.icon) {
|
|
68
70
|
return (_jsx("span", { className: "flex shrink-0 items-center justify-center", style: { width: size, height: size, color: option.color ? resolveColorCss(option.color) : undefined }, "aria-hidden": true, children: _jsx(DynamicIcon, { name: option.icon, className: "size-4" }) }));
|
|
69
71
|
}
|
|
70
72
|
if (option.color) {
|
|
71
73
|
return (_jsx("span", { className: "shrink-0 rounded-full", style: { width: Math.round(size * 0.5), height: Math.round(size * 0.5), background: resolveColorCss(option.color) }, "aria-hidden": true }));
|
|
72
74
|
}
|
|
75
|
+
// No image/icon/color: an imageless reference option. Show its initials
|
|
76
|
+
// (shared InitialsAvatar) so it stays visually aligned with the sibling
|
|
77
|
+
// options that DO carry a thumbnail, rather than a blank gap.
|
|
78
|
+
if (option.label)
|
|
79
|
+
return _jsx(InitialsAvatar, { name: option.label, size: size, rounded: "sm" });
|
|
73
80
|
return null;
|
|
74
81
|
}
|
|
75
82
|
/** True when any option (or the selected one) carries a renderable visual. */
|
package/dist/types.d.ts
CHANGED
|
@@ -158,6 +158,16 @@ export interface RelationMeta {
|
|
|
158
158
|
scope?: Record<string, string>;
|
|
159
159
|
/** Human-readable panel header. */
|
|
160
160
|
label?: string;
|
|
161
|
+
/**
|
|
162
|
+
* Read-only relation: when true the SDK forces canCreate/canEdit/canDelete
|
|
163
|
+
* = false on the rendered `<DynamicRelation>` panel, hiding the "Agregar"
|
|
164
|
+
* button and the per-row edit (Pencil) / delete (Trash2) controls — no
|
|
165
|
+
* matter what perms the host passes. Generic & domain-agnostic. Read from
|
|
166
|
+
* the kernel relation metadata. Tolerates the camelCase alias.
|
|
167
|
+
*/
|
|
168
|
+
readonly?: boolean;
|
|
169
|
+
/** camelCase alias for `readonly`. */
|
|
170
|
+
readOnly?: boolean;
|
|
161
171
|
}
|
|
162
172
|
export interface FilterDefinition {
|
|
163
173
|
key: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asteby/metacore-runtime-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "26.0.0",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"sonner": ">=1.7",
|
|
39
39
|
"zustand": ">=5",
|
|
40
40
|
"@asteby/metacore-sdk": "^3.3.0",
|
|
41
|
-
"@asteby/metacore-ui": "^2.
|
|
41
|
+
"@asteby/metacore-ui": "^2.11.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependenciesMeta": {
|
|
44
44
|
"@tanstack/react-router": {
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"vitest": "^4.0.0",
|
|
69
69
|
"zustand": "^5.0.0",
|
|
70
70
|
"@asteby/metacore-sdk": "3.3.0",
|
|
71
|
-
"@asteby/metacore-ui": "2.
|
|
71
|
+
"@asteby/metacore-ui": "2.11.0"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
74
|
"build": "tsc -p tsconfig.json",
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
//
|
|
3
|
+
// `readonly` on a relation fixes the panel read-only: the SDK forces
|
|
4
|
+
// canCreate/canEdit/canDelete = false regardless of the perms the host passes,
|
|
5
|
+
// hiding the "Agregar" (Plus) button and the per-row edit (Pencil) / delete
|
|
6
|
+
// (Trash2) controls. Generic framework primitive — the flag rides on the
|
|
7
|
+
// kernel relation metadata (RelationMeta.readonly, camelCase alias readOnly).
|
|
8
|
+
//
|
|
9
|
+
// The one_to_many list renders plain metadata-driven cells (no Radix Select),
|
|
10
|
+
// so the happy-dom BubbleSelect value-reset gotcha does not apply here.
|
|
11
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
12
|
+
import { cleanup, render, screen, waitFor } from '@testing-library/react'
|
|
13
|
+
import { DynamicRelation } from '../dynamic-relation'
|
|
14
|
+
import { ApiProvider, type ApiClient } from '../api-context'
|
|
15
|
+
|
|
16
|
+
afterEach(cleanup)
|
|
17
|
+
|
|
18
|
+
const META = {
|
|
19
|
+
name: 'line_item',
|
|
20
|
+
columns: [
|
|
21
|
+
{ key: 'id', label: 'ID', type: 'text', sortable: true, filterable: false, hidden: true },
|
|
22
|
+
{ key: 'invoice_id', label: 'Factura', type: 'text', sortable: false, filterable: false },
|
|
23
|
+
{ key: 'sku', label: 'SKU', type: 'text', sortable: true, filterable: true },
|
|
24
|
+
{ key: 'qty', label: 'Cantidad', type: 'number', sortable: true, filterable: false },
|
|
25
|
+
],
|
|
26
|
+
actions: [],
|
|
27
|
+
hasActions: false,
|
|
28
|
+
enableCRUDActions: false,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const DATA = [
|
|
32
|
+
{ id: 'li_1', invoice_id: 'inv_42', sku: 'A-1', qty: 2 },
|
|
33
|
+
{ id: 'li_2', invoice_id: 'inv_42', sku: 'B-2', qty: 5 },
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
function mockApi(): ApiClient {
|
|
37
|
+
return {
|
|
38
|
+
get: vi.fn((url: string) => {
|
|
39
|
+
if (url.startsWith('/metadata/table/')) {
|
|
40
|
+
return Promise.resolve({ data: { success: true, data: META } })
|
|
41
|
+
}
|
|
42
|
+
return Promise.resolve({ data: { success: true, data: DATA } })
|
|
43
|
+
}),
|
|
44
|
+
post: vi.fn(() => Promise.resolve({ data: { success: true, data: {} } })),
|
|
45
|
+
put: vi.fn(() => Promise.resolve({ data: { success: true, data: {} } })),
|
|
46
|
+
delete: vi.fn(() => Promise.resolve({ data: { success: true, data: {} } })),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function renderPanel(extra: Record<string, unknown>) {
|
|
51
|
+
return render(
|
|
52
|
+
<ApiProvider client={mockApi()}>
|
|
53
|
+
<DynamicRelation
|
|
54
|
+
kind="one_to_many"
|
|
55
|
+
model="line_item"
|
|
56
|
+
foreignKey="invoice_id"
|
|
57
|
+
parentId="inv_42"
|
|
58
|
+
{...extra}
|
|
59
|
+
/>
|
|
60
|
+
</ApiProvider>,
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe('DynamicRelation — readonly', () => {
|
|
65
|
+
it('con readonly=true esconde "Agregar", Pencil y Trash2 aunque el host pase canCreate/canEdit/canDelete=true', async () => {
|
|
66
|
+
renderPanel({ readonly: true, canCreate: true, canEdit: true, canDelete: true })
|
|
67
|
+
|
|
68
|
+
// Rows loaded → cells rendered.
|
|
69
|
+
await waitFor(() => expect(screen.getByText('A-1')).toBeTruthy())
|
|
70
|
+
|
|
71
|
+
// No add control, no per-row edit/delete controls.
|
|
72
|
+
expect(screen.queryByText('Agregar')).toBeNull()
|
|
73
|
+
expect(screen.queryByLabelText('Editar')).toBeNull()
|
|
74
|
+
expect(screen.queryByLabelText('Quitar')).toBeNull()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('acepta el alias camelCase readOnly', async () => {
|
|
78
|
+
renderPanel({ readOnly: true })
|
|
79
|
+
await waitFor(() => expect(screen.getByText('A-1')).toBeTruthy())
|
|
80
|
+
expect(screen.queryByText('Agregar')).toBeNull()
|
|
81
|
+
expect(screen.queryByLabelText('Editar')).toBeNull()
|
|
82
|
+
expect(screen.queryByLabelText('Quitar')).toBeNull()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('control: sin readonly muestra "Agregar", Pencil y Trash2', async () => {
|
|
86
|
+
renderPanel({})
|
|
87
|
+
await waitFor(() => expect(screen.getByText('A-1')).toBeTruthy())
|
|
88
|
+
expect(screen.getByText('Agregar')).toBeTruthy()
|
|
89
|
+
expect(screen.getAllByLabelText('Editar')).toHaveLength(2)
|
|
90
|
+
expect(screen.getAllByLabelText('Quitar')).toHaveLength(2)
|
|
91
|
+
})
|
|
92
|
+
})
|
|
@@ -1054,10 +1054,13 @@ function RelationViewValue({ field, value, record }: { field: FieldDef; value: a
|
|
|
1054
1054
|
return <p className="text-sm py-1 text-muted-foreground">—</p>
|
|
1055
1055
|
}
|
|
1056
1056
|
|
|
1057
|
-
const lead: Pick<ResolvedOption, 'image' | 'color' | 'icon'> = {
|
|
1057
|
+
const lead: Pick<ResolvedOption, 'image' | 'color' | 'icon' | 'label'> = {
|
|
1058
1058
|
image: image ? getImageUrl(image) : null,
|
|
1059
1059
|
color: resolved?.color ?? null,
|
|
1060
1060
|
icon: resolved?.icon ?? null,
|
|
1061
|
+
// Carry the label so an imageless reference falls back to its initials
|
|
1062
|
+
// avatar (shared InitialsAvatar via OptionLead) instead of a blank lead.
|
|
1063
|
+
label: label ?? '',
|
|
1061
1064
|
}
|
|
1062
1065
|
|
|
1063
1066
|
return (
|
|
@@ -1540,11 +1543,21 @@ export function EditField({ field, value, onChange, record }: {
|
|
|
1540
1543
|
return <ImageUploadField field={field} value={value} onChange={onChange} />
|
|
1541
1544
|
}
|
|
1542
1545
|
|
|
1543
|
-
//
|
|
1544
|
-
// `widget: 'dynamic_select'
|
|
1545
|
-
//
|
|
1546
|
-
//
|
|
1547
|
-
|
|
1546
|
+
// Dynamic pickers: a `ref` (kernel-derived belongs_to target), an explicit
|
|
1547
|
+
// `widget: 'dynamic_select'`, OR a `type: 'dynamic_select'` (the kernel maps
|
|
1548
|
+
// any column carrying an `optionsConfig` source — e.g. `currencies` /
|
|
1549
|
+
// `POSOrgCurrency` — to this type) renders the SDK's async searchable picker,
|
|
1550
|
+
// with option thumbnails and the inline-create "+", against the options
|
|
1551
|
+
// endpoint resolved by `resolveOptionsSource` (ref → /api/options/<ref>,
|
|
1552
|
+
// else optionsConfig.source → /api/options/<source>). Without the `type`
|
|
1553
|
+
// check a source-backed picker with no `ref` silently degraded to a raw text
|
|
1554
|
+
// input. Static inline `options` are handled by the enum <Select> branch below.
|
|
1555
|
+
if (
|
|
1556
|
+
(getFieldRef(field as ActionFieldDef) ||
|
|
1557
|
+
field.type === 'dynamic_select' ||
|
|
1558
|
+
field.widget === 'dynamic_select') &&
|
|
1559
|
+
!field.options?.length
|
|
1560
|
+
) {
|
|
1548
1561
|
return (
|
|
1549
1562
|
<DynamicSelectField
|
|
1550
1563
|
field={field as ActionFieldDef}
|
|
@@ -1817,9 +1830,7 @@ function SearchField({ field, value, onChange }: { field: FieldDef; value: any;
|
|
|
1817
1830
|
}}
|
|
1818
1831
|
>
|
|
1819
1832
|
{isSelected && <Check className="mr-2 h-3.5 w-3.5 shrink-0 text-primary" />}
|
|
1820
|
-
{item.image
|
|
1821
|
-
<OptionThumb image={item.image} size={20} />
|
|
1822
|
-
)}
|
|
1833
|
+
<OptionThumb image={item.image} name={itemLabel} size={20} />
|
|
1823
1834
|
<div className="flex flex-col min-w-0 ml-2">
|
|
1824
1835
|
<span className="truncate">{itemLabel}</span>
|
|
1825
1836
|
{item.description && (
|
package/src/dynamic-columns.tsx
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
DropdownMenuContent,
|
|
30
30
|
DropdownMenuItem,
|
|
31
31
|
DropdownMenuTrigger,
|
|
32
|
+
InitialsAvatar,
|
|
32
33
|
} from '@asteby/metacore-ui'
|
|
33
34
|
import {
|
|
34
35
|
DataTableColumnHeader,
|
|
@@ -510,8 +511,14 @@ const RelationCell: React.FC<{
|
|
|
510
511
|
style={chipStyles}
|
|
511
512
|
title={subtitle ? `${display} · ${subtitle}` : display}
|
|
512
513
|
>
|
|
513
|
-
{image
|
|
514
|
+
{image ? (
|
|
514
515
|
<RelationThumbnail src={image} alt={display} getImageUrl={getImageUrl} size={subtitle ? 24 : 18} />
|
|
516
|
+
) : (
|
|
517
|
+
// No image on the resolved sibling: a deterministic initials avatar
|
|
518
|
+
// (shared InitialsAvatar) keyed on the label, so the "producto"-style
|
|
519
|
+
// cross-addon relation reads as a colored badge instead of text-only —
|
|
520
|
+
// matching the picker and detail surfaces.
|
|
521
|
+
<InitialsAvatar name={display} size={subtitle ? 24 : 18} rounded="sm" />
|
|
515
522
|
)}
|
|
516
523
|
{subtitle ? (
|
|
517
524
|
<span className="flex flex-col leading-tight min-w-0">
|
package/src/dynamic-relation.tsx
CHANGED
|
@@ -123,6 +123,15 @@ interface CommonProps {
|
|
|
123
123
|
canCreate?: boolean
|
|
124
124
|
canDelete?: boolean
|
|
125
125
|
canEdit?: boolean
|
|
126
|
+
/**
|
|
127
|
+
* Relación de solo lectura. Cuando es true fuerza canCreate/canEdit/canDelete
|
|
128
|
+
* = false (AND con lo que pase el host: readonly siempre gana), escondiendo el
|
|
129
|
+
* botón "Agregar", el ícono editar (Pencil) y el de eliminar (Trash2). Tolera
|
|
130
|
+
* el alias camelCase `readOnly`.
|
|
131
|
+
*/
|
|
132
|
+
readonly?: boolean
|
|
133
|
+
/** Alias camelCase de `readonly`. */
|
|
134
|
+
readOnly?: boolean
|
|
126
135
|
/** Strings traducibles. */
|
|
127
136
|
strings?: Partial<DynamicRelationStrings>
|
|
128
137
|
/** Wrapper className. */
|
|
@@ -184,10 +193,20 @@ function OneToManyRelation({
|
|
|
184
193
|
canCreate = true,
|
|
185
194
|
canDelete = true,
|
|
186
195
|
canEdit = true,
|
|
196
|
+
readonly,
|
|
197
|
+
readOnly,
|
|
187
198
|
strings,
|
|
188
199
|
className,
|
|
189
200
|
onChange,
|
|
190
201
|
}: DynamicRelationOneToManyProps) {
|
|
202
|
+
// Read-only relation always wins over host-passed perms (AND). Tolerates the
|
|
203
|
+
// camelCase alias the same way lock_rows/lockRows does.
|
|
204
|
+
const isReadonly = readonly === true || readOnly === true
|
|
205
|
+
if (isReadonly) {
|
|
206
|
+
canCreate = false
|
|
207
|
+
canDelete = false
|
|
208
|
+
canEdit = false
|
|
209
|
+
}
|
|
191
210
|
const api = useApi()
|
|
192
211
|
const getImageUrl = useImageUrl()
|
|
193
212
|
const timeZone = useTimeZone()
|
|
@@ -490,10 +509,17 @@ function ManyToManyRelation({
|
|
|
490
509
|
displayKey,
|
|
491
510
|
canCreate = true,
|
|
492
511
|
canDelete = true,
|
|
512
|
+
readonly,
|
|
513
|
+
readOnly,
|
|
493
514
|
strings,
|
|
494
515
|
className,
|
|
495
516
|
onChange,
|
|
496
517
|
}: DynamicRelationManyToManyProps) {
|
|
518
|
+
// Read-only relation always wins over host-passed perms (AND).
|
|
519
|
+
if (readonly === true || readOnly === true) {
|
|
520
|
+
canCreate = false
|
|
521
|
+
canDelete = false
|
|
522
|
+
}
|
|
497
523
|
const api = useApi()
|
|
498
524
|
const { getMetadata, setMetadata: cacheMetadata } = useMetadataCache()
|
|
499
525
|
const labels = { ...DEFAULT_STRINGS, ...(strings || {}) }
|
|
@@ -115,6 +115,10 @@ export function DynamicRelations({
|
|
|
115
115
|
...(strings || {}),
|
|
116
116
|
...(rel.label ? { title: rel.label } : {}),
|
|
117
117
|
}
|
|
118
|
+
// A relation flagged read-only in the kernel metadata forces the
|
|
119
|
+
// panel's mutation controls off regardless of the host perms.
|
|
120
|
+
// Tolerates the camelCase alias.
|
|
121
|
+
const relReadonly = rel.readonly === true || rel.readOnly === true
|
|
118
122
|
if (rel.kind === 'many_to_many') {
|
|
119
123
|
return (
|
|
120
124
|
<DynamicRelation
|
|
@@ -133,6 +137,7 @@ export function DynamicRelations({
|
|
|
133
137
|
className={panelClassName}
|
|
134
138
|
canCreate={canCreate}
|
|
135
139
|
canDelete={canDelete}
|
|
140
|
+
readonly={relReadonly}
|
|
136
141
|
strings={panelStrings}
|
|
137
142
|
onChange={onChange ? () => onChange(rel) : undefined}
|
|
138
143
|
/>
|
|
@@ -150,6 +155,7 @@ export function DynamicRelations({
|
|
|
150
155
|
canCreate={canCreate}
|
|
151
156
|
canDelete={canDelete}
|
|
152
157
|
canEdit={canEdit}
|
|
158
|
+
readonly={relReadonly}
|
|
153
159
|
strings={panelStrings}
|
|
154
160
|
onChange={onChange ? () => onChange(rel) : undefined}
|
|
155
161
|
/>
|
|
@@ -33,8 +33,9 @@ import {
|
|
|
33
33
|
Popover,
|
|
34
34
|
PopoverContent,
|
|
35
35
|
PopoverTrigger,
|
|
36
|
+
InitialsAvatar,
|
|
36
37
|
} from '@asteby/metacore-ui/primitives'
|
|
37
|
-
import { Check, ChevronsUpDown,
|
|
38
|
+
import { Check, ChevronsUpDown, Loader2, Plus } from 'lucide-react'
|
|
38
39
|
import { resolveColorCss } from '@asteby/metacore-ui/lib'
|
|
39
40
|
import { DynamicIcon } from './dynamic-icon'
|
|
40
41
|
import { useOptionsResolver, type ResolvedOption } from './use-options-resolver'
|
|
@@ -49,24 +50,26 @@ import type { ActionFieldDef } from './types'
|
|
|
49
50
|
export const DEFAULT_DEPENDS_HINT = 'Selecciona primero el campo del que depende'
|
|
50
51
|
|
|
51
52
|
/**
|
|
52
|
-
* Small square thumbnail for an option's `image`.
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
53
|
+
* Small square thumbnail for an option's `image`. When the option has no image
|
|
54
|
+
* it falls back to a deterministic initials avatar (shared `InitialsAvatar`)
|
|
55
|
+
* derived from `name`, so an imageless reference reads as a colored badge rather
|
|
56
|
+
* than an empty placeholder — and rows/triggers stay aligned. `size` is in
|
|
57
|
+
* pixels (kept small — 20–24px — so the picker reads as a list, not a gallery).
|
|
58
|
+
* Inline style for the box dimensions: arbitrary Tailwind classes from a
|
|
59
|
+
* federated addon don't always survive the host's class scan.
|
|
57
60
|
*/
|
|
58
|
-
export function OptionThumb({
|
|
61
|
+
export function OptionThumb({
|
|
62
|
+
image,
|
|
63
|
+
name,
|
|
64
|
+
size = 20,
|
|
65
|
+
}: {
|
|
66
|
+
image?: string | null
|
|
67
|
+
name?: string | null
|
|
68
|
+
size?: number
|
|
69
|
+
}) {
|
|
59
70
|
const box = { width: size, height: size }
|
|
60
71
|
if (!image) {
|
|
61
|
-
return
|
|
62
|
-
<span
|
|
63
|
-
className="text-muted-foreground bg-muted flex shrink-0 items-center justify-center rounded-sm"
|
|
64
|
-
style={box}
|
|
65
|
-
aria-hidden
|
|
66
|
-
>
|
|
67
|
-
<ImageIcon className="size-3 opacity-60" />
|
|
68
|
-
</span>
|
|
69
|
-
)
|
|
72
|
+
return <InitialsAvatar name={name} size={size} rounded="sm" />
|
|
70
73
|
}
|
|
71
74
|
return (
|
|
72
75
|
<img
|
|
@@ -94,11 +97,11 @@ export function OptionLead({
|
|
|
94
97
|
option,
|
|
95
98
|
size = 20,
|
|
96
99
|
}: {
|
|
97
|
-
option?: Pick<ResolvedOption, 'image' | 'color' | 'icon'> | null
|
|
100
|
+
option?: Pick<ResolvedOption, 'image' | 'color' | 'icon' | 'label'> | null
|
|
98
101
|
size?: number
|
|
99
102
|
}) {
|
|
100
103
|
if (!option) return null
|
|
101
|
-
if (option.image) return <OptionThumb image={option.image} size={size} />
|
|
104
|
+
if (option.image) return <OptionThumb image={option.image} name={option.label} size={size} />
|
|
102
105
|
if (option.icon) {
|
|
103
106
|
return (
|
|
104
107
|
<span
|
|
@@ -119,6 +122,10 @@ export function OptionLead({
|
|
|
119
122
|
/>
|
|
120
123
|
)
|
|
121
124
|
}
|
|
125
|
+
// No image/icon/color: an imageless reference option. Show its initials
|
|
126
|
+
// (shared InitialsAvatar) so it stays visually aligned with the sibling
|
|
127
|
+
// options that DO carry a thumbnail, rather than a blank gap.
|
|
128
|
+
if (option.label) return <InitialsAvatar name={option.label} size={size} rounded="sm" />
|
|
122
129
|
return null
|
|
123
130
|
}
|
|
124
131
|
|
package/src/types.ts
CHANGED
|
@@ -153,6 +153,16 @@ export interface RelationMeta {
|
|
|
153
153
|
scope?: Record<string, string>
|
|
154
154
|
/** Human-readable panel header. */
|
|
155
155
|
label?: string
|
|
156
|
+
/**
|
|
157
|
+
* Read-only relation: when true the SDK forces canCreate/canEdit/canDelete
|
|
158
|
+
* = false on the rendered `<DynamicRelation>` panel, hiding the "Agregar"
|
|
159
|
+
* button and the per-row edit (Pencil) / delete (Trash2) controls — no
|
|
160
|
+
* matter what perms the host passes. Generic & domain-agnostic. Read from
|
|
161
|
+
* the kernel relation metadata. Tolerates the camelCase alias.
|
|
162
|
+
*/
|
|
163
|
+
readonly?: boolean
|
|
164
|
+
/** camelCase alias for `readonly`. */
|
|
165
|
+
readOnly?: boolean
|
|
156
166
|
}
|
|
157
167
|
|
|
158
168
|
export interface FilterDefinition {
|