@asteby/metacore-runtime-react 29.2.8 → 29.2.10

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,17 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 29.2.10
4
+
5
+ ### Patch Changes
6
+
7
+ - fd81213: BarcodeScanner: prop `feedback` para mostrar un chip de confirmación (producto agregado / sin coincidencia) **sobre** la cámara — los toasts de Sonner no se ven encima del overlay opaco.
8
+
9
+ ## 29.2.9
10
+
11
+ ### Patch Changes
12
+
13
+ - 5621412: Modal de detalle: siempre muestra skeleton mientras carga `/metadata/modal`, aunque la fila de la tabla ya venga como seed — evita el diálogo vacío con solo la descripción.
14
+
3
15
  ## 29.2.8
4
16
 
5
17
  ### Patch Changes
@@ -12,6 +12,11 @@ export declare function isCameraScanSupported(): boolean;
12
12
  * dispositivo no permite audio. Exportado para reutilizarlo con lectores físicos.
13
13
  */
14
14
  export declare function useScanBeep(): () => void;
15
+ /** Chip superpuesto sobre la cámara (éxito / error del padre tras resolver el código). */
16
+ export type BarcodeScannerFeedback = {
17
+ message: string;
18
+ tone?: 'success' | 'error' | 'neutral';
19
+ };
15
20
  export interface BarcodeScannerProps {
16
21
  open: boolean;
17
22
  onClose: () => void;
@@ -35,12 +40,18 @@ export interface BarcodeScannerProps {
35
40
  * modal, sin taparse con el chrome del host. `fixed` cubre todo el viewport.
36
41
  */
37
42
  position?: 'absolute' | 'fixed';
43
+ /**
44
+ * Feedback del padre (ej. "Harina × 2" / "Sin coincidencia") pintado ENCIMA
45
+ * del video. Los toasts de Sonner del host quedan detrás de este overlay
46
+ * opaco en la práctica — este chip es la confirmación visible al escanear.
47
+ */
48
+ feedback?: BarcodeScannerFeedback | null;
38
49
  }
39
50
  /**
40
51
  * Overlay de cámara a pantalla del contenedor con retícula de puntería. Montalo
41
52
  * condicionalmente (`open`) dentro de un ancestro `relative` cuando uses el
42
53
  * posicionamiento `absolute` por defecto.
43
54
  */
44
- export declare function BarcodeScanner({ open, onClose, onDetected, continuous, beepOnScan, title, hint, position, }: BarcodeScannerProps): React.JSX.Element | null;
55
+ export declare function BarcodeScanner({ open, onClose, onDetected, continuous, beepOnScan, title, hint, position, feedback, }: BarcodeScannerProps): React.JSX.Element | null;
45
56
  export default BarcodeScanner;
46
57
  //# sourceMappingURL=barcode-scanner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"barcode-scanner.d.ts","sourceRoot":"","sources":["../src/barcode-scanner.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAQ9B,qFAAqF;AACrF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAOtF;AAoDD,qDAAqD;AACrD,eAAO,MAAM,sBAAsB,UASlC,CAAA;AAQD,uFAAuF;AACvF,wBAAgB,qBAAqB,IAAI,OAAO,CAM/C;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,eAyB1B;AAED,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,8EAA8E;IAC9E,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,uDAAuD;IACvD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,UAAU,GAAG,OAAO,CAAA;CAClC;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC3B,IAAI,EACJ,OAAO,EACP,UAAU,EACV,UAAiB,EACjB,UAAiB,EACjB,KAAyB,EACzB,IAAmC,EACnC,QAAqB,GACxB,EAAE,mBAAmB,4BAgMrB;AAED,eAAe,cAAc,CAAA"}
1
+ {"version":3,"file":"barcode-scanner.d.ts","sourceRoot":"","sources":["../src/barcode-scanner.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAQ9B,qFAAqF;AACrF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAOtF;AAoDD,qDAAqD;AACrD,eAAO,MAAM,sBAAsB,UASlC,CAAA;AAQD,uFAAuF;AACvF,wBAAgB,qBAAqB,IAAI,OAAO,CAM/C;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,eAyB1B;AAED,0FAA0F;AAC1F,MAAM,MAAM,sBAAsB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAA;CACzC,CAAA;AAED,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,8EAA8E;IAC9E,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,uDAAuD;IACvD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,UAAU,GAAG,OAAO,CAAA;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAA;CAC3C;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC3B,IAAI,EACJ,OAAO,EACP,UAAU,EACV,UAAiB,EACjB,UAAiB,EACjB,KAAyB,EACzB,IAAmC,EACnC,QAAqB,EACrB,QAAe,GAClB,EAAE,mBAAmB,4BAsNrB;AAED,eAAe,cAAc,CAAA"}
@@ -93,7 +93,7 @@ export function useScanBeep() {
93
93
  * condicionalmente (`open`) dentro de un ancestro `relative` cuando uses el
94
94
  * posicionamiento `absolute` por defecto.
95
95
  */
96
- export function BarcodeScanner({ open, onClose, onDetected, continuous = true, beepOnScan = true, title = 'Escanear código', hint = 'Apuntá al código de barras', position = 'absolute', }) {
96
+ export function BarcodeScanner({ open, onClose, onDetected, continuous = true, beepOnScan = true, title = 'Escanear código', hint = 'Apuntá al código de barras', position = 'absolute', feedback = null, }) {
97
97
  const videoRef = React.useRef(null);
98
98
  const streamRef = React.useRef(null);
99
99
  const rafRef = React.useRef(null);
@@ -207,7 +207,11 @@ export function BarcodeScanner({ open, onClose, onDetected, continuous = true, b
207
207
  const posClass = position === 'fixed' ? 'fixed' : 'absolute';
208
208
  return (_jsxs("div", { className: `${posClass} inset-0 z-[100] flex flex-col bg-black`, children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-3 text-white", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm font-medium", children: [_jsx(ScanLine, { className: "size-5" }), title] }), _jsxs("div", { className: "flex items-center gap-1", children: [torchAvailable && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: torchOn
209
209
  ? 'bg-amber-400/20 text-amber-300 hover:bg-amber-400/30 hover:text-amber-200'
210
- : 'text-white hover:bg-white/10 hover:text-white', onClick: () => void toggleTorch(), "aria-label": torchOn ? 'Apagar linterna' : 'Encender linterna', "aria-pressed": torchOn, title: torchOn ? 'Apagar linterna' : 'Encender linterna', children: torchOn ? (_jsx(Flashlight, { className: "size-5" })) : (_jsx(FlashlightOff, { className: "size-5" })) })), _jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "text-white hover:bg-white/10 hover:text-white", onClick: onClose, "aria-label": "Cerrar esc\u00E1ner", children: _jsx(X, { className: "size-5" }) })] })] }), _jsx("div", { className: "relative flex flex-1 items-center justify-center overflow-hidden", children: error ? (_jsxs("div", { className: "flex max-w-sm flex-col items-center gap-3 px-6 text-center text-white", children: [_jsx(Camera, { className: "size-10 opacity-70" }), _jsx("p", { className: "text-sm leading-relaxed", children: error }), _jsx(Button, { variant: "secondary", onClick: onClose, children: "Entendido" })] })) : (_jsxs(_Fragment, { children: [_jsx("video", { ref: videoRef, className: "h-full w-full object-cover", muted: true, playsInline: true }), _jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center", children: _jsx("div", { className: "relative h-40 w-72 max-w-[80%] rounded-xl border-2 border-white/80 shadow-[0_0_0_9999px_rgba(0,0,0,0.35)]", children: _jsx("span", { className: "bg-primary absolute inset-x-0 top-1/2 h-0.5 -translate-y-1/2 animate-pulse" }) }) }), _jsx("p", { className: "absolute bottom-8 left-0 right-0 px-4 text-center text-sm text-white/90", children: !ready
210
+ : 'text-white hover:bg-white/10 hover:text-white', onClick: () => void toggleTorch(), "aria-label": torchOn ? 'Apagar linterna' : 'Encender linterna', "aria-pressed": torchOn, title: torchOn ? 'Apagar linterna' : 'Encender linterna', children: torchOn ? (_jsx(Flashlight, { className: "size-5" })) : (_jsx(FlashlightOff, { className: "size-5" })) })), _jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "text-white hover:bg-white/10 hover:text-white", onClick: onClose, "aria-label": "Cerrar esc\u00E1ner", children: _jsx(X, { className: "size-5" }) })] })] }), _jsx("div", { className: "relative flex flex-1 items-center justify-center overflow-hidden", children: error ? (_jsxs("div", { className: "flex max-w-sm flex-col items-center gap-3 px-6 text-center text-white", children: [_jsx(Camera, { className: "size-10 opacity-70" }), _jsx("p", { className: "text-sm leading-relaxed", children: error }), _jsx(Button, { variant: "secondary", onClick: onClose, children: "Entendido" })] })) : (_jsxs(_Fragment, { children: [_jsx("video", { ref: videoRef, className: "h-full w-full object-cover", muted: true, playsInline: true }), _jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center", children: _jsx("div", { className: "relative h-40 w-72 max-w-[80%] rounded-xl border-2 border-white/80 shadow-[0_0_0_9999px_rgba(0,0,0,0.35)]", children: _jsx("span", { className: "bg-primary absolute inset-x-0 top-1/2 h-0.5 -translate-y-1/2 animate-pulse" }) }) }), feedback?.message ? (_jsx("div", { className: "pointer-events-none absolute inset-x-0 top-4 z-10 flex justify-center px-4", "data-testid": "barcode-scanner-feedback", role: "status", "aria-live": "polite", children: _jsx("div", { className: feedback.tone === 'error'
211
+ ? 'max-w-[90%] rounded-full bg-red-500 px-4 py-2.5 text-center text-sm font-semibold text-white shadow-lg'
212
+ : feedback.tone === 'neutral'
213
+ ? 'max-w-[90%] rounded-full bg-white/90 px-4 py-2.5 text-center text-sm font-semibold text-black shadow-lg'
214
+ : 'max-w-[90%] rounded-full bg-emerald-500 px-4 py-2.5 text-center text-sm font-semibold text-white shadow-lg', children: feedback.message }) })) : null, _jsx("p", { className: "absolute bottom-8 left-0 right-0 px-4 text-center text-sm text-white/90", children: !ready
211
215
  ? 'Abriendo cámara…'
212
216
  : torchAvailable
213
217
  ? `${hint} · tocá la linterna si está oscuro`
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-record.d.ts","sourceRoot":"","sources":["../../src/dialogs/dynamic-record.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAyC1C,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAEjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAkB3C,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAEnE,OAAO,EAAqC,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAK1F,YAAY,EAAE,WAAW,EAAE,CAAA;AAE3B,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,QAAQ;IACrB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAA;IACpH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAA;IACvB,YAAY,CAAC,EAAE,GAAG,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACjC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,SAAS,EAAE,CAAA;IACxB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,SAAS,EAAE,CAAA;IACzB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,WAAW,CAAA;IAC1B,0CAA0C;IAC1C,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB;AA0CD,MAAM,WAAW,wBAAwB;IACrC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAA;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;2DAEuD;IACvD,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC;QAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;IAClF;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC;QAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;IACpG;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAA;IAC1C;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB;AASD,wBAAgB,WAAW,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAK1D;AAaD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAG,GAAG,CAgBtE;AAID,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,GAAG,SAAS,EAAE,GAAG,SAAS,CAExE;AAQD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CASrE;AASD,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,cAAc,GAAG,IAAI,CAa5F;AA6FD,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAUjE;AAaD,wBAAgB,mBAAmB,CAC/B,MAAM,EAAE,QAAQ,EAAE,GAAG,SAAS,EAC9B,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,EAChC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACjC,QAAQ,EAAE,CAOZ;AAUD,wBAAgB,sBAAsB,CAClC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,QAAQ,EAAE,GAAG,SAAS,EAC9B,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GACjC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CASrB;AAED,wBAAgB,mBAAmB,CAAC,EAChC,IAAI,EACJ,YAAY,EACZ,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,MAAM,EACN,cAAc,EACd,aAAa,EACb,WAA8B,EAC9B,QAAQ,EACR,QAAQ,EACR,QAAQ,GACX,EAAE,wBAAwB,+BAuhB1B;AA+DD,wBAAgB,iBAAiB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,GAAG,CAAA;CAAE,+BAWlF;AAyDD,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,KAAK,EAAE,QAAQ,EACf,MAAM,EACN,WAAW,EAAE,eAAe,EAC5B,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,YAAY,GACzB,EAAE;IACC,KAAK,EAAE,QAAQ,CAAA;IACf,KAAK,EAAE,GAAG,CAAA;IACV,MAAM,EAAE,GAAG,CAAA;IACX,mFAAmF;IACnF,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB,+BAgQA;AAsID,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;IAC1D,KAAK,EAAE,QAAQ,CAAA;IACf,KAAK,EAAE,GAAG,CAAA;IACV,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC5B,iFAAiF;IACjF,MAAM,CAAC,EAAE,GAAG,CAAA;CACf,+BAiMA"}
1
+ {"version":3,"file":"dynamic-record.d.ts","sourceRoot":"","sources":["../../src/dialogs/dynamic-record.tsx"],"names":[],"mappings":"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"}
@@ -333,9 +333,12 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
333
333
  return v !== undefined;
334
334
  });
335
335
  const load = async () => {
336
- // Only show the skeleton when we have nothing to render yet.
337
- if (!seed)
338
- setLoading(true);
336
+ // Always skeleton until modal metadata is ready to paint fields.
337
+ // A list-row `initialRecord` seed is NOT enough on its own: without
338
+ // meta we don't know which fields to render, which left the dialog
339
+ // body blank (only the "Información detallada…" description) until
340
+ // `/metadata/modal` resolved — especially visible on slow/cold loads.
341
+ setLoading(true);
339
342
  try {
340
343
  let meta = schema ? schema : null;
341
344
  if (!meta) {
@@ -593,7 +596,13 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
593
596
  setDeleting(false);
594
597
  }
595
598
  };
596
- const title = modalMeta ? config.getTitle(modalMeta, t) : '';
599
+ const title = modalMeta
600
+ ? config.getTitle(modalMeta, t)
601
+ : mode === 'create'
602
+ ? 'Nuevo registro'
603
+ : mode === 'edit'
604
+ ? 'Editar registro'
605
+ : 'Ver registro';
597
606
  const visibleFields = filterVisibleFields(modalMeta?.fields, mode, formValues);
598
607
  // Declarative form layout: group the (already visibility-filtered) fields by
599
608
  // their section. Empty sections drop out for free. Steps mode only drives a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "29.2.8",
3
+ "version": "29.2.10",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,88 @@
1
+ // @vitest-environment happy-dom
2
+ //
3
+ // Regression: opening the view dialog with a list-row `initialRecord` seed used
4
+ // to skip `loading`, so while `/metadata/modal` was in flight the body rendered
5
+ // null — an empty modal with only the "Información detallada del registro."
6
+ // description. Skeleton must show until modal metadata is ready.
7
+ import { afterEach, describe, expect, it, vi } from 'vitest'
8
+ import { cleanup, render, screen, waitFor } from '@testing-library/react'
9
+ import { I18nextProvider } from 'react-i18next'
10
+ import i18next from 'i18next'
11
+
12
+ import { DynamicRecordDialog } from '../dialogs/dynamic-record'
13
+ import { ApiProvider } from '../api-context'
14
+
15
+ afterEach(cleanup)
16
+
17
+ function makeI18n() {
18
+ const inst = i18next.createInstance()
19
+ void inst.init({
20
+ lng: 'es',
21
+ fallbackLng: 'es',
22
+ react: { useSuspense: false },
23
+ resources: { es: { translation: {} } },
24
+ })
25
+ return inst
26
+ }
27
+
28
+ describe('DynamicRecordDialog view — skeleton while metadata loads', () => {
29
+ it('shows the field skeleton even when a seed row is provided', async () => {
30
+ let resolveMeta!: (v: unknown) => void
31
+ const metaPromise = new Promise(resolve => {
32
+ resolveMeta = resolve
33
+ })
34
+
35
+ const api = {
36
+ get: vi.fn(async (url: string) => {
37
+ if (String(url).includes('/metadata/modal/')) {
38
+ await metaPromise
39
+ return {
40
+ data: {
41
+ data: {
42
+ title: 'Orden de compra',
43
+ fields: [
44
+ { key: 'number', label: 'Número', type: 'text' },
45
+ { key: 'state', label: 'Estado', type: 'text' },
46
+ ],
47
+ },
48
+ },
49
+ }
50
+ }
51
+ return { data: { data: {} } }
52
+ }),
53
+ post: async () => ({ data: { success: true } }),
54
+ put: async () => ({ data: { success: true } }),
55
+ delete: async () => ({ data: { success: true } }),
56
+ } as never
57
+
58
+ render(
59
+ <I18nextProvider i18n={makeI18n()}>
60
+ <ApiProvider client={api}>
61
+ <DynamicRecordDialog
62
+ open
63
+ onOpenChange={() => {}}
64
+ mode="view"
65
+ model="purchase_orders"
66
+ recordId="po-1"
67
+ initialRecord={{ id: 'po-1', number: 'OC-1', state: 'draft' }}
68
+ />
69
+ </ApiProvider>
70
+ </I18nextProvider>,
71
+ )
72
+
73
+ // Description is always visible; body must NOT be an empty void.
74
+ expect(screen.getByText('Información detallada del registro.')).toBeTruthy()
75
+ // Skeleton primitives from @asteby/metacore-ui
76
+ expect(document.querySelectorAll('[data-slot="skeleton"]').length).toBeGreaterThan(0)
77
+ // Seeded field labels must not appear until meta resolves.
78
+ expect(screen.queryByText('Número')).toBeNull()
79
+
80
+ resolveMeta(undefined)
81
+
82
+ await waitFor(() => {
83
+ expect(screen.getByText('Número')).toBeTruthy()
84
+ expect(screen.getByText('OC-1')).toBeTruthy()
85
+ })
86
+ expect(document.querySelectorAll('[data-slot="skeleton"]').length).toBe(0)
87
+ })
88
+ })
@@ -126,6 +126,12 @@ export function useScanBeep() {
126
126
  }, [])
127
127
  }
128
128
 
129
+ /** Chip superpuesto sobre la cámara (éxito / error del padre tras resolver el código). */
130
+ export type BarcodeScannerFeedback = {
131
+ message: string
132
+ tone?: 'success' | 'error' | 'neutral'
133
+ }
134
+
129
135
  export interface BarcodeScannerProps {
130
136
  open: boolean
131
137
  onClose: () => void
@@ -149,6 +155,12 @@ export interface BarcodeScannerProps {
149
155
  * modal, sin taparse con el chrome del host. `fixed` cubre todo el viewport.
150
156
  */
151
157
  position?: 'absolute' | 'fixed'
158
+ /**
159
+ * Feedback del padre (ej. "Harina × 2" / "Sin coincidencia") pintado ENCIMA
160
+ * del video. Los toasts de Sonner del host quedan detrás de este overlay
161
+ * opaco en la práctica — este chip es la confirmación visible al escanear.
162
+ */
163
+ feedback?: BarcodeScannerFeedback | null
152
164
  }
153
165
 
154
166
  /**
@@ -165,6 +177,7 @@ export function BarcodeScanner({
165
177
  title = 'Escanear código',
166
178
  hint = 'Apuntá al código de barras',
167
179
  position = 'absolute',
180
+ feedback = null,
168
181
  }: BarcodeScannerProps) {
169
182
  const videoRef = React.useRef<HTMLVideoElement | null>(null)
170
183
  const streamRef = React.useRef<MediaStream | null>(null)
@@ -345,6 +358,28 @@ export function BarcodeScanner({
345
358
  <span className="bg-primary absolute inset-x-0 top-1/2 h-0.5 -translate-y-1/2 animate-pulse" />
346
359
  </div>
347
360
  </div>
361
+ {/* Confirmación del padre ENCIMA del video (Sonner queda
362
+ detrás de este overlay opaco al escanear). */}
363
+ {feedback?.message ? (
364
+ <div
365
+ className="pointer-events-none absolute inset-x-0 top-4 z-10 flex justify-center px-4"
366
+ data-testid="barcode-scanner-feedback"
367
+ role="status"
368
+ aria-live="polite"
369
+ >
370
+ <div
371
+ className={
372
+ feedback.tone === 'error'
373
+ ? 'max-w-[90%] rounded-full bg-red-500 px-4 py-2.5 text-center text-sm font-semibold text-white shadow-lg'
374
+ : feedback.tone === 'neutral'
375
+ ? 'max-w-[90%] rounded-full bg-white/90 px-4 py-2.5 text-center text-sm font-semibold text-black shadow-lg'
376
+ : 'max-w-[90%] rounded-full bg-emerald-500 px-4 py-2.5 text-center text-sm font-semibold text-white shadow-lg'
377
+ }
378
+ >
379
+ {feedback.message}
380
+ </div>
381
+ </div>
382
+ ) : null}
348
383
  <p className="absolute bottom-8 left-0 right-0 px-4 text-center text-sm text-white/90">
349
384
  {!ready
350
385
  ? 'Abriendo cámara…'
@@ -612,8 +612,12 @@ export function DynamicRecordDialog({
612
612
  })
613
613
 
614
614
  const load = async () => {
615
- // Only show the skeleton when we have nothing to render yet.
616
- if (!seed) setLoading(true)
615
+ // Always skeleton until modal metadata is ready to paint fields.
616
+ // A list-row `initialRecord` seed is NOT enough on its own: without
617
+ // meta we don't know which fields to render, which left the dialog
618
+ // body blank (only the "Información detallada…" description) until
619
+ // `/metadata/modal` resolved — especially visible on slow/cold loads.
620
+ setLoading(true)
617
621
  try {
618
622
  let meta: ModalMetadata | null = schema ? (schema as ModalMetadata) : null
619
623
  if (!meta) {
@@ -877,7 +881,13 @@ export function DynamicRecordDialog({
877
881
  }
878
882
  }
879
883
 
880
- const title = modalMeta ? config.getTitle(modalMeta, t) : ''
884
+ const title = modalMeta
885
+ ? config.getTitle(modalMeta, t)
886
+ : mode === 'create'
887
+ ? 'Nuevo registro'
888
+ : mode === 'edit'
889
+ ? 'Editar registro'
890
+ : 'Ver registro'
881
891
 
882
892
  const visibleFields = filterVisibleFields(modalMeta?.fields, mode, formValues)
883
893