@asteby/metacore-runtime-react 13.8.5 → 13.10.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 +66 -0
- package/dist/dynamic-columns.d.ts.map +1 -1
- package/dist/dynamic-columns.js +206 -18
- package/dist/dynamic-form-schema.d.ts +9 -0
- package/dist/dynamic-form-schema.d.ts.map +1 -1
- package/dist/dynamic-form-schema.js +29 -0
- package/dist/dynamic-select-field.d.ts.map +1 -1
- package/dist/dynamic-select-field.js +42 -11
- package/dist/types.d.ts +9 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/dynamic-form.test.ts +28 -0
- package/src/dynamic-columns.tsx +374 -39
- package/src/dynamic-form-schema.ts +29 -0
- package/src/dynamic-select-field.tsx +77 -16
- package/src/types.ts +35 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,71 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 13.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- da8139d: feat(dynamic-form,nav): FK→searchable picker, image thumbnails, media→upload, query-aware nav
|
|
8
|
+
- **resolveWidget**: a field that declares an FK target (`ref`, or the
|
|
9
|
+
snake_case `source`/`relation` the kernel may serve) now resolves to
|
|
10
|
+
`dynamic_select` BEFORE the type switch, so any declared relation renders a
|
|
11
|
+
searchable picker instead of a raw text input — regardless of the column's
|
|
12
|
+
SQL type. `image`/`media`/`file` types resolve to the `upload` widget.
|
|
13
|
+
- **DynamicSelectField**: renders the option's `image` as a small thumbnail in
|
|
14
|
+
the trigger (selected option) and in each dropdown row, with a neutral
|
|
15
|
+
placeholder fallback. Thumbnails only appear when the resolved options carry
|
|
16
|
+
images, so image-less relations keep their plain text list. Also tolerates
|
|
17
|
+
the `source`/`relation` ref aliases for option resolution and inline-create.
|
|
18
|
+
- **NavGroup.checkIsActive**: now query-aware. Order-status style items that
|
|
19
|
+
share a path but differ only by a query param (`?status=reception` vs
|
|
20
|
+
`?status=delivery`) light up one at a time instead of all together; an item
|
|
21
|
+
that declares query params must match the current href's query exactly
|
|
22
|
+
(after normalization, with transient `f_` filter params stripped), while
|
|
23
|
+
query-less links keep matching on path alone.
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [da8139d]
|
|
28
|
+
- @asteby/metacore-ui@2.1.2
|
|
29
|
+
|
|
30
|
+
## 13.9.0
|
|
31
|
+
|
|
32
|
+
### Minor Changes
|
|
33
|
+
|
|
34
|
+
- 27b37f3: feat(dynamic-columns): declarative pro cell renderers for dynamic tables
|
|
35
|
+
|
|
36
|
+
Adds a library of declarative cell renderers so columns are rendered
|
|
37
|
+
beautifully out of the box instead of raw text. Driven by `col.cellStyle`
|
|
38
|
+
(or `col.type`), resolved via the existing `renderAs = col.cellStyle ?? col.type`.
|
|
39
|
+
Config is read from `col.styleConfig`, tolerating both snake_case (kernel) and
|
|
40
|
+
camelCase (compiled models).
|
|
41
|
+
|
|
42
|
+
New cellStyles:
|
|
43
|
+
- `url` / `link` — clickable link with an `ExternalLink` icon. `styleConfig`:
|
|
44
|
+
`{ label_field?, url_field?, icon?, new_tab? }`. Shows `label_field` text or
|
|
45
|
+
the URL hostname; opens in a new tab for external URLs (or `new_tab`);
|
|
46
|
+
prefixes `https://` when the scheme is missing.
|
|
47
|
+
- `email` — `mailto:` link with a `Mail` icon.
|
|
48
|
+
- `currency` — `Intl.NumberFormat` currency formatting, right-aligned.
|
|
49
|
+
Currency from `styleConfig.currency` (default `USD`), decimals from
|
|
50
|
+
`styleConfig.decimals` (default 2). No hardcoded MXN.
|
|
51
|
+
- `number` — thousands-separated number, right-aligned.
|
|
52
|
+
- `percent` / `progress` — progress bar (shadcn `Progress`) + `NN%` label.
|
|
53
|
+
- `badge` (generic) — pills a plain value even without `options`/`searchEndpoint`.
|
|
54
|
+
- `status` — badge with semantic color by value (active/paid→green,
|
|
55
|
+
pending/draft→amber, cancelled/failed→red, else grey); explicit
|
|
56
|
+
`options` colors win.
|
|
57
|
+
- `tags` — array / comma-separated string → row of small badges.
|
|
58
|
+
- `color` — color swatch + hex code.
|
|
59
|
+
- `code` / `truncate-text` — monospaced, truncated (`styleConfig.max_length`)
|
|
60
|
+
with a hover copy button.
|
|
61
|
+
- `creator` / `user` — avatar + name + subtitle, generalising the existing
|
|
62
|
+
`avatar`/`search` renderer (name from `styleConfig.name_field`, photo from a
|
|
63
|
+
sibling `.avatar`/`.photo` or `base_path + value`, initials fallback).
|
|
64
|
+
|
|
65
|
+
Also improves the existing `boolean` cell (green `Check` / muted `Minus` icon)
|
|
66
|
+
without breaking the `avatar`/`search`, `date`, `phone`, `image`,
|
|
67
|
+
`media-gallery`, `badge+options` and `relation-badge-list` renderers.
|
|
68
|
+
|
|
3
69
|
## 13.8.5
|
|
4
70
|
|
|
5
71
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dynamic-columns.d.ts","sourceRoot":"","sources":["../src/dynamic-columns.tsx"],"names":[],"mappings":"AA0CA,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;AAgGD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,GAAG,EAAE,KAAK,GAAG,KAAG,OAMlE,CAAA;AAuJD;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,OAAO,GAAE,qBAA0B,GACpC,iBAAiB,CAokBnB;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBACL,CAAA"}
|
package/dist/dynamic-columns.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// Default `getDynamicColumns` factory used by hosts that don't need a custom
|
|
3
3
|
// renderer. Supports every cell type produced by kernel/dynamic metadata:
|
|
4
|
-
// badge (static + endpoint-loaded options), avatar,
|
|
5
|
-
// relation-badge-list, media-gallery, image, plus
|
|
4
|
+
// badge (static + endpoint-loaded options), avatar/search, creator/user,
|
|
5
|
+
// phone, date, boolean, relation-badge-list, media-gallery, image, plus the
|
|
6
|
+
// declarative pro renderers url/link, email, currency, number, percent/
|
|
7
|
+
// progress, status, tags, color, code/truncate-text, and a generic text
|
|
8
|
+
// fallback. The renderer resolves `cellStyle ?? type` for each column.
|
|
6
9
|
//
|
|
7
10
|
// The implementation was previously duplicated across multiple host apps
|
|
8
11
|
// (~550 LOC each, drifting). It now lives here so a single fix propagates
|
|
@@ -16,11 +19,63 @@ import { MoreHorizontal } from 'lucide-react';
|
|
|
16
19
|
import { Avatar, AvatarFallback, AvatarImage, Badge, Button, Checkbox, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@asteby/metacore-ui';
|
|
17
20
|
import { DataTableColumnHeader, FilterableColumnHeader, } from '@asteby/metacore-ui/data-table';
|
|
18
21
|
import { generateBadgeStyles, getInitials } from '@asteby/metacore-ui/lib';
|
|
22
|
+
import { Progress } from './dialogs/_primitives';
|
|
19
23
|
import { OptionsContext } from './options-context';
|
|
20
24
|
import { DynamicIcon } from './dynamic-icon';
|
|
21
25
|
import { isColumnVisibleInTable } from './column-visibility';
|
|
22
26
|
const defaultGetImageUrl = (path) => path;
|
|
23
27
|
const getNestedValue = (obj, path) => path.split('.').reduce((acc, part) => acc && acc[part], obj);
|
|
28
|
+
/**
|
|
29
|
+
* Reads a styleConfig key tolerating both snake_case (emitted by the kernel)
|
|
30
|
+
* and camelCase (sometimes produced by compiled models). Returns the first
|
|
31
|
+
* defined match, e.g. `cfg('label_field', 'labelField')`.
|
|
32
|
+
*/
|
|
33
|
+
const styleCfg = (col, ...keys) => {
|
|
34
|
+
const cfg = col.styleConfig;
|
|
35
|
+
if (!cfg)
|
|
36
|
+
return undefined;
|
|
37
|
+
for (const k of keys) {
|
|
38
|
+
if (cfg[k] !== undefined && cfg[k] !== null)
|
|
39
|
+
return cfg[k];
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
};
|
|
43
|
+
const EmptyCell = () => _jsx("span", { className: "text-muted-foreground", children: "-" });
|
|
44
|
+
/** Resolves the active org currency, defaulting to USD when no override. */
|
|
45
|
+
const resolveCurrency = (col) => styleCfg(col, 'currency') || 'USD';
|
|
46
|
+
const formatNumber = (value, opts, locale) => new Intl.NumberFormat(locale || undefined, opts).format(value);
|
|
47
|
+
/**
|
|
48
|
+
* Semantic status → badge color. Used by the `status` cell when no explicit
|
|
49
|
+
* `options` color is declared. Generic, value-driven mapping.
|
|
50
|
+
*/
|
|
51
|
+
const statusColorFor = (value) => {
|
|
52
|
+
const v = value.toLowerCase();
|
|
53
|
+
if (['active', 'enabled', 'paid', 'completed', 'done', 'success', 'approved', 'open']
|
|
54
|
+
.includes(v))
|
|
55
|
+
return '#22c55e';
|
|
56
|
+
if (['pending', 'draft', 'processing', 'in_progress', 'review', 'waiting'].includes(v))
|
|
57
|
+
return '#eab308';
|
|
58
|
+
if (['inactive', 'disabled', 'cancelled', 'canceled', 'failed', 'rejected', 'error', 'closed']
|
|
59
|
+
.includes(v))
|
|
60
|
+
return '#ef4444';
|
|
61
|
+
return '#6b7280';
|
|
62
|
+
};
|
|
63
|
+
/** Copyable monospaced text cell (code/IDs/hashes). */
|
|
64
|
+
const CodeCell = ({ text, maxLength }) => {
|
|
65
|
+
const [copied, setCopied] = React.useState(false);
|
|
66
|
+
const display = maxLength && text.length > maxLength ? `${text.slice(0, maxLength)}…` : text;
|
|
67
|
+
const onCopy = () => {
|
|
68
|
+
try {
|
|
69
|
+
navigator.clipboard?.writeText(text);
|
|
70
|
+
setCopied(true);
|
|
71
|
+
setTimeout(() => setCopied(false), 1200);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
/* clipboard unavailable */
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
return (_jsxs("div", { className: "group flex items-center gap-1.5", children: [_jsx("code", { className: "rounded bg-muted px-1.5 py-0.5 font-mono text-xs text-foreground/80", title: text, children: display }), _jsx("button", { type: "button", onClick: onCopy, className: "opacity-0 transition-opacity group-hover:opacity-100 text-muted-foreground hover:text-foreground", "aria-label": "Copiar", title: "Copiar", children: copied ? (_jsx(icons.Check, { className: "h-3.5 w-3.5 text-green-500" })) : (_jsx(icons.Copy, { className: "h-3.5 w-3.5" })) })] }));
|
|
78
|
+
};
|
|
24
79
|
/**
|
|
25
80
|
* State-machine gate for per-row actions.
|
|
26
81
|
*
|
|
@@ -124,6 +179,13 @@ const BadgeWithEndpointOptions = ({ endpoint, value }) => {
|
|
|
124
179
|
return _jsx(OptionBadge, { option: option, fallback: String(value) });
|
|
125
180
|
return _jsx(Badge, { variant: "outline", children: String(value) });
|
|
126
181
|
};
|
|
182
|
+
/**
|
|
183
|
+
* Generic avatar-style cell: round/rounded photo (or initials fallback) +
|
|
184
|
+
* primary name + optional subtitle. Backs the `avatar`/`search` columns as
|
|
185
|
+
* well as the `creator`/`user` cellStyles. Paths are parameterised so the same
|
|
186
|
+
* JSX serves every variant.
|
|
187
|
+
*/
|
|
188
|
+
const AvatarCell = ({ name, desc, avatarSrc, getImageUrl }) => (_jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [_jsxs(Avatar, { className: "h-8 w-8 rounded-lg ring-1 ring-border/50", children: [_jsx(AvatarImage, { src: avatarSrc ? getImageUrl(avatarSrc) : '', alt: name, className: "object-cover" }), _jsx(AvatarFallback, { className: "text-[10px] font-bold bg-primary/5 text-primary rounded-lg", children: getInitials(name) })] }), _jsxs("div", { className: "flex flex-col min-w-0 overflow-hidden", children: [_jsx("span", { className: "font-medium text-sm truncate leading-none mb-0.5 text-foreground/90", children: name }), desc && (_jsx("span", { className: "text-[11px] text-muted-foreground truncate leading-none", children: desc }))] })] }));
|
|
127
189
|
/**
|
|
128
190
|
* Builds the canonical column factory used by `<DynamicTable>` when the host
|
|
129
191
|
* does not supply its own. Pass `{ getImageUrl, apiBaseUrl }` to wire avatar
|
|
@@ -196,7 +258,26 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
|
|
|
196
258
|
if (renderAs === 'relation-badge-list') {
|
|
197
259
|
return renderRelationBadges(value, col);
|
|
198
260
|
}
|
|
199
|
-
|
|
261
|
+
// Generic badge (no options/endpoint) — still pill it.
|
|
262
|
+
if (renderAs === 'badge') {
|
|
263
|
+
if (!value && value !== 0)
|
|
264
|
+
return _jsx(EmptyCell, {});
|
|
265
|
+
return _jsx(Badge, { variant: "outline", children: String(value) });
|
|
266
|
+
}
|
|
267
|
+
// Status — semantic color by value, options color wins.
|
|
268
|
+
if (renderAs === 'status') {
|
|
269
|
+
if (!value && value !== 0)
|
|
270
|
+
return _jsx(EmptyCell, {});
|
|
271
|
+
const sv = String(value);
|
|
272
|
+
const option = col.options?.find((o) => o.value === sv);
|
|
273
|
+
if (option)
|
|
274
|
+
return _jsx(OptionBadge, { option: option, fallback: sv });
|
|
275
|
+
const isDark = typeof document !== 'undefined' &&
|
|
276
|
+
document.documentElement.classList.contains('dark');
|
|
277
|
+
const styles = generateBadgeStyles(statusColorFor(sv), { isDark });
|
|
278
|
+
return (_jsx(Badge, { variant: "outline", className: "border-0 capitalize", style: styles, children: sv }));
|
|
279
|
+
}
|
|
280
|
+
switch (renderAs) {
|
|
200
281
|
case 'date': {
|
|
201
282
|
if (!value)
|
|
202
283
|
return _jsx("span", { className: "text-muted-foreground", children: "-" });
|
|
@@ -212,36 +293,143 @@ export function makeDefaultGetDynamicColumns(helpers = {}) {
|
|
|
212
293
|
}
|
|
213
294
|
}
|
|
214
295
|
case 'search':
|
|
215
|
-
case 'avatar':
|
|
216
|
-
|
|
296
|
+
case 'avatar':
|
|
297
|
+
case 'creator':
|
|
298
|
+
case 'user': {
|
|
299
|
+
// `creator`/`user` resolve the name from an explicit
|
|
300
|
+
// styleConfig.name_field first, then the legacy
|
|
301
|
+
// tooltip/displayField hints, then the column key.
|
|
302
|
+
const namePath = styleCfg(col, 'name_field', 'nameField') ||
|
|
303
|
+
col.tooltip ||
|
|
304
|
+
col.displayField ||
|
|
305
|
+
col.key;
|
|
217
306
|
const name = getNestedValue(row.original, namePath) || 'N/A';
|
|
218
307
|
const desc = getNestedValue(row.original, col.description || '');
|
|
308
|
+
const basePath = styleCfg(col, 'base_path', 'basePath') ?? col.basePath ?? '';
|
|
219
309
|
let avatarSrc;
|
|
220
310
|
if (col.key.includes('.')) {
|
|
311
|
+
// Look for a sibling `.avatar` or `.photo` field.
|
|
221
312
|
const parentPath = col.key.split('.').slice(0, -1).join('.');
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
if (
|
|
225
|
-
avatarSrc = String(
|
|
226
|
-
}
|
|
227
|
-
else if (value &&
|
|
228
|
-
(String(value).startsWith('http') || String(value).startsWith('https'))) {
|
|
229
|
-
avatarSrc = String(value);
|
|
313
|
+
const sibling = getNestedValue(row.original, `${parentPath}.avatar`) ||
|
|
314
|
+
getNestedValue(row.original, `${parentPath}.photo`);
|
|
315
|
+
if (sibling)
|
|
316
|
+
avatarSrc = String(sibling);
|
|
230
317
|
}
|
|
231
|
-
|
|
232
|
-
|
|
318
|
+
if (!avatarSrc && value) {
|
|
319
|
+
if (String(value).startsWith('http')) {
|
|
320
|
+
avatarSrc = String(value);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
avatarSrc = `${apiBaseUrl}${basePath}${value}`;
|
|
324
|
+
}
|
|
233
325
|
}
|
|
234
|
-
return (
|
|
326
|
+
return (_jsx(AvatarCell, { name: String(name), desc: desc ? String(desc) : undefined, avatarSrc: avatarSrc, getImageUrl: getImageUrl }));
|
|
235
327
|
}
|
|
236
328
|
case 'relation-badge-list':
|
|
237
329
|
return renderRelationBadges(value, col);
|
|
330
|
+
case 'url':
|
|
331
|
+
case 'link': {
|
|
332
|
+
const labelField = styleCfg(col, 'label_field', 'labelField');
|
|
333
|
+
const urlField = styleCfg(col, 'url_field', 'urlField');
|
|
334
|
+
const rawUrl = urlField
|
|
335
|
+
? getNestedValue(row.original, urlField)
|
|
336
|
+
: value;
|
|
337
|
+
if (!rawUrl)
|
|
338
|
+
return _jsx(EmptyCell, {});
|
|
339
|
+
const urlStr = String(rawUrl);
|
|
340
|
+
const href = /^https?:\/\//i.test(urlStr) ? urlStr : `https://${urlStr}`;
|
|
341
|
+
let label;
|
|
342
|
+
if (labelField) {
|
|
343
|
+
label = String(getNestedValue(row.original, labelField) ?? href);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
try {
|
|
347
|
+
label = new URL(href).hostname;
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
label = urlStr;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const isExternal = !/^https?:\/\/(localhost|127\.)/i.test(href);
|
|
354
|
+
const newTab = styleCfg(col, 'new_tab', 'newTab') === true || isExternal;
|
|
355
|
+
const iconName = styleCfg(col, 'icon') || 'ExternalLink';
|
|
356
|
+
return (_jsxs("a", { href: href, ...(newTab
|
|
357
|
+
? { target: '_blank', rel: 'noopener noreferrer' }
|
|
358
|
+
: {}), className: "inline-flex items-center gap-1.5 text-sm font-medium text-primary hover:underline", onClick: (e) => e.stopPropagation(), children: [_jsx(DynamicIcon, { name: iconName, className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "truncate max-w-[260px]", children: label })] }));
|
|
359
|
+
}
|
|
360
|
+
case 'email': {
|
|
361
|
+
if (!value)
|
|
362
|
+
return _jsx(EmptyCell, {});
|
|
363
|
+
const email = String(value);
|
|
364
|
+
return (_jsxs("a", { href: `mailto:${email}`, className: "inline-flex items-center gap-1.5 text-sm font-medium text-primary hover:underline", onClick: (e) => e.stopPropagation(), children: [_jsx(icons.Mail, { className: "h-3.5 w-3.5 shrink-0 opacity-70" }), _jsx("span", { className: "truncate max-w-[260px]", children: email })] }));
|
|
365
|
+
}
|
|
366
|
+
case 'currency': {
|
|
367
|
+
const num = typeof value === 'number' ? value : Number(value);
|
|
368
|
+
if (value === null || value === undefined || isNaN(num))
|
|
369
|
+
return (_jsx("div", { className: "text-right", children: _jsx(EmptyCell, {}) }));
|
|
370
|
+
const decimals = styleCfg(col, 'decimals') ?? 2;
|
|
371
|
+
return (_jsx("span", { className: "block text-right font-medium tabular-nums", children: formatNumber(num, {
|
|
372
|
+
style: 'currency',
|
|
373
|
+
currency: resolveCurrency(col),
|
|
374
|
+
minimumFractionDigits: decimals,
|
|
375
|
+
maximumFractionDigits: decimals,
|
|
376
|
+
}, currentLanguage) }));
|
|
377
|
+
}
|
|
378
|
+
case 'number': {
|
|
379
|
+
const num = typeof value === 'number' ? value : Number(value);
|
|
380
|
+
if (value === null || value === undefined || isNaN(num))
|
|
381
|
+
return (_jsx("div", { className: "text-right", children: _jsx(EmptyCell, {}) }));
|
|
382
|
+
const decimals = styleCfg(col, 'decimals');
|
|
383
|
+
return (_jsx("span", { className: "block text-right font-medium tabular-nums", children: formatNumber(num, decimals !== undefined
|
|
384
|
+
? {
|
|
385
|
+
minimumFractionDigits: decimals,
|
|
386
|
+
maximumFractionDigits: decimals,
|
|
387
|
+
}
|
|
388
|
+
: {}, currentLanguage) }));
|
|
389
|
+
}
|
|
390
|
+
case 'percent':
|
|
391
|
+
case 'progress': {
|
|
392
|
+
const num = typeof value === 'number' ? value : Number(value);
|
|
393
|
+
if (value === null || value === undefined || isNaN(num))
|
|
394
|
+
return _jsx(EmptyCell, {});
|
|
395
|
+
const pct = Math.max(0, Math.min(100, num));
|
|
396
|
+
return (_jsxs("div", { className: "flex items-center gap-2 min-w-[120px]", children: [_jsx(Progress, { value: pct, className: "flex-1" }), _jsxs("span", { className: "text-xs font-medium tabular-nums text-muted-foreground w-9 text-right", children: [Math.round(pct), "%"] })] }));
|
|
397
|
+
}
|
|
398
|
+
case 'tags': {
|
|
399
|
+
const list = Array.isArray(value)
|
|
400
|
+
? value.map(String)
|
|
401
|
+
: value
|
|
402
|
+
? String(value)
|
|
403
|
+
.split(',')
|
|
404
|
+
.map((s) => s.trim())
|
|
405
|
+
.filter(Boolean)
|
|
406
|
+
: [];
|
|
407
|
+
if (list.length === 0)
|
|
408
|
+
return _jsx(EmptyCell, {});
|
|
409
|
+
return (_jsx("div", { className: "flex flex-wrap gap-1", children: list.map((tag, i) => (_jsx(Badge, { variant: "secondary", className: "px-1.5 py-0 text-[10px]", children: tag }, `${col.key}-${i}`))) }));
|
|
410
|
+
}
|
|
411
|
+
case 'color': {
|
|
412
|
+
if (!value)
|
|
413
|
+
return _jsx(EmptyCell, {});
|
|
414
|
+
const hex = String(value);
|
|
415
|
+
return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-4 w-4 rounded border border-border/60 shrink-0", style: { background: hex } }), _jsx("code", { className: "font-mono text-xs text-muted-foreground", children: hex })] }));
|
|
416
|
+
}
|
|
417
|
+
case 'code':
|
|
418
|
+
case 'truncate-text': {
|
|
419
|
+
if (value === null || value === undefined || value === '')
|
|
420
|
+
return _jsx(EmptyCell, {});
|
|
421
|
+
const maxLength = styleCfg(col, 'max_length', 'maxLength');
|
|
422
|
+
return _jsx(CodeCell, { text: String(value), maxLength: maxLength });
|
|
423
|
+
}
|
|
238
424
|
case 'phone': {
|
|
239
425
|
if (!value)
|
|
240
426
|
return _jsx("span", { className: "text-muted-foreground", children: "-" });
|
|
241
427
|
return _jsx("span", { className: "font-medium text-sm", children: String(value) });
|
|
242
428
|
}
|
|
243
|
-
case 'boolean':
|
|
244
|
-
|
|
429
|
+
case 'boolean': {
|
|
430
|
+
const showText = styleCfg(col, 'show_text', 'showText') !== false;
|
|
431
|
+
return (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [value ? (_jsx(icons.Check, { className: "h-4 w-4 text-green-500" })) : (_jsx(icons.Minus, { className: "h-4 w-4 text-muted-foreground" })), showText && (_jsx("span", { className: "text-sm text-muted-foreground", children: value ? 'Sí' : 'No' }))] }));
|
|
432
|
+
}
|
|
245
433
|
case 'media-gallery': {
|
|
246
434
|
if (!value || (Array.isArray(value) && value.length === 0)) {
|
|
247
435
|
return _jsx("span", { className: "text-muted-foreground", children: "-" });
|
|
@@ -54,6 +54,15 @@ export interface BalanceState {
|
|
|
54
54
|
*/
|
|
55
55
|
export declare function evaluateBalance(field: ActionFieldDef, rows: any[] | undefined): BalanceState | undefined;
|
|
56
56
|
export declare function resolveWidget(field: ActionFieldDef): string;
|
|
57
|
+
/**
|
|
58
|
+
* Resolves a field's FK target, tolerating the camelCase `ref` (authored SDK
|
|
59
|
+
* shape) and the snake_case `source` / `relation` aliases the kernel manifest
|
|
60
|
+
* may serve for a belongs_to column. Returns the trimmed model key, or
|
|
61
|
+
* `undefined` when the field declares no relation.
|
|
62
|
+
*/
|
|
63
|
+
export declare function getFieldRef(field: ActionFieldDef): string | undefined;
|
|
64
|
+
/** True when a field declares an FK target the SDK can resolve options against. */
|
|
65
|
+
export declare function fieldHasRef(field: ActionFieldDef): boolean;
|
|
57
66
|
/**
|
|
58
67
|
* Normalizes an upload field's config, tolerating both the camelCase authored
|
|
59
68
|
* SDK shape and the snake_case the kernel serves (`max_size`, `storage_path`).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-form-schema.d.ts","sourceRoot":"","sources":["../src/dynamic-form-schema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAmB,MAAM,KAAK,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,SAAS,CAAA;AAiB9D;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS,GAAG,IAAI,CAEzF;AAcD,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE;;kBAMtD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,cAAc,EAAE,CAGrE;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,cAAc,GACtB;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAatG;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAO3C;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACjC,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,GAAG,EAAE,GAAG,SAAS,GACxB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWxB;AAED,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC3B,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,GAAG,EAAE,GAAG,SAAS,GACxB,YAAY,GAAG,SAAS,CAgB1B;AAqDD,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"dynamic-form-schema.d.ts","sourceRoot":"","sources":["../src/dynamic-form-schema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAmB,MAAM,KAAK,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,SAAS,CAAA;AAiB9D;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS,GAAG,IAAI,CAEzF;AAcD,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE;;kBAMtD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,cAAc,EAAE,CAGrE;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,cAAc,GACtB;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAatG;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAO3C;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACjC,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,GAAG,EAAE,GAAG,SAAS,GACxB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWxB;AAED,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC3B,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,GAAG,EAAE,GAAG,SAAS,GACxB,YAAY,GAAG,SAAS,CAgB1B;AAqDD,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CA4B3D;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAIrE;AAED,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAE1D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,GAAG;IACpD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB,CAaA"}
|
|
@@ -190,6 +190,13 @@ function fieldToZod(field) {
|
|
|
190
190
|
export function resolveWidget(field) {
|
|
191
191
|
if (field.widget)
|
|
192
192
|
return field.widget;
|
|
193
|
+
// S1: any field that declares an FK target (`ref`, or the snake_case
|
|
194
|
+
// `source`/`relation` the kernel may serve) renders as an async searchable
|
|
195
|
+
// single-select — NOT a raw text input. This wins over the `type` switch so
|
|
196
|
+
// a declared FK column is a picker regardless of its SQL column type
|
|
197
|
+
// (uuid/text/etc), matching the kernel's option-resolution semantics.
|
|
198
|
+
if (fieldHasRef(field))
|
|
199
|
+
return 'dynamic_select';
|
|
193
200
|
switch (field.type) {
|
|
194
201
|
case 'textarea': return 'textarea';
|
|
195
202
|
case 'select': return 'select';
|
|
@@ -202,9 +209,31 @@ export function resolveWidget(field) {
|
|
|
202
209
|
// File upload: POSTs to the host upload endpoint and stores the returned
|
|
203
210
|
// file url/path as the field value. Rendered by `UploadField`.
|
|
204
211
|
case 'upload': return 'upload';
|
|
212
|
+
// S2: media-bearing types resolve to the upload widget so an `image`
|
|
213
|
+
// (logo/photo) or generic `file`/`media` field gets a real file picker
|
|
214
|
+
// instead of a free-text input.
|
|
215
|
+
case 'image': return 'upload';
|
|
216
|
+
case 'media': return 'upload';
|
|
217
|
+
case 'file': return 'upload';
|
|
205
218
|
default: return 'text';
|
|
206
219
|
}
|
|
207
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Resolves a field's FK target, tolerating the camelCase `ref` (authored SDK
|
|
223
|
+
* shape) and the snake_case `source` / `relation` aliases the kernel manifest
|
|
224
|
+
* may serve for a belongs_to column. Returns the trimmed model key, or
|
|
225
|
+
* `undefined` when the field declares no relation.
|
|
226
|
+
*/
|
|
227
|
+
export function getFieldRef(field) {
|
|
228
|
+
const ref = field.ref ?? field.source ?? field.relation;
|
|
229
|
+
if (typeof ref === 'string' && ref.trim() !== '')
|
|
230
|
+
return ref.trim();
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
/** True when a field declares an FK target the SDK can resolve options against. */
|
|
234
|
+
export function fieldHasRef(field) {
|
|
235
|
+
return getFieldRef(field) !== undefined;
|
|
236
|
+
}
|
|
208
237
|
/**
|
|
209
238
|
* Normalizes an upload field's config, tolerating both the camelCase authored
|
|
210
239
|
* SDK shape and the snake_case the kernel serves (`max_size`, `storage_path`).
|
|
@@ -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":"AAuCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAgD7C,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,GAAG,CAAA;IACV,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;CAC7B;AAED,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,uBAAuB,2CA2KrF;AAED,eAAe,kBAAkB,CAAA"}
|
|
@@ -24,8 +24,28 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
24
24
|
// case — start empty and never hit this.
|
|
25
25
|
import { useEffect, useState } from 'react';
|
|
26
26
|
import { Button, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, Popover, PopoverContent, PopoverTrigger, } from '@asteby/metacore-ui/primitives';
|
|
27
|
-
import { Check, ChevronsUpDown, Loader2, Plus } from 'lucide-react';
|
|
27
|
+
import { Check, ChevronsUpDown, ImageIcon, Loader2, Plus } from 'lucide-react';
|
|
28
28
|
import { useOptionsResolver } from './use-options-resolver';
|
|
29
|
+
import { getFieldRef } from './dynamic-form-schema';
|
|
30
|
+
/**
|
|
31
|
+
* Small square thumbnail for an option's `image`. Falls back to a neutral
|
|
32
|
+
* placeholder icon when the option has no image so rows/triggers stay aligned.
|
|
33
|
+
* `size` is in pixels (kept small — 20–24px — so the picker reads as a list,
|
|
34
|
+
* not a gallery). Inline style for the box dimensions: arbitrary Tailwind
|
|
35
|
+
* classes from a federated addon don't always survive the host's class scan.
|
|
36
|
+
*/
|
|
37
|
+
function OptionThumb({ image, size = 20 }) {
|
|
38
|
+
const box = { width: size, height: size };
|
|
39
|
+
if (!image) {
|
|
40
|
+
return (_jsx("span", { className: "text-muted-foreground bg-muted flex shrink-0 items-center justify-center rounded-sm", style: box, "aria-hidden": true, children: _jsx(ImageIcon, { className: "size-3 opacity-60" }) }));
|
|
41
|
+
}
|
|
42
|
+
return (_jsx("img", { src: image, alt: "", "aria-hidden": true, loading: "lazy", className: "shrink-0 rounded-sm object-cover", style: box,
|
|
43
|
+
// A broken image url shouldn't leave a torn-icon glyph; collapse to
|
|
44
|
+
// the neutral placeholder background instead.
|
|
45
|
+
onError: (e) => {
|
|
46
|
+
e.currentTarget.style.visibility = 'hidden';
|
|
47
|
+
} }));
|
|
48
|
+
}
|
|
29
49
|
function useDebounced(value, ms) {
|
|
30
50
|
const [debounced, setDebounced] = useState(value);
|
|
31
51
|
useEffect(() => {
|
|
@@ -41,22 +61,33 @@ export function DynamicSelectField({ field, value, onChange }) {
|
|
|
41
61
|
// Remember the label of the option the user actually picked so the trigger
|
|
42
62
|
// shows a name (not a UUID) without a round-trip.
|
|
43
63
|
const [picked, setPicked] = useState(null);
|
|
64
|
+
// Tolerate the snake_case `source`/`relation` aliases the kernel may serve
|
|
65
|
+
// for the FK target, not just camelCase `ref`.
|
|
66
|
+
const fieldRef = getFieldRef(field);
|
|
44
67
|
const { options, loading } = useOptionsResolver({
|
|
45
68
|
modelKey: '',
|
|
46
69
|
fieldKey: 'id',
|
|
47
|
-
ref:
|
|
70
|
+
ref: fieldRef,
|
|
48
71
|
// searchEndpoint only drives the URL when there's no ref — ref is the
|
|
49
72
|
// canonical, kernel-derived path and wins.
|
|
50
|
-
endpoint:
|
|
73
|
+
endpoint: fieldRef ? undefined : field.searchEndpoint,
|
|
51
74
|
query: debounced,
|
|
52
75
|
limit: 20,
|
|
53
76
|
// Don't fetch until the popover opens (and keep fetching as the query
|
|
54
77
|
// changes while open).
|
|
55
78
|
enabled: open,
|
|
56
79
|
});
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
80
|
+
// The currently-selected option, resolved either from what the user picked
|
|
81
|
+
// (cached in `picked`) or from the loaded page. Drives both the trigger
|
|
82
|
+
// label and its thumbnail.
|
|
83
|
+
const selectedOption = (picked && String(picked.id) === String(value) ? picked : null) ??
|
|
84
|
+
options.find((o) => String(o.id) === String(value)) ??
|
|
85
|
+
null;
|
|
86
|
+
const selectedLabel = selectedOption?.label ?? (value ? String(value) : '');
|
|
87
|
+
// Only switch the picker into "with thumbnails" mode when the data actually
|
|
88
|
+
// carries images — a relation whose options have no `image` keeps the plain
|
|
89
|
+
// text list it had before (no empty placeholder column).
|
|
90
|
+
const hasImages = !!selectedOption?.image || options.some((o) => !!o.image);
|
|
60
91
|
const handlePick = (opt) => {
|
|
61
92
|
setPicked(opt);
|
|
62
93
|
onChange(String(opt.id));
|
|
@@ -69,11 +100,11 @@ export function DynamicSelectField({ field, value, onChange }) {
|
|
|
69
100
|
// hands back the new record and we select it immediately. No host import →
|
|
70
101
|
// no circular dependency; works for ANY dynamic_select with a `ref`.
|
|
71
102
|
const openCreate = () => {
|
|
72
|
-
if (!
|
|
103
|
+
if (!fieldRef || typeof window === 'undefined')
|
|
73
104
|
return;
|
|
74
105
|
window.dispatchEvent(new CustomEvent('metacore:create-record', {
|
|
75
106
|
detail: {
|
|
76
|
-
model:
|
|
107
|
+
model: fieldRef,
|
|
77
108
|
onCreated: (rec) => {
|
|
78
109
|
if (rec && rec.id != null) {
|
|
79
110
|
const id = String(rec.id);
|
|
@@ -88,12 +119,12 @@ export function DynamicSelectField({ field, value, onChange }) {
|
|
|
88
119
|
// to the cell. Without min-w-0 the combobox+button row sizes to its content
|
|
89
120
|
// (the long empty-state placeholder) and overflows the column, pushing the
|
|
90
121
|
// "+" off-screen — it only "fit" once a short value was selected.
|
|
91
|
-
return (_jsxs("div", { className: "flex w-full min-w-0 items-center gap-1.5", children: [_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", role: "combobox", "aria-expanded": open, id: field.key, className: "min-w-0 flex-1 justify-between font-normal", "data-empty": !value, children: [
|
|
122
|
+
return (_jsxs("div", { className: "flex w-full min-w-0 items-center gap-1.5", children: [_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", role: "combobox", "aria-expanded": open, id: field.key, className: "min-w-0 flex-1 justify-between font-normal", "data-empty": !value, children: [_jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [hasImages && value ? (_jsx(OptionThumb, { image: selectedOption?.image, size: 20 })) : null, _jsx("span", { className: 'min-w-0 flex-1 truncate ' + (selectedLabel ? '' : 'text-muted-foreground'), children: selectedLabel || field.placeholder || 'Buscar…' })] }), _jsx(ChevronsUpDown, { className: "ml-2 size-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "p-0", align: "start",
|
|
92
123
|
// Match the trigger width without an arbitrary Tailwind class
|
|
93
124
|
// (those don't always survive a consuming app's Tailwind scan).
|
|
94
125
|
style: { width: 'var(--radix-popover-trigger-width)' }, children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { placeholder: field.placeholder || 'Buscar…', value: search, onValueChange: setSearch }), _jsxs(CommandList, { children: [loading && (_jsxs("div", { className: "text-muted-foreground flex items-center justify-center gap-2 py-6 text-sm", children: [_jsx(Loader2, { className: "size-4 animate-spin" }), "Buscando\u2026"] })), !loading && options.length === 0 && (_jsx(CommandEmpty, { children: debounced ? 'Sin resultados' : 'Escribí para buscar…' })), !loading && options.length > 0 && (_jsx(CommandGroup, { className: "max-h-64 overflow-auto", children: options.map((opt) => {
|
|
95
126
|
const isSel = String(opt.id) === String(value);
|
|
96
|
-
return (_jsxs(CommandItem, { value: String(opt.id), onSelect: () => handlePick(opt), children: [_jsx(Check, { className: 'mr-2 size-4 ' + (isSel ? 'opacity-100' : 'opacity-0') }), _jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate", children: opt.label }), opt.description && (_jsx("span", { className: "text-muted-foreground truncate text-xs", children: opt.description }))] })] }, String(opt.id)));
|
|
97
|
-
}) }))] })] }) })] }),
|
|
127
|
+
return (_jsxs(CommandItem, { value: String(opt.id), onSelect: () => handlePick(opt), children: [_jsx(Check, { className: 'mr-2 size-4 shrink-0 ' + (isSel ? 'opacity-100' : 'opacity-0') }), hasImages && (_jsx(OptionThumb, { image: opt.image, size: 24 })), _jsxs("div", { className: "ml-2 flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate", children: opt.label }), opt.description && (_jsx("span", { className: "text-muted-foreground truncate text-xs", children: opt.description }))] })] }, String(opt.id)));
|
|
128
|
+
}) }))] })] }) })] }), fieldRef && (_jsx(Button, { type: "button", variant: "outline", size: "icon", className: "size-9 shrink-0", onClick: openCreate, title: `Crear ${field.label ?? fieldRef}`, "aria-label": `Crear ${field.label ?? fieldRef}`, children: _jsx(Plus, { className: "size-4" }) }))] }));
|
|
98
129
|
}
|
|
99
130
|
export default DynamicSelectField;
|
package/dist/types.d.ts
CHANGED
|
@@ -75,7 +75,7 @@ export type ColumnVisibility = 'all' | 'table' | 'modal' | 'list' | (string & {}
|
|
|
75
75
|
export interface ColumnDefinition {
|
|
76
76
|
key: string;
|
|
77
77
|
label: string;
|
|
78
|
-
type: 'text' | 'number' | 'date' | 'select' | 'search' | 'relation-badge-list' | 'avatar' | 'boolean' | 'phone' | 'media-gallery' | 'image';
|
|
78
|
+
type: 'text' | 'number' | 'date' | 'select' | 'search' | 'relation-badge-list' | 'avatar' | 'boolean' | 'phone' | 'media-gallery' | 'image' | 'url' | 'link' | 'email' | 'currency' | 'percent' | 'progress' | 'badge' | 'status' | 'tags' | 'color' | 'code' | 'truncate-text' | 'creator' | 'user';
|
|
79
79
|
sortable: boolean;
|
|
80
80
|
filterable: boolean;
|
|
81
81
|
hidden?: boolean;
|
|
@@ -156,6 +156,14 @@ export interface ActionFieldDef {
|
|
|
156
156
|
* `useOptionsResolver` against `/api/options/<ref>?field=id`.
|
|
157
157
|
*/
|
|
158
158
|
ref?: string;
|
|
159
|
+
/**
|
|
160
|
+
* snake_case aliases the kernel manifest may serve for a belongs_to FK
|
|
161
|
+
* target instead of `ref`. Treated as equivalent to `ref` by the SDK so a
|
|
162
|
+
* declared relation renders a searchable picker regardless of which key the
|
|
163
|
+
* backend emits.
|
|
164
|
+
*/
|
|
165
|
+
source?: string;
|
|
166
|
+
relation?: string;
|
|
159
167
|
/**
|
|
160
168
|
* Columns of a repeatable line-items group. Mirrors the kernel v3
|
|
161
169
|
* `ActionField.item_fields` (json `item_fields`). Present on a field
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,cAAc,EAAE,MAAM,CAAA;IACtB,iBAAiB,EAAE,MAAM,CAAA;IACzB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,UAAU,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,YAAY,EAAE,CAAA;CAC7B;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IACzB,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAA;IACZ,kEAAkE;IAClE,IAAI,EAAE,aAAa,GAAG,cAAc,CAAA;IACpC;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,YAAY,GAAG,cAAc,GAAG,MAAM,CAAA;IACnE,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACrF,cAAc,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEjF,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,
|
|
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": "13.
|
|
3
|
+
"version": "13.10.0",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"date-fns": ">=3",
|
|
35
35
|
"react-day-picker": ">=8",
|
|
36
36
|
"@asteby/metacore-sdk": "^3.1.0",
|
|
37
|
-
"@asteby/metacore-ui": "^2.1.
|
|
37
|
+
"@asteby/metacore-ui": "^2.1.2"
|
|
38
38
|
},
|
|
39
39
|
"peerDependenciesMeta": {
|
|
40
40
|
"@tanstack/react-router": {
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"typescript": "^6.0.0",
|
|
62
62
|
"vitest": "^4.0.0",
|
|
63
63
|
"zustand": "^5.0.0",
|
|
64
|
-
"@asteby/metacore-
|
|
65
|
-
"@asteby/metacore-
|
|
64
|
+
"@asteby/metacore-ui": "2.1.2",
|
|
65
|
+
"@asteby/metacore-sdk": "3.1.0"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"build": "tsc -p tsconfig.json",
|