@asteby/metacore-runtime-react 49.5.0 → 49.7.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 +12 -0
- package/dist/business/customer-picker.d.ts +8 -2
- package/dist/business/customer-picker.d.ts.map +1 -1
- package/dist/business/customer-picker.js +30 -27
- package/dist/business/index.d.ts +1 -0
- package/dist/business/index.d.ts.map +1 -1
- package/dist/business/index.js +1 -0
- package/dist/business/line-product-cell.d.ts +8 -10
- package/dist/business/line-product-cell.d.ts.map +1 -1
- package/dist/business/line-product-cell.js +13 -90
- package/dist/business/product-options.d.ts +41 -0
- package/dist/business/product-options.d.ts.map +1 -0
- package/dist/business/product-options.js +58 -0
- package/dist/business/product-picker.d.ts +5 -1
- package/dist/business/product-picker.d.ts.map +1 -1
- package/dist/business/product-picker.js +26 -35
- package/dist/business/relate-documents.d.ts +1 -1
- package/dist/business/relate-documents.d.ts.map +1 -1
- package/dist/business/relate-documents.js +6 -8
- package/dist/business/vehicle-picker.d.ts +8 -2
- package/dist/business/vehicle-picker.d.ts.map +1 -1
- package/dist/business/vehicle-picker.js +28 -28
- package/dist/dialogs/dynamic-record.d.ts.map +1 -1
- package/dist/dialogs/dynamic-record.js +28 -43
- package/dist/dynamic-multi-select-field.d.ts.map +1 -1
- package/dist/dynamic-multi-select-field.js +36 -6
- package/dist/dynamic-select-field.d.ts +9 -23
- package/dist/dynamic-select-field.d.ts.map +1 -1
- package/dist/dynamic-select-field.js +105 -172
- package/dist/entity-select.d.ts +5 -2
- package/dist/entity-select.d.ts.map +1 -1
- package/dist/entity-select.js +23 -116
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/query-batch.d.ts.map +1 -1
- package/dist/query-batch.js +4 -0
- package/dist/record-picker-actions.d.ts +40 -2
- package/dist/record-picker-actions.d.ts.map +1 -1
- package/dist/record-picker-actions.js +44 -4
- package/dist/record-picker-dialog.d.ts +23 -0
- package/dist/record-picker-dialog.d.ts.map +1 -0
- package/dist/record-picker-dialog.js +37 -0
- package/dist/record-picker-option.d.ts +25 -0
- package/dist/record-picker-option.d.ts.map +1 -0
- package/dist/record-picker-option.js +55 -0
- package/dist/record-picker.d.ts +149 -0
- package/dist/record-picker.d.ts.map +1 -0
- package/dist/record-picker.js +372 -0
- package/dist/use-option-ids.d.ts +34 -0
- package/dist/use-option-ids.d.ts.map +1 -0
- package/dist/use-option-ids.js +138 -0
- package/dist/use-options-resolver.d.ts +4 -0
- package/dist/use-options-resolver.d.ts.map +1 -1
- package/dist/use-options-resolver.js +10 -1
- package/package.json +1 -1
- package/src/__tests__/business-pickers.test.tsx +6 -3
- package/src/__tests__/document-editor-inline-selects.test.tsx +2 -1
- package/src/__tests__/document-form-dialog.test.tsx +4 -3
- package/src/__tests__/document-lines-grid.test.tsx +2 -1
- package/src/__tests__/edit-field-polish.test.tsx +6 -3
- package/src/__tests__/option-ids.test.tsx +181 -0
- package/src/__tests__/record-picker.test.tsx +469 -0
- package/src/business/customer-picker.tsx +87 -98
- package/src/business/index.ts +1 -0
- package/src/business/line-product-cell.tsx +55 -180
- package/src/business/product-options.tsx +119 -0
- package/src/business/product-picker.tsx +63 -126
- package/src/business/relate-documents.tsx +36 -45
- package/src/business/vehicle-picker.tsx +76 -99
- package/src/dialogs/dynamic-record.tsx +44 -97
- package/src/dynamic-multi-select-field.tsx +53 -15
- package/src/dynamic-select-field.tsx +188 -379
- package/src/entity-select.tsx +53 -202
- package/src/index.ts +35 -2
- package/src/query-batch.ts +3 -0
- package/src/record-picker-actions.tsx +101 -8
- package/src/record-picker-dialog.tsx +80 -0
- package/src/record-picker-option.tsx +96 -0
- package/src/record-picker.tsx +971 -0
- package/src/use-option-ids.ts +168 -0
- package/src/use-options-resolver.ts +11 -1
- package/dist/business/use-async-search.d.ts +0 -10
- package/dist/business/use-async-search.d.ts.map +0 -1
- package/dist/business/use-async-search.js +0 -38
- package/src/business/use-async-search.ts +0 -43
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// DynamicSelectField — async, searchable single-select for declarative forms.
|
|
3
|
+
// A configuration of the shared <RecordPicker> (record-picker.tsx).
|
|
3
4
|
//
|
|
4
5
|
// This is the declarative answer to "I don't want to type a raw FK UUID".
|
|
5
6
|
// Instead of a plain <select> that dumps every option (RefSelect) or a free
|
|
@@ -18,135 +19,86 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
18
19
|
// 1. field.ref → /options/<ref>?field=id (canonical, preferred)
|
|
19
20
|
// 2. field.searchEndpoint→ used verbatim as the options endpoint (escape hatch)
|
|
20
21
|
//
|
|
21
|
-
// Edit
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
22
|
+
// Edit mode: an EXISTING value is labelled from (in order) the option the user
|
|
23
|
+
// just picked, the loaded page, the caller's `seedOption`, and otherwise one
|
|
24
|
+
// `?ids=` lookup (useResolveOptionIds — batched and shared with every picker of
|
|
25
|
+
// the same ref on screen). While it resolves the trigger reads "Cargando…",
|
|
26
|
+
// never the raw id; an id that no longer exists reads "(registro eliminado)".
|
|
25
27
|
import { useEffect, useRef, useState } from 'react';
|
|
26
28
|
import { getOptionFilter } from './option-filter';
|
|
27
29
|
import { useTranslation } from 'react-i18next';
|
|
28
|
-
import { Badge, Button
|
|
29
|
-
import {
|
|
30
|
-
import { resolveColorCss } from '@asteby/metacore-ui/lib';
|
|
30
|
+
import { Badge, Button } from '@asteby/metacore-ui/primitives';
|
|
31
|
+
import { ScanLine } from 'lucide-react';
|
|
31
32
|
import { BarcodeScanner } from './barcode-scanner';
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
33
|
+
import { RecordPicker } from './record-picker';
|
|
34
|
+
import { OptionLead } from './record-picker-option';
|
|
35
|
+
import { recordLabel, requestRecordCreate, requestRecordEdit, withSearchPrefill } from './record-picker-actions';
|
|
35
36
|
import { useOptionsResolver } from './use-options-resolver';
|
|
37
|
+
import { useResolveOptionIds } from './use-option-ids';
|
|
38
|
+
import { useDebouncedValue } from './use-debounced-value';
|
|
36
39
|
import { getDependsOn, getFieldRef, resolveOptionsSource } from './dynamic-form-schema';
|
|
40
|
+
export { OptionLead, OptionThumb } from './record-picker-option';
|
|
37
41
|
/**
|
|
38
42
|
* Default hint shown when a cascading picker's depended-on field is still
|
|
39
43
|
* empty. Domain-neutral on purpose; a caller may override it per field via
|
|
40
44
|
* `dependsHint`.
|
|
41
45
|
*/
|
|
42
46
|
export const DEFAULT_DEPENDS_HINT = 'Selecciona primero el campo del que depende';
|
|
47
|
+
/** Label of a held value whose record no longer exists (or is not visible). */
|
|
48
|
+
export const DELETED_RECORD_LABEL = '(registro eliminado)';
|
|
43
49
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* pixels (kept small — 20–24px — so the picker reads as a list, not a gallery).
|
|
49
|
-
* Inline style for the box dimensions: arbitrary Tailwind classes from a
|
|
50
|
-
* federated addon don't always survive the host's class scan.
|
|
51
|
-
*/
|
|
52
|
-
export function OptionThumb({ image, name, size = 20, }) {
|
|
53
|
-
const [broken, setBroken] = useState(false);
|
|
54
|
-
const box = { width: size, height: size };
|
|
55
|
-
// Missing or 404 image (bare avatar filenames often 404) → initials, never
|
|
56
|
-
// an invisible box that looks like "no avatar".
|
|
57
|
-
if (!image || broken) {
|
|
58
|
-
return _jsx(InitialsAvatar, { name: name, size: size, rounded: "sm", tone: "neutral" });
|
|
59
|
-
}
|
|
60
|
-
// A lucide icon name stored where an image url/path is expected (the `icon`
|
|
61
|
-
// form widget's icon mode) → render the glyph instead of a broken <img>.
|
|
62
|
-
if (isLucideIconName(image)) {
|
|
63
|
-
return (_jsx("span", { className: "flex shrink-0 items-center justify-center rounded-sm bg-muted", style: box, "aria-hidden": true, children: _jsx(DynamicIcon, { name: image, className: "size-4" }) }));
|
|
64
|
-
}
|
|
65
|
-
return (_jsx("img", { src: image, alt: "", "aria-hidden": true, loading: "lazy", className: "shrink-0 rounded-sm object-cover", style: box, onError: () => setBroken(true) }));
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Leading visual for an option: a photo thumbnail (FK relations with an image),
|
|
69
|
-
* else a declared icon, else a color dot (enum/status options with a color).
|
|
70
|
-
* Returns null when the option carries none, so plain text options stay plain.
|
|
50
|
+
* Declarative `dynamic_select` (manifest `ref` / `optionsConfig.source`) on top
|
|
51
|
+
* of the shared {@link RecordPicker}: options from the canonical options
|
|
52
|
+
* endpoint (`useOptionsResolver`: option_filter, options[].when keep-value,
|
|
53
|
+
* cascade `filter_value`), inline create/edit through the host modal events.
|
|
71
54
|
*/
|
|
72
|
-
export function OptionLead({ option, size = 20, }) {
|
|
73
|
-
if (!option)
|
|
74
|
-
return null;
|
|
75
|
-
if (option.image)
|
|
76
|
-
return _jsx(OptionThumb, { image: option.image, name: option.label, size: size });
|
|
77
|
-
if (option.icon) {
|
|
78
|
-
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" }) }));
|
|
79
|
-
}
|
|
80
|
-
if (option.color) {
|
|
81
|
-
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 }));
|
|
82
|
-
}
|
|
83
|
-
// No image/icon/color: an imageless reference option. Show its initials
|
|
84
|
-
// (shared InitialsAvatar) so it stays visually aligned with the sibling
|
|
85
|
-
// options that DO carry a thumbnail, rather than a blank gap.
|
|
86
|
-
if (option.label)
|
|
87
|
-
return _jsx(InitialsAvatar, { name: option.label, size: size, rounded: "sm", tone: "neutral" });
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
function useDebounced(value, ms) {
|
|
91
|
-
return useDebouncedValue(value, ms);
|
|
92
|
-
}
|
|
93
55
|
export function DynamicSelectField({ field, value, onChange, onPick, seedOption, dependsValue, dependsHint, readonly = false, staticOptions = null, descriptionAsBadge = false, hideCreate = false, createDefaults, createLockedFields, invalid = false, }) {
|
|
94
56
|
const { t } = useTranslation();
|
|
95
57
|
const ph = (fallback) => field.placeholder ? t(field.placeholder, { defaultValue: field.placeholder }) : fallback;
|
|
96
58
|
const [open, setOpen] = useState(false);
|
|
97
59
|
const [search, setSearch] = useState('');
|
|
98
60
|
const [scanOpen, setScanOpen] = useState(false);
|
|
99
|
-
const debounced =
|
|
61
|
+
const debounced = useDebouncedValue(search, 250);
|
|
100
62
|
// Escaneo por cámara para "llenar rápido": opt-in por campo (`scan`). El
|
|
101
|
-
// botón aparece SIEMPRE que el campo lo declara (igual que el POS)
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
// cuando no hay cámara. Un código escaneado alimenta la búsqueda y abre el
|
|
105
|
-
// picker para elegir la referencia sin tipear el UUID.
|
|
63
|
+
// botón aparece SIEMPRE que el campo lo declara (igual que el POS). El
|
|
64
|
+
// BarcodeScanner ya degrada con un mensaje cuando no hay cámara. Un código
|
|
65
|
+
// escaneado alimenta la búsqueda y abre el picker.
|
|
106
66
|
const scanEnabled = !!(field.scan ?? field.scannable);
|
|
107
67
|
const handleScanDetected = (code) => {
|
|
108
68
|
setSearch(code);
|
|
109
69
|
setOpen(true);
|
|
110
70
|
};
|
|
111
|
-
// Remember the
|
|
112
|
-
//
|
|
71
|
+
// Remember the option the user actually picked so the trigger shows a name
|
|
72
|
+
// (not a UUID) without a round-trip.
|
|
113
73
|
const [picked, setPicked] = useState(null);
|
|
114
|
-
// Tolerate the snake_case `source`/`relation` aliases the
|
|
115
|
-
// for the FK target, not just camelCase `ref`.
|
|
74
|
+
// Tolerate the snake_case `source`/`relation` aliases for the FK target.
|
|
116
75
|
const fieldRef = getFieldRef(field);
|
|
117
|
-
//
|
|
118
|
-
// over the field's `ref` — query the SOURCE model with `field=<value>`;
|
|
119
|
-
// otherwise keep the canonical `ref`-based resolution.
|
|
76
|
+
// An `optionsConfig.source` (dependent/scoped picker) wins over `ref`.
|
|
120
77
|
const source = resolveOptionsSource(field);
|
|
121
78
|
// Cascade: a `dependsOn` field whose value is still empty leaves this
|
|
122
|
-
// picker disabled until the parent is set.
|
|
123
|
-
// value the caller threaded from the form context.
|
|
79
|
+
// picker disabled until the parent is set.
|
|
124
80
|
const dependsOn = getDependsOn(field);
|
|
125
81
|
const scope = dependsValue ? String(dependsValue) : '';
|
|
126
82
|
const blockedByDependency = !!dependsOn && scope === '';
|
|
127
83
|
const useStatic = Array.isArray(staticOptions);
|
|
128
84
|
const optionFilter = getOptionFilter(field);
|
|
85
|
+
// optionsConfig.source → `/options/<source>`. Else searchEndpoint only
|
|
86
|
+
// drives the URL when there's no ref (ref is canonical and wins).
|
|
87
|
+
const optionsEndpoint = source.endpoint ?? (source.ref ? undefined : field.searchEndpoint);
|
|
129
88
|
const { options: fetchedOptions, loading: fetchLoading } = useOptionsResolver({
|
|
130
89
|
modelKey: '',
|
|
131
90
|
fieldKey: source.fieldKey,
|
|
132
91
|
ref: source.ref,
|
|
133
|
-
|
|
134
|
-
// drives the URL when there's no ref — ref is the canonical,
|
|
135
|
-
// kernel-derived path and wins.
|
|
136
|
-
endpoint: source.endpoint ?? (source.ref ? undefined : field.searchEndpoint),
|
|
92
|
+
endpoint: optionsEndpoint,
|
|
137
93
|
query: debounced,
|
|
138
94
|
limit: 20,
|
|
139
|
-
// Cascade scope forwarded as filter_value (only when this field
|
|
140
|
-
// declares a dependency). Re-fetches when the parent value changes.
|
|
141
95
|
filterValue: dependsOn ? scope : undefined,
|
|
142
96
|
optionFilter,
|
|
143
97
|
keepValue: value,
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
// Static lists never hit the network.
|
|
149
|
-
enabled: !useStatic && (open || readonly) && !blockedByDependency,
|
|
98
|
+
// Fetch only while open (a held value's label comes from the `?ids=`
|
|
99
|
+
// lookup below, readonly included). Blocked cascades and static lists
|
|
100
|
+
// never fetch.
|
|
101
|
+
enabled: !useStatic && open && !blockedByDependency,
|
|
150
102
|
});
|
|
151
103
|
const options = useStatic
|
|
152
104
|
? staticOptions.filter((o) => {
|
|
@@ -158,8 +110,7 @@ export function DynamicSelectField({ field, value, onChange, onPick, seedOption,
|
|
|
158
110
|
})
|
|
159
111
|
: fetchedOptions;
|
|
160
112
|
const loading = useStatic ? false : fetchLoading;
|
|
161
|
-
//
|
|
162
|
-
// belongs to the new scope, so clear the selection (skip the initial mount).
|
|
113
|
+
// A new cascade scope invalidates the selection (skip the initial mount).
|
|
163
114
|
const prevScopeRef = useRef(scope);
|
|
164
115
|
useEffect(() => {
|
|
165
116
|
if (!dependsOn)
|
|
@@ -171,17 +122,27 @@ export function DynamicSelectField({ field, value, onChange, onPick, seedOption,
|
|
|
171
122
|
onChange('');
|
|
172
123
|
}
|
|
173
124
|
}, [dependsOn, scope, value, onChange]);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
options.find((o) => String(o.id) === String(value)) ??
|
|
179
|
-
(seedOption && String(seedOption.id) === String(value) ? seedOption : null) ??
|
|
125
|
+
const valueKey = value != null && value !== '' ? String(value) : '';
|
|
126
|
+
const knownOption = (picked && String(picked.id) === valueKey ? picked : null) ??
|
|
127
|
+
options.find((o) => String(o.id) === valueKey) ??
|
|
128
|
+
(seedOption && String(seedOption.id) === valueKey ? seedOption : null) ??
|
|
180
129
|
null;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
130
|
+
// A held value nobody has labelled yet: one `?ids=` lookup (no popover).
|
|
131
|
+
const needsLookup = !!valueKey && !knownOption && !useStatic;
|
|
132
|
+
const { resolved, loading: idsLoading } = useResolveOptionIds({
|
|
133
|
+
ref: source.ref,
|
|
134
|
+
endpoint: optionsEndpoint,
|
|
135
|
+
field: source.fieldKey,
|
|
136
|
+
ids: needsLookup ? [valueKey] : [],
|
|
137
|
+
enabled: needsLookup,
|
|
138
|
+
});
|
|
139
|
+
const resolution = needsLookup ? resolved.get(valueKey) : undefined;
|
|
140
|
+
const selectedOption = knownOption ?? (resolution?.status === 'found' ? resolution.option : null);
|
|
141
|
+
const valueResolving = needsLookup && !selectedOption && idsLoading;
|
|
142
|
+
const valueMissing = needsLookup && !selectedOption && resolution?.status === 'missing';
|
|
143
|
+
const selectedLabel = selectedOption?.label ?? (valueResolving ? '' : valueMissing ? DELETED_RECORD_LABEL : valueKey);
|
|
144
|
+
const resolvingNode = (_jsx("span", { className: "min-w-0 flex-1 truncate animate-pulse text-muted-foreground", "data-slot": "record-picker-value-loading", children: "Cargando\u2026" }));
|
|
145
|
+
const missingNode = (_jsx("span", { className: "min-w-0 flex-1 truncate italic text-muted-foreground", "data-slot": "record-picker-value-missing", children: DELETED_RECORD_LABEL }));
|
|
185
146
|
const handlePick = (opt) => {
|
|
186
147
|
setPicked(opt);
|
|
187
148
|
onChange(String(opt.id));
|
|
@@ -189,88 +150,60 @@ export function DynamicSelectField({ field, value, onChange, onPick, seedOption,
|
|
|
189
150
|
setOpen(false);
|
|
190
151
|
setSearch('');
|
|
191
152
|
};
|
|
192
|
-
|
|
193
|
-
//
|
|
194
|
-
// via
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// Inline-edit: with a value selected the joined pencil opens THAT record's
|
|
198
|
-
// edit modal through the sibling `metacore:edit-record` event (the host's
|
|
199
|
-
// RecordCreateBridge already listens for it). On save the trigger label
|
|
200
|
-
// refreshes from the returned record.
|
|
201
|
-
const openEdit = () => {
|
|
202
|
-
if (!fieldRef || !value || typeof window === 'undefined')
|
|
153
|
+
const toOption = (id, label) => ({ id, value: id, label, name: label });
|
|
154
|
+
// Inline create / edit: the REFERENCED model's real modal, rendered by the
|
|
155
|
+
// host (RecordCreateBridge) via decoupled window events — no host import.
|
|
156
|
+
const openCreate = (query) => {
|
|
157
|
+
if (!fieldRef)
|
|
203
158
|
return;
|
|
204
159
|
setOpen(false);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
160
|
+
requestRecordCreate({
|
|
161
|
+
model: fieldRef,
|
|
162
|
+
// The searched text seeds the new record's name; explicit
|
|
163
|
+
// createDefaults win. Keys that aren't fields are ignored.
|
|
164
|
+
defaults: withSearchPrefill(query, 'name', createDefaults),
|
|
165
|
+
lockedFields: createLockedFields,
|
|
166
|
+
onCreated: (rec) => {
|
|
167
|
+
if (rec && rec.id != null) {
|
|
168
|
+
const id = String(rec.id);
|
|
169
|
+
handlePick(toOption(id, recordLabel(rec) ?? id));
|
|
170
|
+
}
|
|
215
171
|
},
|
|
216
|
-
})
|
|
172
|
+
});
|
|
217
173
|
};
|
|
218
|
-
const
|
|
219
|
-
if (!fieldRef ||
|
|
174
|
+
const openEdit = () => {
|
|
175
|
+
if (!fieldRef || !value)
|
|
220
176
|
return;
|
|
221
177
|
setOpen(false);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
onCreated: (rec) => {
|
|
231
|
-
if (rec && rec.id != null) {
|
|
232
|
-
const id = String(rec.id);
|
|
233
|
-
const label = String(rec.name ?? rec.label ?? rec.title ?? rec.id);
|
|
234
|
-
handlePick({ id, value: id, label, name: label });
|
|
235
|
-
}
|
|
236
|
-
},
|
|
178
|
+
const id = String(value);
|
|
179
|
+
requestRecordEdit({
|
|
180
|
+
model: fieldRef,
|
|
181
|
+
recordId: id,
|
|
182
|
+
onSaved: (rec) => {
|
|
183
|
+
const label = recordLabel(rec);
|
|
184
|
+
if (label != null)
|
|
185
|
+
setPicked({ ...(selectedOption ?? toOption(id, label)), id, value: id, label, name: label });
|
|
237
186
|
},
|
|
238
|
-
})
|
|
187
|
+
});
|
|
239
188
|
};
|
|
240
|
-
|
|
241
|
-
// label (name) as a disabled control — no popover, no inline-create. While
|
|
242
|
-
// the eager fetch is in flight the label falls back to the seed/raw value,
|
|
243
|
-
// then snaps to the name once options arrive.
|
|
244
|
-
if (readonly) {
|
|
245
|
-
return (_jsx(Button, { type: "button", variant: "outline", role: "combobox", id: field.key, disabled: true, "aria-readonly": "true", className: "w-full min-w-0 cursor-default justify-start font-normal opacity-100", children: _jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [selectedOption ? _jsx(OptionLead, { option: selectedOption, size: 20 }) : null, _jsx("span", { className: 'min-w-0 flex-1 truncate ' + (selectedOption ? '' : 'text-muted-foreground'), children: selectedOption?.label ?? (loading ? 'Cargando…' : ph('—')) })] }) }));
|
|
246
|
-
}
|
|
247
|
-
// w-full + min-w-0: as a grid cell child, the row must be allowed to shrink
|
|
248
|
-
// to the cell. Without min-w-0 the combobox+button row sizes to its content
|
|
249
|
-
// (the long empty-state placeholder) and overflows the column, pushing the
|
|
250
|
-
// "+" off-screen — it only "fit" once a short value was selected.
|
|
251
|
-
const canMutate = !!fieldRef && !hideCreate && !useStatic;
|
|
252
|
-
const joined = canMutate && !blockedByDependency && hasRecordPickerAction(!!value, openCreate, openEdit);
|
|
189
|
+
const canMutate = !!fieldRef && !hideCreate && !useStatic && !blockedByDependency;
|
|
253
190
|
const fieldName = field.label ? t(field.label, { defaultValue: field.label }) : fieldRef ?? '';
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
: 'Sin resultados — escribe para filtrar' })), !loading && options.length > 0 && (_jsx(CommandGroup, { className: "max-h-64 overflow-auto", children: options.map((opt) => {
|
|
272
|
-
const isSel = String(opt.id) === String(value);
|
|
273
|
-
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') }), _jsx(OptionLead, { option: opt, size: 24 }), descriptionAsBadge ? (_jsxs("div", { className: "ml-2 flex min-w-0 flex-1 items-center gap-2", children: [_jsx("span", { className: "min-w-0 flex-1 truncate", children: opt.label }), opt.description ? (_jsx(Badge, { variant: "secondary", className: "shrink-0 font-normal tabular-nums", children: opt.description })) : null] })) : (_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)));
|
|
274
|
-
}) })), canMutate && !loading && (_jsx(PickerCreateItem, { label: fieldName, onSelect: openCreate }))] })] }) })] }), canMutate && !blockedByDependency && (_jsx(RecordPickerAction, { hasValue: !!value, label: fieldName, onCreate: openCreate, onEdit: openEdit }))] }), scanEnabled && (_jsx(Button, { type: "button", variant: "outline", size: "icon", className: "size-9 shrink-0", onClick: () => setScanOpen(true), title: "Escanear con la c\u00E1mara", "aria-label": "Escanear c\u00F3digo de barras con la c\u00E1mara", children: _jsx(ScanLine, { className: "size-4" }) })), scanEnabled && (_jsx(BarcodeScanner, { open: scanOpen, onClose: () => setScanOpen(false), onDetected: handleScanDetected, continuous: false, position: "fixed", title: `Escanear ${field.label ?? ''}`.trim() }))] }));
|
|
191
|
+
const badge = (opt) => opt.description ? (_jsx(Badge, { variant: "secondary", className: "shrink-0 font-normal tabular-nums", children: opt.description })) : null;
|
|
192
|
+
return (_jsx(RecordPicker, { slot: "dynamic-select", id: field.key, items: options, loading: loading, getKey: (o) => String(o.id), getLabel: (o) => o.label, value: value, selected: selectedOption, onSelect: handlePick, query: search, onQueryChange: setSearch, open: open && !blockedByDependency, onOpenChange: (o) => {
|
|
193
|
+
if (!blockedByDependency)
|
|
194
|
+
setOpen(o);
|
|
195
|
+
}, disabled: blockedByDependency, readOnly: readonly, invalid: invalid, renderLead: (o, where) => _jsx(OptionLead, { option: o, size: where === 'option' ? 24 : 20 }), getDescription: descriptionAsBadge ? undefined : (o) => o.description, renderTrailing: descriptionAsBadge ? (o) => badge(o) : undefined, renderValue: () => {
|
|
196
|
+
// Never flash the raw id while the label resolves.
|
|
197
|
+
if (valueResolving)
|
|
198
|
+
return resolvingNode;
|
|
199
|
+
if (valueMissing)
|
|
200
|
+
return missingNode;
|
|
201
|
+
if (readonly) {
|
|
202
|
+
return (_jsxs(_Fragment, { children: [selectedOption ? _jsx(OptionLead, { option: selectedOption, size: 20 }) : null, _jsx("span", { className: 'min-w-0 flex-1 truncate ' + (selectedOption ? '' : 'text-muted-foreground'), children: selectedOption?.label ?? ph('—') })] }));
|
|
203
|
+
}
|
|
204
|
+
return (_jsxs(_Fragment, { children: [value && selectedOption ? _jsx(OptionLead, { option: selectedOption, size: 20 }) : null, _jsx("span", { className: 'min-w-0 flex-1 truncate ' + (selectedLabel ? '' : 'text-muted-foreground'), children: blockedByDependency ? dependsHint || DEFAULT_DEPENDS_HINT : selectedLabel || ph('Buscar…') }), descriptionAsBadge && selectedOption ? badge(selectedOption) : null] }));
|
|
205
|
+
}, placeholder: ph('Buscar…'), searchPlaceholder: ph('Buscar…'), emptyText: debounced ? 'Sin resultados' : useStatic ? 'Sin opciones' : 'Sin resultados — escribe para filtrar', entityLabel: fieldName, onCreate: canMutate ? openCreate : undefined, onEdit: canMutate ? openEdit : undefined, triggerProps: {
|
|
206
|
+
'data-depends-blocked': blockedByDependency ? '' : undefined,
|
|
207
|
+
}, after: scanEnabled ? (_jsxs(_Fragment, { children: [_jsx(Button, { type: "button", variant: "outline", size: "icon", className: "size-9 shrink-0", onClick: () => setScanOpen(true), title: "Escanear con la c\u00E1mara", "aria-label": "Escanear c\u00F3digo de barras con la c\u00E1mara", children: _jsx(ScanLine, { className: "size-4" }) }), _jsx(BarcodeScanner, { open: scanOpen, onClose: () => setScanOpen(false), onDetected: handleScanDetected, continuous: false, position: "fixed", title: `Escanear ${field.label ?? ''}`.trim() })] })) : null }));
|
|
275
208
|
}
|
|
276
209
|
export default DynamicSelectField;
|
package/dist/entity-select.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { LucideIcon } from 'lucide-react';
|
|
2
2
|
/** One searchable option: `value` is the id, `label` the display text. */
|
|
3
3
|
export interface EntitySelectOption {
|
|
4
4
|
value: string;
|
|
@@ -52,5 +52,8 @@ export interface EntitySelectProps {
|
|
|
52
52
|
*/
|
|
53
53
|
lockedCreateFields?: string[];
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
/**
|
|
56
|
+
* @deprecated Thin wrapper over {@link RecordPicker}; prefer it for new code.
|
|
57
|
+
*/
|
|
58
|
+
export declare function EntitySelect({ model, value, label, onSelect, fetcher, icon, placeholder, searchPlaceholder, emptyText, preload, canCreate, canEdit, endpoint, labelField, disabled, createDefaults, lockedCreateFields, }: EntitySelectProps): import("react").JSX.Element;
|
|
56
59
|
//# sourceMappingURL=entity-select.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-select.d.ts","sourceRoot":"","sources":["../src/entity-select.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"entity-select.d.ts","sourceRoot":"","sources":["../src/entity-select.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAM9C,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,iBAAiB;IAC9B,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAA;IACb,uCAAuC;IACvC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,kEAAkE;IAClE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,sDAAsD;IACtD,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC3D,sFAAsF;IACtF,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAA;IAE1E,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAElB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACxC;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;CAChC;AAOD;;GAEG;AACH,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,KAAK,EACL,KAAK,EACL,QAAQ,EACR,OAAO,EACP,IAAI,EACJ,WAA4B,EAC5B,iBAA6B,EAC7B,SAA4B,EAC5B,OAAe,EACf,SAAS,EACT,OAAO,EACP,QAAQ,EACR,UAAmB,EACnB,QAAgB,EAChB,cAAc,EACd,kBAAkB,GACrB,EAAE,iBAAiB,+BAmDnB"}
|
package/dist/entity-select.js
CHANGED
|
@@ -1,126 +1,33 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// affordances every "pick a related record" control should have, exactly like
|
|
6
|
-
// the dynamic create modal's relation fields (Categoría/Marca) do:
|
|
7
|
-
//
|
|
8
|
-
// - nothing selected → a "+" that opens the model's CREATE dialog, and
|
|
9
|
-
// auto-selects the record it creates;
|
|
10
|
-
// - a record selected → a pencil that opens that record's EDIT dialog.
|
|
11
|
-
//
|
|
12
|
-
// Both affordances are gated by the kernel permissions (useCan): the "+" only
|
|
13
|
-
// shows when the user can create the model, the pencil only when they can edit
|
|
14
|
-
// it. Everything is DYNAMIC — the create/edit form comes from the model's
|
|
15
|
-
// `/metadata/modal/:model` schema via <CreateRecordDialog>, so no per-model form
|
|
16
|
-
// code is needed. This lives in the SDK so POS, purchases and any future addon
|
|
17
|
-
// share ONE implementation instead of each re-porting a bespoke picker.
|
|
18
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
19
|
-
import { Search, X } from 'lucide-react';
|
|
20
|
-
import { Button, Command, CommandGroup, CommandInput, CommandItem, CommandList, Popover, PopoverContent, PopoverTrigger, } from '@asteby/metacore-ui';
|
|
21
|
-
import { CreateRecordDialog } from './dialogs/create-record-dialog';
|
|
22
|
-
import { JOINED_TRIGGER_CLASS, PickerCreateItem, RecordPickerAction, hasRecordPickerAction } from './record-picker-actions';
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { RecordPicker, useLatestSearch } from './record-picker';
|
|
3
|
+
import { recordLabel } from './record-picker-actions';
|
|
4
|
+
import { useRecordPickerDialog } from './record-picker-dialog';
|
|
23
5
|
import { useCan } from './permissions-context';
|
|
24
|
-
import { useApi } from './api-context';
|
|
25
6
|
/** Lowercase model → permission capability namespace (Supplier → supplier). */
|
|
26
7
|
function capabilityNamespace(model) {
|
|
27
8
|
return model.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
|
|
28
9
|
}
|
|
29
|
-
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated Thin wrapper over {@link RecordPicker}; prefer it for new code.
|
|
12
|
+
*/
|
|
13
|
+
export function EntitySelect({ model, value, label, onSelect, fetcher, icon, placeholder = 'Seleccionar…', searchPlaceholder = 'Buscar…', emptyText = 'Sin resultados', preload = false, canCreate, canEdit, endpoint, labelField = 'name', disabled = false, createDefaults, lockedCreateFields, }) {
|
|
30
14
|
const can = useCan();
|
|
31
|
-
const api = useApi();
|
|
32
15
|
const ns = capabilityNamespace(model);
|
|
33
16
|
const mayCreate = canCreate ?? can(`${ns}.create`);
|
|
34
17
|
const mayEdit = canEdit ?? can(`${ns}.update`);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const rows = await fetcher(q, signal);
|
|
51
|
-
if (!signal.aborted)
|
|
52
|
-
setResults(rows);
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
if (!signal.aborted)
|
|
56
|
-
setResults([]);
|
|
57
|
-
}
|
|
58
|
-
finally {
|
|
59
|
-
if (!signal.aborted)
|
|
60
|
-
setIsLoading(false);
|
|
61
|
-
}
|
|
62
|
-
}, [fetcher, minChars]);
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
if (!open)
|
|
65
|
-
return;
|
|
66
|
-
const controller = new AbortController();
|
|
67
|
-
const timeout = setTimeout(() => run(searchTerm, controller.signal), 250);
|
|
68
|
-
return () => {
|
|
69
|
-
clearTimeout(timeout);
|
|
70
|
-
controller.abort();
|
|
71
|
-
};
|
|
72
|
-
}, [searchTerm, run, open]);
|
|
73
|
-
const pick = (row) => {
|
|
74
|
-
onSelect(row.value, row.label);
|
|
75
|
-
setOpen(false);
|
|
76
|
-
setSearchTerm('');
|
|
77
|
-
};
|
|
78
|
-
const clear = (e) => {
|
|
79
|
-
e.stopPropagation();
|
|
80
|
-
onSelect(null, null);
|
|
81
|
-
};
|
|
82
|
-
const openCreate = () => {
|
|
83
|
-
setOpen(false);
|
|
84
|
-
setDialogRecordId(undefined);
|
|
85
|
-
setDialogOpen(true);
|
|
86
|
-
};
|
|
87
|
-
const openEdit = () => {
|
|
88
|
-
if (!value)
|
|
89
|
-
return;
|
|
90
|
-
setOpen(false);
|
|
91
|
-
setDialogRecordId(value);
|
|
92
|
-
setDialogOpen(true);
|
|
93
|
-
};
|
|
94
|
-
// Read the {id,label} off a saved record and select it. The transport
|
|
95
|
-
// matches the standard org-scoped CRUD the dynamic modal uses, so create/edit
|
|
96
|
-
// stay consistent with the rest of the app.
|
|
97
|
-
const selectSaved = (rec) => {
|
|
98
|
-
if (!rec)
|
|
99
|
-
return;
|
|
100
|
-
const id = rec.id != null ? String(rec.id) : value ?? '';
|
|
101
|
-
const lbl = (rec[labelField] != null && String(rec[labelField])) ||
|
|
102
|
-
(rec.name != null && String(rec.name)) ||
|
|
103
|
-
label ||
|
|
104
|
-
id;
|
|
105
|
-
onSelect(id, lbl);
|
|
106
|
-
};
|
|
107
|
-
const onCreate = mayCreate ? openCreate : undefined;
|
|
108
|
-
const onEdit = mayEdit ? openEdit : undefined;
|
|
109
|
-
const joined = hasRecordPickerAction(!!value, onCreate, onEdit);
|
|
110
|
-
return (_jsxs("div", { className: "flex items-center", "data-slot": "entity-select", children: [_jsxs(Popover, { open: open, onOpenChange: disabled ? undefined : setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", disabled: disabled, className: 'w-full min-w-0 flex-1 justify-start gap-2 font-normal' + (joined ? ` ${JOINED_TRIGGER_CLASS}` : ''), children: [Icon && _jsx(Icon, { className: "text-muted-foreground size-4 shrink-0" }), _jsx("span", { className: "flex-1 truncate text-left", children: label ?? placeholder }), value && (_jsx("span", { role: "button", tabIndex: 0, onClick: clear, onKeyDown: (e) => {
|
|
111
|
-
if (e.key === 'Enter' || e.key === ' ')
|
|
112
|
-
clear(e);
|
|
113
|
-
}, className: "hover:bg-accent ml-auto shrink-0 rounded-sm p-0.5", children: _jsx(X, { className: "size-3.5" }) }))] }) }), _jsx(PopoverContent, { className: "p-0", align: "start", collisionPadding: 8, style: { width: 'max(var(--radix-popover-trigger-width), 14rem)', maxWidth: 'calc(100vw - 1rem)' }, children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { placeholder: searchPlaceholder, value: searchTerm, onValueChange: setSearchTerm }), _jsxs(CommandList, { children: [isLoading && (_jsx("div", { className: "text-muted-foreground py-4 text-center text-sm", children: "Buscando\u2026" })), !isLoading &&
|
|
114
|
-
searchTerm.length >= minChars &&
|
|
115
|
-
results.length === 0 && _jsx("div", { className: "text-muted-foreground py-6 text-center text-sm", children: emptyText }), !isLoading && results.length > 0 && (_jsx(CommandGroup, { className: "max-h-64 overflow-auto", children: results.map((row) => (_jsxs(CommandItem, { value: row.value, onSelect: () => pick(row), className: "flex flex-col items-start gap-0.5", children: [_jsx("span", { className: "text-sm font-medium", children: row.label }), row.description && (_jsx("span", { className: "text-muted-foreground text-xs", children: row.description }))] }, row.value))) })), !isLoading && !preload && searchTerm.length < minChars && (_jsxs("div", { className: "text-muted-foreground flex flex-col items-center gap-1 py-6", children: [_jsx(Search, { className: "size-5" }), _jsx("span", { className: "text-xs", children: "Escribe al menos 2 caracteres" })] })), onCreate && !isLoading && _jsx(PickerCreateItem, { label: model, onSelect: openCreate })] })] }) })] }), _jsx(RecordPickerAction, { hasValue: !!value, label: model, onCreate: onCreate, onEdit: onEdit, disabled: disabled }), dialogOpen && (_jsx(CreateRecordDialog, { modelKey: model, open: dialogOpen, onOpenChange: setDialogOpen, recordId: dialogRecordId, endpoint: base, defaults: dialogRecordId ? undefined : createDefaults, lockedFields: dialogRecordId ? undefined : lockedCreateFields, onCreate: async (data) => {
|
|
116
|
-
const res = await api.post(base, data);
|
|
117
|
-
const rec = (res.data?.data ?? res.data);
|
|
118
|
-
selectSaved(rec);
|
|
119
|
-
return rec.id != null ? { id: String(rec.id) } : undefined;
|
|
120
|
-
}, onUpdate: async (id, data) => {
|
|
121
|
-
const res = await api.put(`${base}/${id}`, data);
|
|
122
|
-
const rec = (res.data?.data ?? res.data);
|
|
123
|
-
selectSaved({ id, ...rec });
|
|
124
|
-
return { id: String(id) };
|
|
125
|
-
} }))] }));
|
|
18
|
+
// Read the {id,label} off a saved record and select it (create) or
|
|
19
|
+
// refresh its label (edit).
|
|
20
|
+
const { openCreate, openEdit, dialog } = useRecordPickerDialog({
|
|
21
|
+
model,
|
|
22
|
+
endpoint,
|
|
23
|
+
prefillField: labelField,
|
|
24
|
+
createDefaults,
|
|
25
|
+
lockedCreateFields,
|
|
26
|
+
onSaved: (rec) => {
|
|
27
|
+
const id = rec.id != null ? String(rec.id) : value ?? '';
|
|
28
|
+
onSelect(id, recordLabel(rec, labelField) ?? label ?? id);
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
const search = useLatestSearch(fetcher);
|
|
32
|
+
return (_jsxs(_Fragment, { children: [_jsx(RecordPicker, { slot: "entity-select", search: search, minChars: preload ? 0 : 2, minCharsText: "Escribe al menos 2 caracteres", getKey: (o) => o.value, getLabel: (o) => o.label, getDescription: (o) => o.description, showCheck: false, value: value, selected: value ? { value, label: label ?? value } : null, onSelect: (o) => onSelect(o.value, o.label), onClear: () => onSelect(null, null), icon: icon, placeholder: placeholder, renderValue: (sel) => (_jsx("span", { className: 'min-w-0 flex-1 truncate' + (sel ? '' : ' text-muted-foreground'), children: label ?? placeholder })), searchPlaceholder: searchPlaceholder, emptyText: emptyText, entityLabel: model, onCreate: mayCreate ? (q) => openCreate(q) : undefined, onEdit: mayEdit && value ? () => openEdit(value) : undefined, disabled: disabled }), dialog] }));
|
|
126
33
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -85,8 +85,13 @@ export type { DynamicRecordDialogProps, FieldDef, FieldOption, GetImageUrl } fro
|
|
|
85
85
|
export { CreateRecordDialog } from './dialogs/create-record-dialog';
|
|
86
86
|
export { EntitySelect } from './entity-select';
|
|
87
87
|
export type { EntitySelectProps, EntitySelectOption } from './entity-select';
|
|
88
|
-
export {
|
|
89
|
-
export type {
|
|
88
|
+
export { RecordPicker, useRecordSearch, useLatestSearch, RECORD_PICKER_Z_INDEX } from './record-picker';
|
|
89
|
+
export type { RecordPickerProps, RecordPickerItemState, RecordPickerVariant, RecordPickerTrigger, UseRecordSearchOptions, UseRecordSearchResult, } from './record-picker';
|
|
90
|
+
export { RecordPickerAction, PickerCreateItem, JOINED_TRIGGER_CLASS, hasRecordPickerAction, requestRecordCreate, requestRecordEdit, recordLabel, withSearchPrefill, } from './record-picker-actions';
|
|
91
|
+
export type { RecordPickerActionProps, RecordCreateRequest, RecordEditRequest } from './record-picker-actions';
|
|
92
|
+
export { useRecordPickerDialog } from './record-picker-dialog';
|
|
93
|
+
export type { UseRecordPickerDialogOptions, UseRecordPickerDialogResult } from './record-picker-dialog';
|
|
94
|
+
export { OptionLead, OptionThumb } from './record-picker-option';
|
|
90
95
|
export { ViewRecordDialog } from './dialogs/view-record-dialog';
|
|
91
96
|
export type { ModelKey, ModelSchema, CreateResult, RecordDialogProps, CreateRecordDialogProps, ViewRecordDialogProps, } from './dialogs/types';
|
|
92
97
|
export { ExportDialog } from './dialogs/export';
|
|
@@ -99,6 +104,8 @@ export { registerAgentResultRenderer, resolveAgentResultRenderer, listAgentResul
|
|
|
99
104
|
export { isColumnVisibleInTable, isColumnVisibleInModal, isColumnVisibleInLineSubtable, AUDIT_SUBTABLE_COLUMN_KEYS, getSearchableColumnKeys, } from './column-visibility';
|
|
100
105
|
export { batchGet, loadQueryPart, nameBatchTokens, narrowInToken, optionsBatchToken, optionsModelFromUrl, rememberInRows, invalidateQueryBatchData, resetQueryBatchCache, splitInToken, tokenForGet, type InListToken, type NarrowedIn, type QueryPart, } from './query-batch';
|
|
101
106
|
export { useOptionsResolver, invalidateOptionsCache, projectOption, type ResolvedOption, type OptionsMeta, type UseOptionsResolverArgs, type UseOptionsResolverResult, } from './use-options-resolver';
|
|
107
|
+
export { useResolveOptionIds, OPTION_IDS_CHUNK, type OptionIdResolution, type UseResolveOptionIdsArgs, type UseResolveOptionIdsResult, } from './use-option-ids';
|
|
108
|
+
export { DELETED_RECORD_LABEL } from './dynamic-select-field';
|
|
102
109
|
export { setOrgConfigBridge, getOrgConfigBridge, resolveValidatorToken, type OrgConfigBridge, } from './use-org-config-bridge';
|
|
103
110
|
export { registerValidator, getDependsOn, resolveDependsValue, getOptionsConfig, resolveOptionsSource, getVisibleWhen, evaluateVisibleWhen, ATTRIBUTE_CLASSES_KEY, scopeValueFromFilterToken, buildListScopeValues, evaluateVisibleWhenForListScope, applyLineItemRowFormulas, computeLineItemTotals, toNumber, } from './dynamic-form-schema';
|
|
104
111
|
export { BranchCreateGateProvider, useBranchCreateGate, type BranchCreateGateApi, } from './branch-create-gate';
|