@asteby/metacore-runtime-react 18.27.0 → 18.28.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 +6 -0
- package/dist/action-modal-dispatcher.d.ts.map +1 -1
- package/dist/action-modal-dispatcher.js +19 -1
- package/dist/dynamic-line-items.js +3 -1
- package/dist/dynamic-select-field.d.ts +9 -1
- package/dist/dynamic-select-field.d.ts.map +1 -1
- package/dist/dynamic-select-field.js +11 -3
- package/package.json +1 -1
- package/src/action-modal-dispatcher.tsx +25 -1
- package/src/dynamic-line-items.tsx +3 -1
- package/src/dynamic-select-field.tsx +37 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 18.28.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d3b7060: feat(line-items): a PrefillSpec can `lock` item-field columns — locked dynamic_select cells render as a resolved, read-only NAME (eager option fetch, never the raw id) instead of an editable picker. Used for receive-goods/partial-reception lines whose product is dictated by the source document; the create flow (no prefill) stays fully editable.
|
|
8
|
+
|
|
3
9
|
## 18.27.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-modal-dispatcher.d.ts","sourceRoot":"","sources":["../src/action-modal-dispatcher.tsx"],"names":[],"mappings":"AA+CA,OAAO,EACH,KAAK,cAAc,EACnB,KAAK,gBAAgB,EAExB,MAAM,sBAAsB,CAAA;AAE7B,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"action-modal-dispatcher.d.ts","sourceRoot":"","sources":["../src/action-modal-dispatcher.tsx"],"names":[],"mappings":"AA+CA,OAAO,EACH,KAAK,cAAc,EACnB,KAAK,gBAAgB,EAExB,MAAM,sBAAsB,CAAA;AAE7B,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAA;AA0FhD,wBAAgB,qBAAqB,CAAC,EAClC,IAAI,EACJ,YAAY,EACZ,MAAM,EACN,KAAK,EACL,MAAM,EACN,QAAQ,EACR,SAAS,GACZ,EAAE,gBAAgB,sCAiDlB"}
|
|
@@ -38,6 +38,24 @@ function toNum(v) {
|
|
|
38
38
|
const n = typeof v === 'number' ? v : parseFloat(String(v ?? ''));
|
|
39
39
|
return Number.isFinite(n) ? n : 0;
|
|
40
40
|
}
|
|
41
|
+
// applyPrefillLock marks the item-field columns named in a line-items field's
|
|
42
|
+
// PrefillSpec.lock as read-only, so the prefilled cells (e.g. the product of a
|
|
43
|
+
// receive line) render as a resolved, non-editable name. Returns the field
|
|
44
|
+
// untouched when there is no prefill spec or no lock list (the create flow,
|
|
45
|
+
// which carries no prefill, stays fully editable). The readonly flag is set on
|
|
46
|
+
// BOTH itemFields aliases the renderers tolerate.
|
|
47
|
+
function applyPrefillLock(field) {
|
|
48
|
+
const spec = lineItemsDefault(field);
|
|
49
|
+
if (!isPrefillSpec(spec) || !spec.lock || spec.lock.length === 0)
|
|
50
|
+
return field;
|
|
51
|
+
const lock = new Set(spec.lock);
|
|
52
|
+
const f = field;
|
|
53
|
+
const items = f.itemFields ?? f.item_fields;
|
|
54
|
+
if (!Array.isArray(items))
|
|
55
|
+
return field;
|
|
56
|
+
const patched = items.map((c) => (c && c.key && lock.has(c.key) ? { ...c, readonly: true } : c));
|
|
57
|
+
return { ...field, itemFields: patched, item_fields: patched };
|
|
58
|
+
}
|
|
41
59
|
// buildPrefillRows projects record[spec.$prefillFromRecord] into modal rows.
|
|
42
60
|
function buildPrefillRows(spec, record) {
|
|
43
61
|
const src = record?.[spec.$prefillFromRecord];
|
|
@@ -207,7 +225,7 @@ formValues) {
|
|
|
207
225
|
// Repeatable line-items group → row grid (value is an array of row objects).
|
|
208
226
|
// The header form values flow in so a cell can depend on a header field.
|
|
209
227
|
if (isLineItemsField(field)) {
|
|
210
|
-
return _jsx(DynamicLineItems, { field: field, value: value, onChange: onChange, formValues: formValues });
|
|
228
|
+
return _jsx(DynamicLineItems, { field: applyPrefillLock(field), value: value, onChange: onChange, formValues: formValues });
|
|
211
229
|
}
|
|
212
230
|
// Resolve the widget the same way DynamicForm does (explicit widget wins,
|
|
213
231
|
// else inferred from type) so action modals and the standalone form stay in
|
|
@@ -91,7 +91,9 @@ function CellRenderer({ field, value, onChange, disabled, formValues, rowValues
|
|
|
91
91
|
// Async searchable picker per row cell — e.g. the account_id column of a
|
|
92
92
|
// journal entry's debit/credit lines. Same widget as the flat form.
|
|
93
93
|
if (widget === 'dynamic_select') {
|
|
94
|
-
|
|
94
|
+
const ro = !!field.readonly ||
|
|
95
|
+
!!field.read_only;
|
|
96
|
+
return _jsx(DynamicSelectField, { field: field, value: value, onChange: onChange, dependsValue: dependsValue, readonly: ro });
|
|
95
97
|
}
|
|
96
98
|
if (widget === 'select' && (field.ref || getOptionsConfig(field)?.source)) {
|
|
97
99
|
return (_jsx(RefCell, { field: field, value: value, onChange: onChange, disabled: disabled, formValues: formValues, rowValues: rowValues }));
|
|
@@ -47,7 +47,15 @@ export interface DynamicSelectFieldProps {
|
|
|
47
47
|
dependsValue?: string;
|
|
48
48
|
/** Overrides the disabled-state hint shown while `dependsValue` is empty. */
|
|
49
49
|
dependsHint?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Renders the picker as a locked, non-interactive display of the current
|
|
52
|
+
* value's resolved label (no popover, no inline-create). Used when the value
|
|
53
|
+
* is fixed by context — e.g. the product of a receive-goods line is dictated
|
|
54
|
+
* by the source document, not chosen. Options are fetched eagerly (not just
|
|
55
|
+
* on open) so the label resolves to the NAME instead of showing the raw id.
|
|
56
|
+
*/
|
|
57
|
+
readonly?: boolean;
|
|
50
58
|
}
|
|
51
|
-
export declare function DynamicSelectField({ field, value, onChange, seedOption, dependsValue, dependsHint, }: DynamicSelectFieldProps): import("react").JSX.Element;
|
|
59
|
+
export declare function DynamicSelectField({ field, value, onChange, seedOption, dependsValue, dependsHint, readonly, }: DynamicSelectFieldProps): import("react").JSX.Element;
|
|
52
60
|
export default DynamicSelectField;
|
|
53
61
|
//# sourceMappingURL=dynamic-select-field.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-select-field.tsx"],"names":[],"mappings":"AAuCA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,gDAAgD,CAAA;AAEjF;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,IAAS,EAAE,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,+BA4BzF;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACvB,MAAM,EACN,IAAS,GACZ,EAAE;IACC,MAAM,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC,GAAG,IAAI,CAAA;IAChE,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,sCAwBA;AAqBD,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,GAAG,CAAA;IACV,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;IAC1B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,cAAc,GAAG,IAAI,CAAA;IAClC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-select-field.tsx"],"names":[],"mappings":"AAuCA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,gDAAgD,CAAA;AAEjF;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,IAAS,EAAE,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,+BA4BzF;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACvB,MAAM,EACN,IAAS,GACZ,EAAE;IACC,MAAM,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC,GAAG,IAAI,CAAA;IAChE,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB,sCAwBA;AAqBD,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,GAAG,CAAA;IACV,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;IAC1B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,cAAc,GAAG,IAAI,CAAA;IAClC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,kBAAkB,CAAC,EAC/B,KAAK,EACL,KAAK,EACL,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,WAAW,EACX,QAAgB,GACnB,EAAE,uBAAuB,+BAsOzB;AAED,eAAe,kBAAkB,CAAA"}
|
|
@@ -85,7 +85,7 @@ function useDebounced(value, ms) {
|
|
|
85
85
|
}, [value, ms]);
|
|
86
86
|
return debounced;
|
|
87
87
|
}
|
|
88
|
-
export function DynamicSelectField({ field, value, onChange, seedOption, dependsValue, dependsHint, }) {
|
|
88
|
+
export function DynamicSelectField({ field, value, onChange, seedOption, dependsValue, dependsHint, readonly = false, }) {
|
|
89
89
|
const [open, setOpen] = useState(false);
|
|
90
90
|
const [search, setSearch] = useState('');
|
|
91
91
|
const debounced = useDebounced(search, 250);
|
|
@@ -120,8 +120,9 @@ export function DynamicSelectField({ field, value, onChange, seedOption, depends
|
|
|
120
120
|
filterValue: dependsOn ? scope : undefined,
|
|
121
121
|
// Don't fetch until the popover opens (and keep fetching as the query
|
|
122
122
|
// changes while open). A picker blocked by an unset dependency never
|
|
123
|
-
// fetches.
|
|
124
|
-
|
|
123
|
+
// fetches. A readonly cell fetches eagerly so its value's label resolves
|
|
124
|
+
// to the name without the user ever opening it.
|
|
125
|
+
enabled: (open || readonly) && !blockedByDependency,
|
|
125
126
|
});
|
|
126
127
|
// When the depended-on value changes, the previously-picked option no longer
|
|
127
128
|
// belongs to the new scope, so clear the selection (skip the initial mount).
|
|
@@ -175,6 +176,13 @@ export function DynamicSelectField({ field, value, onChange, seedOption, depends
|
|
|
175
176
|
},
|
|
176
177
|
}));
|
|
177
178
|
};
|
|
179
|
+
// Locked display: the value is fixed by context, so render the resolved
|
|
180
|
+
// label (name) as a disabled control — no popover, no inline-create. While
|
|
181
|
+
// the eager fetch is in flight the label falls back to the seed/raw value,
|
|
182
|
+
// then snaps to the name once options arrive.
|
|
183
|
+
if (readonly) {
|
|
184
|
+
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: [hasVisual && value ? _jsx(OptionLead, { option: selectedOption, size: 20 }) : null, _jsx("span", { className: 'min-w-0 flex-1 truncate ' + (selectedLabel ? '' : 'text-muted-foreground'), children: selectedLabel || (loading ? 'Cargando…' : field.placeholder || '—') })] }) }));
|
|
185
|
+
}
|
|
178
186
|
// w-full + min-w-0: as a grid cell child, the row must be allowed to shrink
|
|
179
187
|
// to the cell. Without min-w-0 the combobox+button row sizes to its content
|
|
180
188
|
// (the long empty-state placeholder) and overflows the column, pushing the
|
package/package.json
CHANGED
|
@@ -72,6 +72,13 @@ interface PrefillSpec {
|
|
|
72
72
|
$prefillFromRecord: string
|
|
73
73
|
map?: Record<string, string>
|
|
74
74
|
remaining?: { target: string; of: string; minus?: string }
|
|
75
|
+
/**
|
|
76
|
+
* Item-field keys to lock (render read-only) in the prefilled rows — e.g. the
|
|
77
|
+
* product of a receive-goods line is dictated by the source document, so it
|
|
78
|
+
* is shown as a resolved name but cannot be changed. Editable in the create
|
|
79
|
+
* flow (which carries no prefill spec); only the prefilled action locks them.
|
|
80
|
+
*/
|
|
81
|
+
lock?: string[]
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
function isPrefillSpec(v: unknown): v is PrefillSpec {
|
|
@@ -96,6 +103,23 @@ function toNum(v: unknown): number {
|
|
|
96
103
|
return Number.isFinite(n) ? n : 0
|
|
97
104
|
}
|
|
98
105
|
|
|
106
|
+
// applyPrefillLock marks the item-field columns named in a line-items field's
|
|
107
|
+
// PrefillSpec.lock as read-only, so the prefilled cells (e.g. the product of a
|
|
108
|
+
// receive line) render as a resolved, non-editable name. Returns the field
|
|
109
|
+
// untouched when there is no prefill spec or no lock list (the create flow,
|
|
110
|
+
// which carries no prefill, stays fully editable). The readonly flag is set on
|
|
111
|
+
// BOTH itemFields aliases the renderers tolerate.
|
|
112
|
+
function applyPrefillLock(field: ActionFieldDef): ActionFieldDef {
|
|
113
|
+
const spec = lineItemsDefault(field)
|
|
114
|
+
if (!isPrefillSpec(spec) || !spec.lock || spec.lock.length === 0) return field
|
|
115
|
+
const lock = new Set(spec.lock)
|
|
116
|
+
const f = field as ActionFieldDef & { itemFields?: any[]; item_fields?: any[] }
|
|
117
|
+
const items: any[] | undefined = f.itemFields ?? f.item_fields
|
|
118
|
+
if (!Array.isArray(items)) return field
|
|
119
|
+
const patched: any[] = items.map((c) => (c && c.key && lock.has(c.key) ? { ...c, readonly: true } : c))
|
|
120
|
+
return { ...(field as any), itemFields: patched, item_fields: patched } as ActionFieldDef
|
|
121
|
+
}
|
|
122
|
+
|
|
99
123
|
// buildPrefillRows projects record[spec.$prefillFromRecord] into modal rows.
|
|
100
124
|
function buildPrefillRows(spec: PrefillSpec, record: any): Array<Record<string, any>> {
|
|
101
125
|
const src = record?.[spec.$prefillFromRecord]
|
|
@@ -397,7 +421,7 @@ function renderField(
|
|
|
397
421
|
// Repeatable line-items group → row grid (value is an array of row objects).
|
|
398
422
|
// The header form values flow in so a cell can depend on a header field.
|
|
399
423
|
if (isLineItemsField(field)) {
|
|
400
|
-
return <DynamicLineItems field={field} value={value} onChange={onChange} formValues={formValues} />
|
|
424
|
+
return <DynamicLineItems field={applyPrefillLock(field)} value={value} onChange={onChange} formValues={formValues} />
|
|
401
425
|
}
|
|
402
426
|
// Resolve the widget the same way DynamicForm does (explicit widget wins,
|
|
403
427
|
// else inferred from type) so action modals and the standalone form stay in
|
|
@@ -267,7 +267,9 @@ function CellRenderer({ field, value, onChange, disabled, formValues, rowValues
|
|
|
267
267
|
// Async searchable picker per row cell — e.g. the account_id column of a
|
|
268
268
|
// journal entry's debit/credit lines. Same widget as the flat form.
|
|
269
269
|
if (widget === 'dynamic_select') {
|
|
270
|
-
|
|
270
|
+
const ro = !!(field as { readonly?: boolean; read_only?: boolean }).readonly ||
|
|
271
|
+
!!(field as { readonly?: boolean; read_only?: boolean }).read_only
|
|
272
|
+
return <DynamicSelectField field={field} value={value} onChange={onChange} dependsValue={dependsValue} readonly={ro} />
|
|
271
273
|
}
|
|
272
274
|
if (widget === 'select' && (field.ref || getOptionsConfig(field)?.source)) {
|
|
273
275
|
return (
|
|
@@ -162,6 +162,14 @@ export interface DynamicSelectFieldProps {
|
|
|
162
162
|
dependsValue?: string
|
|
163
163
|
/** Overrides the disabled-state hint shown while `dependsValue` is empty. */
|
|
164
164
|
dependsHint?: string
|
|
165
|
+
/**
|
|
166
|
+
* Renders the picker as a locked, non-interactive display of the current
|
|
167
|
+
* value's resolved label (no popover, no inline-create). Used when the value
|
|
168
|
+
* is fixed by context — e.g. the product of a receive-goods line is dictated
|
|
169
|
+
* by the source document, not chosen. Options are fetched eagerly (not just
|
|
170
|
+
* on open) so the label resolves to the NAME instead of showing the raw id.
|
|
171
|
+
*/
|
|
172
|
+
readonly?: boolean
|
|
165
173
|
}
|
|
166
174
|
|
|
167
175
|
export function DynamicSelectField({
|
|
@@ -171,6 +179,7 @@ export function DynamicSelectField({
|
|
|
171
179
|
seedOption,
|
|
172
180
|
dependsValue,
|
|
173
181
|
dependsHint,
|
|
182
|
+
readonly = false,
|
|
174
183
|
}: DynamicSelectFieldProps) {
|
|
175
184
|
const [open, setOpen] = useState(false)
|
|
176
185
|
const [search, setSearch] = useState('')
|
|
@@ -210,8 +219,9 @@ export function DynamicSelectField({
|
|
|
210
219
|
filterValue: dependsOn ? scope : undefined,
|
|
211
220
|
// Don't fetch until the popover opens (and keep fetching as the query
|
|
212
221
|
// changes while open). A picker blocked by an unset dependency never
|
|
213
|
-
// fetches.
|
|
214
|
-
|
|
222
|
+
// fetches. A readonly cell fetches eagerly so its value's label resolves
|
|
223
|
+
// to the name without the user ever opening it.
|
|
224
|
+
enabled: (open || readonly) && !blockedByDependency,
|
|
215
225
|
})
|
|
216
226
|
|
|
217
227
|
// When the depended-on value changes, the previously-picked option no longer
|
|
@@ -272,6 +282,31 @@ export function DynamicSelectField({
|
|
|
272
282
|
)
|
|
273
283
|
}
|
|
274
284
|
|
|
285
|
+
// Locked display: the value is fixed by context, so render the resolved
|
|
286
|
+
// label (name) as a disabled control — no popover, no inline-create. While
|
|
287
|
+
// the eager fetch is in flight the label falls back to the seed/raw value,
|
|
288
|
+
// then snaps to the name once options arrive.
|
|
289
|
+
if (readonly) {
|
|
290
|
+
return (
|
|
291
|
+
<Button
|
|
292
|
+
type="button"
|
|
293
|
+
variant="outline"
|
|
294
|
+
role="combobox"
|
|
295
|
+
id={field.key}
|
|
296
|
+
disabled
|
|
297
|
+
aria-readonly="true"
|
|
298
|
+
className="w-full min-w-0 cursor-default justify-start font-normal opacity-100"
|
|
299
|
+
>
|
|
300
|
+
<span className="flex min-w-0 flex-1 items-center gap-2 text-left">
|
|
301
|
+
{hasVisual && value ? <OptionLead option={selectedOption} size={20} /> : null}
|
|
302
|
+
<span className={'min-w-0 flex-1 truncate ' + (selectedLabel ? '' : 'text-muted-foreground')}>
|
|
303
|
+
{selectedLabel || (loading ? 'Cargando…' : field.placeholder || '—')}
|
|
304
|
+
</span>
|
|
305
|
+
</span>
|
|
306
|
+
</Button>
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
|
|
275
310
|
// w-full + min-w-0: as a grid cell child, the row must be allowed to shrink
|
|
276
311
|
// to the cell. Without min-w-0 the combobox+button row sizes to its content
|
|
277
312
|
// (the long empty-state placeholder) and overflows the column, pushing the
|