@asteby/metacore-runtime-react 29.1.0 → 29.2.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/dynamic-form.d.ts.map +1 -1
- package/dist/dynamic-form.js +19 -1
- package/package.json +1 -1
- package/src/dynamic-form.tsx +64 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 29.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- fd10c7d: Los inputs de texto/número de formularios declarativos aceptan el opt-in `scan`: cuando el campo lo declara y el navegador soporta cámara, aparece un botón que escanea un código de barras y **llena el input** (ej. el SKU al crear producto). El escáner queda abierto (continuo) para corregir/reintentar; se cierra con la X. Complementa el `scan` ya soportado en `dynamic_select`.
|
|
8
|
+
|
|
3
9
|
## 29.1.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-form.d.ts","sourceRoot":"","sources":["../src/dynamic-form.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,eAAe,CAAA;AAcrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EACH,cAAc,EACd,aAAa,EAOhB,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-form.d.ts","sourceRoot":"","sources":["../src/dynamic-form.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,eAAe,CAAA;AAcrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,EACH,cAAc,EACd,aAAa,EAOhB,MAAM,uBAAuB,CAAA;AAU9B,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,CAAA;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAErD,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,UAAU,CAAA;CAC1B;AAED,wBAAgB,WAAW,CAAC,EACxB,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,WAAuB,EACvB,WAAwB,EACxB,QAAgB,EAChB,UAAU,GACb,EAAE,gBAAgB,+BA8LlB"}
|
package/dist/dynamic-form.js
CHANGED
|
@@ -7,6 +7,8 @@ import { groupFieldsBySection } from './form-layout';
|
|
|
7
7
|
import { FieldSection, WizardProgress } from './form-layout-ui';
|
|
8
8
|
import { Input, Textarea, Label, Switch, Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@asteby/metacore-ui/primitives';
|
|
9
9
|
import { buildZodSchema, resolveWidget, isLineItemsField, evaluateBalance, applyOptionWhen, getDependsOn, getVisibleWhen, evaluateVisibleWhen, } from './dynamic-form-schema';
|
|
10
|
+
import { ScanLine } from 'lucide-react';
|
|
11
|
+
import { BarcodeScanner, isCameraScanSupported } from './barcode-scanner';
|
|
10
12
|
import { useOptionsResolver } from './use-options-resolver';
|
|
11
13
|
import { DynamicLineItems } from './dynamic-line-items';
|
|
12
14
|
import { DynamicSelectField } from './dynamic-select-field';
|
|
@@ -238,9 +240,25 @@ function FieldRenderer({ field, value, onChange, initialValues, effectiveOptions
|
|
|
238
240
|
case 'date':
|
|
239
241
|
return _jsx(DynamicDateField, { field: field, value: value, onChange: onChange });
|
|
240
242
|
default:
|
|
241
|
-
return _jsx(
|
|
243
|
+
return (_jsx(ScannableInput, { field: field, value: value, onChange: onChange, type: field.type === 'email' ? 'email' : field.type === 'url' ? 'url' : 'text' }));
|
|
242
244
|
}
|
|
243
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Input de texto/número con opción de ESCANEO por cámara para "llenar rápido".
|
|
248
|
+
* Cuando el campo declara `scan` (alias `scannable`) y el navegador soporta
|
|
249
|
+
* `BarcodeDetector`, agrega un botón de cámara: el código escaneado se escribe
|
|
250
|
+
* en el input y el escáner SE QUEDA ABIERTO (continuo) por si querés corregir o
|
|
251
|
+
* escanear otro — lo cerrás con la X. Sin el flag o sin soporte, es un Input
|
|
252
|
+
* normal (retrocompat total).
|
|
253
|
+
*/
|
|
254
|
+
function ScannableInput({ field, value, onChange, type, }) {
|
|
255
|
+
const [scanOpen, setScanOpen] = useState(false);
|
|
256
|
+
const scanEnabled = !!(field.scan ?? field.scannable) && isCameraScanSupported();
|
|
257
|
+
const input = (_jsx(Input, { id: field.key, type: type, value: value ?? '', onChange: (e) => onChange(type === 'number' ? e.target.valueAsNumber || '' : e.target.value), placeholder: field.placeholder }));
|
|
258
|
+
if (!scanEnabled)
|
|
259
|
+
return input;
|
|
260
|
+
return (_jsxs("div", { className: "flex w-full min-w-0 items-center gap-1.5", children: [_jsx("div", { className: "min-w-0 flex-1", children: input }), _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: (code) => onChange(code), continuous: true, position: "fixed", title: `Escanear ${field.label ?? ''}`.trim() })] }));
|
|
261
|
+
}
|
|
244
262
|
function RefSelect({ field, value, onChange }) {
|
|
245
263
|
const { options, loading } = useOptionsResolver({
|
|
246
264
|
modelKey: '', // unused — `ref` drives the URL
|
package/package.json
CHANGED
package/src/dynamic-form.tsx
CHANGED
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
getVisibleWhen,
|
|
28
28
|
evaluateVisibleWhen,
|
|
29
29
|
} from './dynamic-form-schema'
|
|
30
|
+
import { ScanLine } from 'lucide-react'
|
|
31
|
+
import { BarcodeScanner, isCameraScanSupported } from './barcode-scanner'
|
|
30
32
|
import { useOptionsResolver, type ResolvedOption } from './use-options-resolver'
|
|
31
33
|
import { DynamicLineItems } from './dynamic-line-items'
|
|
32
34
|
import { DynamicSelectField } from './dynamic-select-field'
|
|
@@ -414,10 +416,71 @@ function FieldRenderer({
|
|
|
414
416
|
case 'date':
|
|
415
417
|
return <DynamicDateField field={field} value={value} onChange={onChange} />
|
|
416
418
|
default:
|
|
417
|
-
return
|
|
419
|
+
return (
|
|
420
|
+
<ScannableInput
|
|
421
|
+
field={field}
|
|
422
|
+
value={value}
|
|
423
|
+
onChange={onChange}
|
|
424
|
+
type={field.type === 'email' ? 'email' : field.type === 'url' ? 'url' : 'text'}
|
|
425
|
+
/>
|
|
426
|
+
)
|
|
418
427
|
}
|
|
419
428
|
}
|
|
420
429
|
|
|
430
|
+
/**
|
|
431
|
+
* Input de texto/número con opción de ESCANEO por cámara para "llenar rápido".
|
|
432
|
+
* Cuando el campo declara `scan` (alias `scannable`) y el navegador soporta
|
|
433
|
+
* `BarcodeDetector`, agrega un botón de cámara: el código escaneado se escribe
|
|
434
|
+
* en el input y el escáner SE QUEDA ABIERTO (continuo) por si querés corregir o
|
|
435
|
+
* escanear otro — lo cerrás con la X. Sin el flag o sin soporte, es un Input
|
|
436
|
+
* normal (retrocompat total).
|
|
437
|
+
*/
|
|
438
|
+
function ScannableInput({
|
|
439
|
+
field,
|
|
440
|
+
value,
|
|
441
|
+
onChange,
|
|
442
|
+
type,
|
|
443
|
+
}: FieldRendererProps & { type: string }) {
|
|
444
|
+
const [scanOpen, setScanOpen] = useState(false)
|
|
445
|
+
const scanEnabled = !!(field.scan ?? field.scannable) && isCameraScanSupported()
|
|
446
|
+
const input = (
|
|
447
|
+
<Input
|
|
448
|
+
id={field.key}
|
|
449
|
+
type={type}
|
|
450
|
+
value={value ?? ''}
|
|
451
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
452
|
+
onChange(type === 'number' ? e.target.valueAsNumber || '' : e.target.value)
|
|
453
|
+
}
|
|
454
|
+
placeholder={field.placeholder}
|
|
455
|
+
/>
|
|
456
|
+
)
|
|
457
|
+
if (!scanEnabled) return input
|
|
458
|
+
return (
|
|
459
|
+
<div className="flex w-full min-w-0 items-center gap-1.5">
|
|
460
|
+
<div className="min-w-0 flex-1">{input}</div>
|
|
461
|
+
<Button
|
|
462
|
+
type="button"
|
|
463
|
+
variant="outline"
|
|
464
|
+
size="icon"
|
|
465
|
+
className="size-9 shrink-0"
|
|
466
|
+
onClick={() => setScanOpen(true)}
|
|
467
|
+
title="Escanear con la cámara"
|
|
468
|
+
aria-label="Escanear código de barras con la cámara"
|
|
469
|
+
>
|
|
470
|
+
<ScanLine className="size-4" />
|
|
471
|
+
</Button>
|
|
472
|
+
<BarcodeScanner
|
|
473
|
+
open={scanOpen}
|
|
474
|
+
onClose={() => setScanOpen(false)}
|
|
475
|
+
onDetected={(code) => onChange(code)}
|
|
476
|
+
continuous
|
|
477
|
+
position="fixed"
|
|
478
|
+
title={`Escanear ${field.label ?? ''}`.trim()}
|
|
479
|
+
/>
|
|
480
|
+
</div>
|
|
481
|
+
)
|
|
482
|
+
}
|
|
483
|
+
|
|
421
484
|
function RefSelect({ field, value, onChange }: FieldRendererProps) {
|
|
422
485
|
const { options, loading } = useOptionsResolver({
|
|
423
486
|
modelKey: '', // unused — `ref` drives the URL
|