@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 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;AAQ9B,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"}
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"}
@@ -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(Input, { id: field.key, type: field.type === 'email' ? 'email' : field.type === 'url' ? 'url' : 'text', value: value || '', onChange: (e) => onChange(e.target.value), placeholder: field.placeholder });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "29.1.0",
3
+ "version": "29.2.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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 <Input id={field.key} type={field.type === 'email' ? 'email' : field.type === 'url' ? 'url' : 'text'} value={value || ''} onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)} placeholder={field.placeholder} />
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