@asteby/metacore-runtime-react 24.0.1 → 24.0.2

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 24.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - a27d1a2: feat(relations): secondary identifier (SKU/email) under a reference chip
8
+
9
+ Reference chips (resolved FK cells, endpoint-option badges, jsonb line-item refs,
10
+ confirm-modal items) now render a muted secondary line under the label — a
11
+ product's SKU, a user's email — so a resolved record reads "Name / SKU" instead
12
+ of a bare name. Declarative: the SDK reads the backend-projected `subtitle` /
13
+ `description` on the option or resolved sibling (never a hardcoded column). Absent
14
+ → single-line as before. The select picker already surfaced `description`; this
15
+ brings the resolved-chip surfaces in line.
16
+
3
17
  ## 24.0.1
4
18
 
5
19
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"collection-cell.d.ts","sourceRoot":"","sources":["../src/collection-cell.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAkD9B,sFAAsF;AACtF,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,MAAM,CAAA;AAE9D;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACtB,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAA;IACX,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAA;CACf;AAoND;;;;;;GAMG;AACH,wBAAgB,WAAW,CACvB,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,MAAM,EACf,CAAC,CAAC,EAAE,SAAS,GACd,MAAM,CAWR;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACtB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,EACf,CAAC,CAAC,EAAE,SAAS,GACd,MAAM,CAcR;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAYnD;AAiOD,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,OAAO,CAAA;IACd,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2EAA2E;IAC3E,CAAC,CAAC,EAAE,SAAS,CAAA;IACb;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,SAAS,EAAE,CAAA;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;CAC/B;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,EAC3B,KAAK,EACL,SAAa,EACb,MAAM,EACN,CAAC,EACD,UAAU,EACV,WAAW,EACX,OAAiB,GACpB,EAAE,mBAAmB,qBA2HrB"}
1
+ {"version":3,"file":"collection-cell.d.ts","sourceRoot":"","sources":["../src/collection-cell.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAkD9B,sFAAsF;AACtF,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,MAAM,CAAA;AAE9D;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACtB,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAA;IACX,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAA;CACf;AAwOD;;;;;;GAMG;AACH,wBAAgB,WAAW,CACvB,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,MAAM,EACf,CAAC,CAAC,EAAE,SAAS,GACd,MAAM,CAWR;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACtB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,EACf,CAAC,CAAC,EAAE,SAAS,GACd,MAAM,CAcR;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAYnD;AAkOD,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,OAAO,CAAA;IACd,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2EAA2E;IAC3E,CAAC,CAAC,EAAE,SAAS,CAAA;IACb;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,SAAS,EAAE,CAAA;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;CAC/B;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,EAC3B,KAAK,EACL,SAAa,EACb,MAAM,EACN,CAAC,EACD,UAAU,EACV,WAAW,EACX,OAAiB,GACpB,EAAE,mBAAmB,qBA2HrB"}
@@ -55,12 +55,16 @@ function resolvedRefFor(field, row) {
55
55
  const obj = sibling;
56
56
  const label = obj.label;
57
57
  if (label !== undefined && label !== null && label !== '') {
58
+ const sub = obj.subtitle ?? obj.description;
58
59
  return {
59
60
  label: String(label),
60
61
  value: obj.value,
61
62
  image: typeof obj.image === 'string' && obj.image !== ''
62
63
  ? obj.image
63
64
  : undefined,
65
+ subtitle: sub !== undefined && sub !== null && sub !== ''
66
+ ? String(sub)
67
+ : undefined,
64
68
  };
65
69
  }
66
70
  }
@@ -92,10 +96,12 @@ function resolvedRefObject(v) {
92
96
  const label = v.label;
93
97
  if (label === undefined || label === null || label === '')
94
98
  return null;
99
+ const sub = v.subtitle ?? v.description;
95
100
  return {
96
101
  label: String(label),
97
102
  value: v.value,
98
103
  image: typeof v.image === 'string' && v.image !== '' ? v.image : undefined,
104
+ subtitle: sub !== undefined && sub !== null && sub !== '' ? String(sub) : undefined,
99
105
  };
100
106
  }
101
107
  /**
@@ -104,9 +110,10 @@ function resolvedRefObject(v) {
104
110
  * resolved name. The exact look the FK table columns use, so resolved relations
105
111
  * read consistently everywhere (table popover, detail view, edit, line items).
106
112
  */
107
- function RefChip({ label, image, getImageUrl, }) {
113
+ function RefChip({ label, image, subtitle, getImageUrl, }) {
108
114
  const isDark = useIsDarkTheme();
109
- return (_jsxs("span", { className: "inline-flex max-w-[220px] items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium", style: relationChipStyles(label, { isDark }), title: label, children: [image ? (_jsxs(Avatar, { className: "shrink-0 rounded-sm ring-1 ring-border/40", style: { width: 18, height: 18 }, children: [_jsx(AvatarImage, { src: getImageUrl ? getImageUrl(image) : image, alt: label, className: "object-cover" }), _jsx(AvatarFallback, { className: "rounded-sm bg-primary/10 text-[8px] font-bold text-primary", children: getInitials(label) })] })) : (_jsx(Box, { className: "h-3 w-3 shrink-0 opacity-70" })), _jsx("span", { className: "truncate", children: label })] }));
115
+ const size = subtitle ? 24 : 18;
116
+ return (_jsxs("span", { className: "inline-flex max-w-[220px] items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium", style: relationChipStyles(label, { isDark }), title: subtitle ? `${label} · ${subtitle}` : label, children: [image ? (_jsxs(Avatar, { className: "shrink-0 rounded-sm ring-1 ring-border/40", style: { width: size, height: size }, children: [_jsx(AvatarImage, { src: getImageUrl ? getImageUrl(image) : image, alt: label, className: "object-cover" }), _jsx(AvatarFallback, { className: "rounded-sm bg-primary/10 text-[8px] font-bold text-primary", children: getInitials(label) })] })) : (_jsx(Box, { className: "h-3 w-3 shrink-0 opacity-70" })), subtitle ? (_jsxs("span", { className: "flex flex-col leading-tight min-w-0", children: [_jsx("span", { className: "truncate", children: label }), _jsx("span", { className: "truncate text-[0.65rem] opacity-70", children: subtitle })] })) : (_jsx("span", { className: "truncate", children: label }))] }));
110
117
  }
111
118
  /**
112
119
  * Visual cell for one declared item-field. A resolved `ref` renders as a
@@ -117,7 +124,7 @@ function RefChip({ label, image, getImageUrl, }) {
117
124
  function ItemFieldCell({ field, row, getImageUrl, }) {
118
125
  const ref = resolvedRefFor(field, row);
119
126
  if (ref?.label) {
120
- return (_jsx(RefChip, { label: ref.label, image: ref.image, getImageUrl: getImageUrl }));
127
+ return (_jsx(RefChip, { label: ref.label, image: ref.image, subtitle: ref.subtitle, getImageUrl: getImageUrl }));
121
128
  }
122
129
  return _jsx(_Fragment, { children: formatScalar(row[field.key]) });
123
130
  }
@@ -311,7 +318,7 @@ function MiniTable({ rows, locale, t, itemFields, getImageUrl, }) {
311
318
  const ref = refKeys.has(key)
312
319
  ? resolvedRefObject(row[key])
313
320
  : null;
314
- return (_jsx(TableCell, { className: "text-xs whitespace-nowrap", children: ref ? (_jsx(RefChip, { label: ref.label, image: ref.image, getImageUrl: getImageUrl })) : (formatScalar(row[key])) }, key));
321
+ return (_jsx(TableCell, { className: "text-xs whitespace-nowrap", children: ref ? (_jsx(RefChip, { label: ref.label, image: ref.image, subtitle: ref.subtitle, getImageUrl: getImageUrl })) : (formatScalar(row[key])) }, key));
315
322
  }) }, i))) })] }));
316
323
  }
317
324
  function ScalarList({ values }) {
@@ -39,6 +39,15 @@ export interface DisplayOption {
39
39
  icon?: string;
40
40
  color?: string;
41
41
  image?: string;
42
+ /**
43
+ * Secondary identifier shown UNDER the label (muted, smaller) — a product's
44
+ * SKU/barcode, a user's email, etc. Lets a reference chip read like
45
+ * "Camiseta / SKU-001" instead of a bare name, so a resolved record is
46
+ * unambiguous. Populated declaratively: the backend projects it as the
47
+ * option/relation `description` (SearchConfig/FieldOptionsConfig.Description,
48
+ * or a column's `label_description`). Absent → single-line label as before.
49
+ */
50
+ subtitle?: string;
42
51
  }
43
52
  /**
44
53
  * Colored option badge — the canonical "pro" pill for a select/status/badge
@@ -1 +1 @@
1
- {"version":3,"file":"display-value.d.ts","sourceRoot":"","sources":["../src/display-value.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,MAAM,OAAO,CAAA;AASzB;;;GAGG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAmBxC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAepD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,CAcA,CAAA;AAED,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC;IAC/B,MAAM,EAAE,aAAa,CAAA;IACrB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB,CAmBA,CAAA"}
1
+ {"version":3,"file":"display-value.d.ts","sourceRoot":"","sources":["../src/display-value.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,MAAM,OAAO,CAAA;AASzB;;;GAGG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAmBxC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAepD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,CAcA,CAAA;AAED,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC;IAC/B,MAAM,EAAE,aAAa,CAAA;IACrB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB,CA8BA,CAAA"}
@@ -70,5 +70,5 @@ export const OptionBadge = ({ option, getImageUrl }) => {
70
70
  const isDark = useIsDarkTheme();
71
71
  const colorSource = option.color || optionColor(option.value || option.label);
72
72
  const colorStyles = generateBadgeStyles(colorSource, { isDark });
73
- return (_jsxs(Badge, { variant: "outline", className: "flex items-center gap-1 border-0", style: colorStyles, children: [option.image ? (_jsx(RelationThumbnail, { src: option.image, alt: option.label, getImageUrl: getImageUrl, size: 16 })) : (option.icon && _jsx(DynamicIcon, { name: option.icon, className: "h-3.5 w-3.5" })), _jsx("span", { children: option.label })] }));
73
+ return (_jsxs(Badge, { variant: "outline", className: "flex items-center gap-1 border-0", style: colorStyles, children: [option.image ? (_jsx(RelationThumbnail, { src: option.image, alt: option.label, getImageUrl: getImageUrl, size: option.subtitle ? 22 : 16 })) : (option.icon && _jsx(DynamicIcon, { name: option.icon, className: "h-3.5 w-3.5" })), option.subtitle ? (_jsxs("span", { className: "flex flex-col leading-tight text-start", children: [_jsx("span", { children: option.label }), _jsx("span", { className: "text-[0.7rem] opacity-70", children: option.subtitle })] })) : (_jsx("span", { children: option.label }))] }));
74
74
  };
@@ -112,6 +112,15 @@ export declare const resolveRelationLabel: (col: ColumnDefinition, row: any) =>
112
112
  * the chip then renders text-only, exactly as before.
113
113
  */
114
114
  export declare const resolveRelationImage: (col: ColumnDefinition, row: any) => string;
115
+ /**
116
+ * Reads a secondary identifier the backend stamps on a resolved FK sibling — a
117
+ * product's SKU, a user's email — projected as `subtitle`/`description` (the
118
+ * relational twin of `image` via the column's `label_description`). Rendered
119
+ * muted under the label so a reference chip reads "Name / SKU". Domain-agnostic:
120
+ * only the generic `subtitle`/`description` keys are read (never a hardcoded
121
+ * `sku`/`code`), so the author picks the column declaratively. '' when absent.
122
+ */
123
+ export declare const resolveRelationSubtitle: (col: ColumnDefinition, row: any) => string;
115
124
  /**
116
125
  * Builds the canonical column factory used by `<DynamicTable>` when the host
117
126
  * does not supply its own. Pass `{ getImageUrl, apiBaseUrl }` to wire avatar
@@ -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;AAuC9C,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;AAqFhF;;;;;;;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;AAsID;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAqmBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,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;AAuC9C,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;AA8ID;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAqmBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
@@ -213,8 +213,11 @@ const BadgeWithEndpointOptions = ({ endpoint, value, getImageUrl }) => {
213
213
  const { optionsMap } = React.useContext(OptionsContext);
214
214
  const options = optionsMap.get(endpoint) || [];
215
215
  const option = options.find((opt) => opt.value === value);
216
+ // Reference options carry the backend-projected `description` (the SKU/email
217
+ // the author pointed the options config at) — surface it as the chip subtitle
218
+ // so a resolved record reads "Name / SKU" instead of a bare name.
216
219
  if (option)
217
- return _jsx(OptionBadge, { option: option, fallback: String(value), getImageUrl: getImageUrl });
220
+ return _jsx(OptionBadge, { option: { ...option, subtitle: option.subtitle ?? option.description }, fallback: String(value), getImageUrl: getImageUrl });
218
221
  // No declared option matched → humanize the raw token as a safety net so a
219
222
  // cell never shows `in_progress` verbatim (option.label still wins above).
220
223
  return _jsx(Badge, { variant: "outline", children: humanizeToken(value) });
@@ -336,6 +339,23 @@ export const resolveRelationImage = (col, row) => {
336
339
  }
337
340
  return '';
338
341
  };
342
+ /**
343
+ * Reads a secondary identifier the backend stamps on a resolved FK sibling — a
344
+ * product's SKU, a user's email — projected as `subtitle`/`description` (the
345
+ * relational twin of `image` via the column's `label_description`). Rendered
346
+ * muted under the label so a reference chip reads "Name / SKU". Domain-agnostic:
347
+ * only the generic `subtitle`/`description` keys are read (never a hardcoded
348
+ * `sku`/`code`), so the author picks the column declaratively. '' when absent.
349
+ */
350
+ export const resolveRelationSubtitle = (col, row) => {
351
+ const sibling = getNestedValue(row, relationKeyFor(col));
352
+ if (sibling && typeof sibling === 'object') {
353
+ const sub = sibling.subtitle ?? sibling.description;
354
+ if (sub !== undefined && sub !== null && sub !== '')
355
+ return String(sub);
356
+ }
357
+ return '';
358
+ };
339
359
  /**
340
360
  * Renders a resolved FK relation as a clean, truncated chip. Reads the
341
361
  * backend-resolved sibling `{ value, label[, image] }` (see `relationKeyFor`)
@@ -350,12 +370,13 @@ const RelationCell = ({ col, row, getImageUrl }) => {
350
370
  if (!display)
351
371
  return _jsx(EmptyCell, {});
352
372
  const image = resolveRelationImage(col, row);
373
+ const subtitle = resolveRelationSubtitle(col, row);
353
374
  // Deterministic, SUBTLE color keyed on the resolved label — lighter than
354
375
  // enum badges (soft tint, no heavy fill) so category/brand chips read as
355
376
  // alive yet stay visually distinct from option/status badges. Inline style
356
377
  // (hex-derived) bypasses the host tailwind safelist.
357
378
  const chipStyles = relationChipStyles(display, { isDark });
358
- 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: display, children: [image && (_jsx(RelationThumbnail, { src: image, alt: display, getImageUrl: getImageUrl, size: 18 })), _jsx("span", { className: "truncate", children: display })] }));
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 })), 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 }))] }));
359
380
  };
360
381
  /**
361
382
  * Renders a SAP-style polymorphic source-document reference as a navigable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "24.0.1",
3
+ "version": "24.0.2",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,8 +37,8 @@
37
37
  "react-i18next": ">=13",
38
38
  "sonner": ">=1.7",
39
39
  "zustand": ">=5",
40
- "@asteby/metacore-sdk": "^3.2.0",
41
- "@asteby/metacore-ui": "^2.10.0"
40
+ "@asteby/metacore-sdk": "^3.2.1",
41
+ "@asteby/metacore-ui": "^2.10.1"
42
42
  },
43
43
  "peerDependenciesMeta": {
44
44
  "@tanstack/react-router": {
@@ -67,8 +67,8 @@
67
67
  "typescript": "^6.0.0",
68
68
  "vitest": "^4.0.0",
69
69
  "zustand": "^5.0.0",
70
- "@asteby/metacore-sdk": "3.2.0",
71
- "@asteby/metacore-ui": "2.10.0"
70
+ "@asteby/metacore-sdk": "3.2.1",
71
+ "@asteby/metacore-ui": "2.10.1"
72
72
  },
73
73
  "scripts": {
74
74
  "build": "tsc -p tsconfig.json",
@@ -92,6 +92,8 @@ interface ResolvedRef {
92
92
  value?: unknown
93
93
  /** Optional thumbnail (product photo, logo, avatar) resolved by the backend. */
94
94
  image?: string
95
+ /** Optional secondary identifier (SKU/email) shown muted under the label. */
96
+ subtitle?: string
95
97
  }
96
98
 
97
99
  /**
@@ -110,6 +112,7 @@ function resolvedRefFor(
110
112
  const obj = sibling as Record<string, unknown>
111
113
  const label = obj.label
112
114
  if (label !== undefined && label !== null && label !== '') {
115
+ const sub = obj.subtitle ?? obj.description
113
116
  return {
114
117
  label: String(label),
115
118
  value: obj.value,
@@ -117,6 +120,10 @@ function resolvedRefFor(
117
120
  typeof obj.image === 'string' && obj.image !== ''
118
121
  ? obj.image
119
122
  : undefined,
123
+ subtitle:
124
+ sub !== undefined && sub !== null && sub !== ''
125
+ ? String(sub)
126
+ : undefined,
120
127
  }
121
128
  }
122
129
  } else if (typeof sibling === 'string' && sibling !== '') {
@@ -148,11 +155,14 @@ function resolvedRefObject(
148
155
  if (!isPlainObject(v)) return null
149
156
  const label = v.label
150
157
  if (label === undefined || label === null || label === '') return null
158
+ const sub = v.subtitle ?? v.description
151
159
  return {
152
160
  label: String(label),
153
161
  value: v.value,
154
162
  image:
155
163
  typeof v.image === 'string' && v.image !== '' ? v.image : undefined,
164
+ subtitle:
165
+ sub !== undefined && sub !== null && sub !== '' ? String(sub) : undefined,
156
166
  }
157
167
  }
158
168
 
@@ -165,23 +175,26 @@ function resolvedRefObject(
165
175
  function RefChip({
166
176
  label,
167
177
  image,
178
+ subtitle,
168
179
  getImageUrl,
169
180
  }: {
170
181
  label: string
171
182
  image?: string
183
+ subtitle?: string
172
184
  getImageUrl?: (path: string) => string
173
185
  }): React.ReactElement {
174
186
  const isDark = useIsDarkTheme()
187
+ const size = subtitle ? 24 : 18
175
188
  return (
176
189
  <span
177
190
  className="inline-flex max-w-[220px] items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium"
178
191
  style={relationChipStyles(label, { isDark })}
179
- title={label}
192
+ title={subtitle ? `${label} · ${subtitle}` : label}
180
193
  >
181
194
  {image ? (
182
195
  <Avatar
183
196
  className="shrink-0 rounded-sm ring-1 ring-border/40"
184
- style={{ width: 18, height: 18 }}
197
+ style={{ width: size, height: size }}
185
198
  >
186
199
  <AvatarImage
187
200
  src={getImageUrl ? getImageUrl(image) : image}
@@ -195,7 +208,14 @@ function RefChip({
195
208
  ) : (
196
209
  <Box className="h-3 w-3 shrink-0 opacity-70" />
197
210
  )}
198
- <span className="truncate">{label}</span>
211
+ {subtitle ? (
212
+ <span className="flex flex-col leading-tight min-w-0">
213
+ <span className="truncate">{label}</span>
214
+ <span className="truncate text-[0.65rem] opacity-70">{subtitle}</span>
215
+ </span>
216
+ ) : (
217
+ <span className="truncate">{label}</span>
218
+ )}
199
219
  </span>
200
220
  )
201
221
  }
@@ -218,7 +238,7 @@ function ItemFieldCell({
218
238
  const ref = resolvedRefFor(field, row)
219
239
  if (ref?.label) {
220
240
  return (
221
- <RefChip label={ref.label} image={ref.image} getImageUrl={getImageUrl} />
241
+ <RefChip label={ref.label} image={ref.image} subtitle={ref.subtitle} getImageUrl={getImageUrl} />
222
242
  )
223
243
  }
224
244
  return <>{formatScalar(row[field.key])}</>
@@ -495,6 +515,7 @@ function MiniTable({
495
515
  <RefChip
496
516
  label={ref.label}
497
517
  image={ref.image}
518
+ subtitle={ref.subtitle}
498
519
  getImageUrl={getImageUrl}
499
520
  />
500
521
  ) : (
@@ -98,6 +98,15 @@ export interface DisplayOption {
98
98
  icon?: string
99
99
  color?: string
100
100
  image?: string
101
+ /**
102
+ * Secondary identifier shown UNDER the label (muted, smaller) — a product's
103
+ * SKU/barcode, a user's email, etc. Lets a reference chip read like
104
+ * "Camiseta / SKU-001" instead of a bare name, so a resolved record is
105
+ * unambiguous. Populated declaratively: the backend projects it as the
106
+ * option/relation `description` (SearchConfig/FieldOptionsConfig.Description,
107
+ * or a column's `label_description`). Absent → single-line label as before.
108
+ */
109
+ subtitle?: string
101
110
  }
102
111
 
103
112
  /**
@@ -123,12 +132,23 @@ export const OptionBadge: React.FC<{
123
132
  src={option.image}
124
133
  alt={option.label}
125
134
  getImageUrl={getImageUrl}
126
- size={16}
135
+ size={option.subtitle ? 22 : 16}
127
136
  />
128
137
  ) : (
129
138
  option.icon && <DynamicIcon name={option.icon} className="h-3.5 w-3.5" />
130
139
  )}
131
- <span>{option.label}</span>
140
+ {option.subtitle ? (
141
+ // Two-line identity: label on top, the secondary identifier
142
+ // (SKU/email/…) muted underneath. `leading-tight` keeps the pill
143
+ // compact; the subtitle inherits the badge's fg at reduced opacity
144
+ // so it reads as secondary on any color.
145
+ <span className="flex flex-col leading-tight text-start">
146
+ <span>{option.label}</span>
147
+ <span className="text-[0.7rem] opacity-70">{option.subtitle}</span>
148
+ </span>
149
+ ) : (
150
+ <span>{option.label}</span>
151
+ )}
132
152
  </Badge>
133
153
  )
134
154
  }
@@ -332,7 +332,10 @@ const BadgeWithEndpointOptions: React.FC<{
332
332
  const { optionsMap } = React.useContext(OptionsContext)
333
333
  const options = optionsMap.get(endpoint) || []
334
334
  const option = options.find((opt: any) => opt.value === value)
335
- if (option) return <OptionBadge option={option} fallback={String(value)} getImageUrl={getImageUrl} />
335
+ // Reference options carry the backend-projected `description` (the SKU/email
336
+ // the author pointed the options config at) — surface it as the chip subtitle
337
+ // so a resolved record reads "Name / SKU" instead of a bare name.
338
+ if (option) return <OptionBadge option={{ ...option, subtitle: option.subtitle ?? (option as any).description }} fallback={String(value)} getImageUrl={getImageUrl} />
336
339
  // No declared option matched → humanize the raw token as a safety net so a
337
340
  // cell never shows `in_progress` verbatim (option.label still wins above).
338
341
  return <Badge variant="outline">{humanizeToken(value)}</Badge>
@@ -461,6 +464,23 @@ export const resolveRelationImage = (col: ColumnDefinition, row: any): string =>
461
464
  return ''
462
465
  }
463
466
 
467
+ /**
468
+ * Reads a secondary identifier the backend stamps on a resolved FK sibling — a
469
+ * product's SKU, a user's email — projected as `subtitle`/`description` (the
470
+ * relational twin of `image` via the column's `label_description`). Rendered
471
+ * muted under the label so a reference chip reads "Name / SKU". Domain-agnostic:
472
+ * only the generic `subtitle`/`description` keys are read (never a hardcoded
473
+ * `sku`/`code`), so the author picks the column declaratively. '' when absent.
474
+ */
475
+ export const resolveRelationSubtitle = (col: ColumnDefinition, row: any): string => {
476
+ const sibling = getNestedValue(row, relationKeyFor(col))
477
+ if (sibling && typeof sibling === 'object') {
478
+ const sub = sibling.subtitle ?? sibling.description
479
+ if (sub !== undefined && sub !== null && sub !== '') return String(sub)
480
+ }
481
+ return ''
482
+ }
483
+
464
484
  /**
465
485
  * Renders a resolved FK relation as a clean, truncated chip. Reads the
466
486
  * backend-resolved sibling `{ value, label[, image] }` (see `relationKeyFor`)
@@ -478,6 +498,7 @@ const RelationCell: React.FC<{
478
498
  const display = resolveRelationLabel(col, row)
479
499
  if (!display) return <EmptyCell />
480
500
  const image = resolveRelationImage(col, row)
501
+ const subtitle = resolveRelationSubtitle(col, row)
481
502
  // Deterministic, SUBTLE color keyed on the resolved label — lighter than
482
503
  // enum badges (soft tint, no heavy fill) so category/brand chips read as
483
504
  // alive yet stay visually distinct from option/status badges. Inline style
@@ -487,12 +508,19 @@ const RelationCell: React.FC<{
487
508
  <span
488
509
  className="inline-flex max-w-[220px] items-center gap-1.5 rounded-md px-2 py-0.5 text-sm font-medium"
489
510
  style={chipStyles}
490
- title={display}
511
+ title={subtitle ? `${display} · ${subtitle}` : display}
491
512
  >
492
513
  {image && (
493
- <RelationThumbnail src={image} alt={display} getImageUrl={getImageUrl} size={18} />
514
+ <RelationThumbnail src={image} alt={display} getImageUrl={getImageUrl} size={subtitle ? 24 : 18} />
515
+ )}
516
+ {subtitle ? (
517
+ <span className="flex flex-col leading-tight min-w-0">
518
+ <span className="truncate">{display}</span>
519
+ <span className="truncate text-[0.7rem] opacity-70">{subtitle}</span>
520
+ </span>
521
+ ) : (
522
+ <span className="truncate">{display}</span>
494
523
  )}
495
- <span className="truncate">{display}</span>
496
524
  </span>
497
525
  )
498
526
  }