pixelon 0.5.0 → 0.5.2

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/dist/index.js CHANGED
@@ -10945,6 +10945,7 @@ const Drawer = ({
10945
10945
  "data-testid": dataTestId,
10946
10946
  scrollIndicatorOffset = 1
10947
10947
  }) => {
10948
+ const idTitulo = React.useId();
10948
10949
  const [isMobile, setIsMobile] = React.useState(false);
10949
10950
  const contentRef = React.useRef(null);
10950
10951
  const [showScrollIndicator, setShowScrollIndicator] = React.useState(false);
@@ -11068,14 +11069,14 @@ const Drawer = ({
11068
11069
  style: getDrawerStyles(),
11069
11070
  role: "dialog",
11070
11071
  "aria-modal": "true",
11071
- "aria-labelledby": title ? "drawer-title" : void 0,
11072
+ "aria-labelledby": title ? idTitulo : void 0,
11072
11073
  "data-testid": dataTestId,
11073
11074
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${COMPONENT_PREFIX}drawer__viewport`, children: [
11074
11075
  (title || showCloseButton || headerExtra) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${COMPONENT_PREFIX}drawer__header`, children: [
11075
11076
  title && /* @__PURE__ */ jsxRuntime.jsx(
11076
11077
  "h2",
11077
11078
  {
11078
- id: "drawer-title",
11079
+ id: idTitulo,
11079
11080
  className: `${COMPONENT_PREFIX}drawer__title`,
11080
11081
  children: title
11081
11082
  }
@@ -13188,7 +13189,9 @@ function mapFieldsToFormFields(fields, mode) {
13188
13189
  readOnly: field.readOnly,
13189
13190
  placeholder: field.placeholder,
13190
13191
  uiProps: field.uiProps,
13191
- transformOnSubmit: field.parseValue
13192
+ transformOnSubmit: field.parseValue,
13193
+ gridSpan: field.gridSpan
13194
+ // SPEC-131 T7
13192
13195
  }));
13193
13196
  }
13194
13197
 
@@ -13368,6 +13371,47 @@ function calculateTotalPages(totalRecords, limit) {
13368
13371
  return Math.ceil(totalRecords / limit);
13369
13372
  }
13370
13373
 
13374
+ function etiquetaDeOpcion(valor, uiProps) {
13375
+ const opciones = uiProps?.options;
13376
+ if (!Array.isArray(opciones)) return null;
13377
+ const encontrada = opciones.find((o) => o && String(o.value) === String(valor));
13378
+ return encontrada?.label != null ? String(encontrada.label) : null;
13379
+ }
13380
+ function esNumerico(moleculeType) {
13381
+ return moleculeType === "INPUT_CURRENCY" || moleculeType === "INPUT_NUMBER";
13382
+ }
13383
+ function aTexto$1(valor, uiProps, moleculeType) {
13384
+ if (valor == null || valor === "") return "";
13385
+ if (typeof valor === "boolean") return valor ? "S\xED" : "No";
13386
+ if (esNumerico(moleculeType)) {
13387
+ const n = Number(String(valor).replace(/[^\d.-]/g, ""));
13388
+ if (Number.isFinite(n)) return n.toLocaleString();
13389
+ }
13390
+ if (typeof File !== "undefined" && valor instanceof File) return valor.name;
13391
+ if (Array.isArray(valor)) {
13392
+ const partes = valor.map((v) => aTexto$1(v, uiProps, moleculeType)).filter(Boolean);
13393
+ return partes.join(", ");
13394
+ }
13395
+ if (typeof valor === "object") {
13396
+ const o = valor;
13397
+ for (const clave of ["label", "address", "name", "text", "value"]) {
13398
+ if (typeof o[clave] === "string" && o[clave]) return o[clave];
13399
+ }
13400
+ return "";
13401
+ }
13402
+ return etiquetaDeOpcion(valor, uiProps) ?? String(valor);
13403
+ }
13404
+ function resumenParaConfirmar(campos, datos) {
13405
+ if (!datos) return [];
13406
+ const lineas = [];
13407
+ for (const campo of campos) {
13408
+ const valor = aTexto$1(datos[campo.name], campo.uiProps, campo.moleculeType);
13409
+ if (!valor) continue;
13410
+ lineas.push({ label: campo.label ?? campo.name, valor });
13411
+ }
13412
+ return lineas;
13413
+ }
13414
+
13371
13415
  const useAnchoPropio = () => {
13372
13416
  const ref = React.useRef(null);
13373
13417
  const [ancho, setAncho] = React.useState(null);
@@ -13812,6 +13856,10 @@ function Crud({
13812
13856
  return "Formulario";
13813
13857
  }
13814
13858
  };
13859
+ const resumenConfirmacion = React.useMemo(
13860
+ () => confirmAction === "create" || confirmAction === "edit" ? resumenParaConfirmar(formFields, pendingFormData) : [],
13861
+ [confirmAction, formFields, pendingFormData]
13862
+ );
13815
13863
  const getConfirmMessage = () => {
13816
13864
  switch (confirmAction) {
13817
13865
  case "create":
@@ -13951,6 +13999,10 @@ function Crud({
13951
13999
  className: `${COMPONENT_PREFIX}crud__confirm-drawer`,
13952
14000
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${COMPONENT_PREFIX}crud__confirm-content`, children: [
13953
14001
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: `${COMPONENT_PREFIX}crud__confirm-message`, children: getConfirmMessage() }),
14002
+ resumenConfirmacion.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("dl", { className: `${COMPONENT_PREFIX}crud__confirm-summary`, children: resumenConfirmacion.map((linea) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${COMPONENT_PREFIX}crud__confirm-summary-row`, children: [
14003
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { children: linea.label }),
14004
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: linea.valor })
14005
+ ] }, linea.label)) }),
13954
14006
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${COMPONENT_PREFIX}crud__confirm-actions`, children: [
13955
14007
  /* @__PURE__ */ jsxRuntime.jsx(
13956
14008
  Button,
@@ -14125,7 +14177,7 @@ function getFormatter(key) {
14125
14177
  }
14126
14178
 
14127
14179
  const OPTIONS_LOAD_ERROR = "No se pudieron cargar las opciones. Int\xE9ntalo de nuevo.";
14128
- function unwrap$4(raw) {
14180
+ function unwrap$5(raw) {
14129
14181
  if (raw && typeof raw === "object" && "response" in raw) {
14130
14182
  return raw.response;
14131
14183
  }
@@ -14146,7 +14198,7 @@ function resolveBody(body, ctx) {
14146
14198
  }
14147
14199
  async function loadOptions(eventBus, ep, ctx) {
14148
14200
  const raw = await fetchViaEventBus(eventBus, ep.service, ep.url, ep.method, resolveBody(ep.body, ctx));
14149
- let data = unwrap$4(raw);
14201
+ let data = unwrap$5(raw);
14150
14202
  if (ep.response_path && data && typeof data === "object" && !Array.isArray(data)) {
14151
14203
  data = data[ep.response_path];
14152
14204
  }
@@ -14184,7 +14236,7 @@ const mensajeDeError = (e, respaldo) => {
14184
14236
  };
14185
14237
 
14186
14238
  const REFRESH_INTERVAL_MS = 2e4;
14187
- function unwrap$3(raw) {
14239
+ function unwrap$4(raw) {
14188
14240
  if (raw && typeof raw === "object" && "response" in raw) {
14189
14241
  return raw.response;
14190
14242
  }
@@ -14233,7 +14285,7 @@ function useReactiveCrud(config, eventBus) {
14233
14285
  filters
14234
14286
  });
14235
14287
  assertOk(raw);
14236
- const data = unwrap$3(raw);
14288
+ const data = unwrap$4(raw);
14237
14289
  let rows = [];
14238
14290
  let total = 0;
14239
14291
  if (Array.isArray(data)) {
@@ -14437,6 +14489,72 @@ const FileField = ({
14437
14489
  ] });
14438
14490
  };
14439
14491
 
14492
+ function unwrap$3(raw) {
14493
+ if (raw && typeof raw === "object" && "response" in raw) {
14494
+ return raw.response;
14495
+ }
14496
+ return raw;
14497
+ }
14498
+ function aTexto(valor, locale, moleculeType) {
14499
+ if (valor == null || valor === "") return "";
14500
+ if (moleculeType === "INPUT_CURRENCY" || moleculeType === "INPUT_NUMBER") {
14501
+ const n = Number(valor);
14502
+ if (!Number.isNaN(n)) return n.toLocaleString(locale || "es-CO");
14503
+ }
14504
+ if (typeof valor === "object") {
14505
+ const o = valor;
14506
+ for (const clave of ["label", "address", "name", "text", "value"]) {
14507
+ if (typeof o[clave] === "string" && o[clave]) return o[clave];
14508
+ }
14509
+ return "";
14510
+ }
14511
+ return String(valor);
14512
+ }
14513
+ function ContextoDeLaAccion({ contexto, row, eventBus }) {
14514
+ const { locale } = useChatConfig();
14515
+ const [urlAdjunto, setUrlAdjunto] = React.useState("");
14516
+ const [estadoAdjunto, setEstadoAdjunto] = React.useState("idle");
14517
+ const adjunto = contexto.adjunto;
14518
+ const idAdjunto = adjunto ? String(row[adjunto.idField] ?? "") : "";
14519
+ React.useEffect(() => {
14520
+ if (!adjunto || !idAdjunto || !eventBus) return;
14521
+ let vivo = true;
14522
+ setEstadoAdjunto("cargando");
14523
+ void (async () => {
14524
+ try {
14525
+ const crudo = unwrap$3(
14526
+ await fetchViaEventBus(eventBus, adjunto.media.service, adjunto.media.urlPath.replace("{id}", idAdjunto), "GET")
14527
+ );
14528
+ const url = typeof crudo === "string" ? crudo : String(crudo?.url ?? "");
14529
+ if (!vivo) return;
14530
+ setUrlAdjunto(url);
14531
+ setEstadoAdjunto(url ? "idle" : "error");
14532
+ } catch {
14533
+ if (vivo) setEstadoAdjunto("error");
14534
+ }
14535
+ })();
14536
+ return () => {
14537
+ vivo = false;
14538
+ };
14539
+ }, [adjunto, idAdjunto, eventBus]);
14540
+ const lineas = (contexto.campos ?? []).map((c) => ({ label: c.label, valor: aTexto(row[c.field], locale, c.moleculeType) })).filter((l) => l.valor);
14541
+ if (lineas.length === 0 && !adjunto) return null;
14542
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-reactive-crud__contexto", children: [
14543
+ lineas.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("dl", { className: "px-chat-reactive-crud__contexto-datos", children: lineas.map((l) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-reactive-crud__contexto-linea", children: [
14544
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { children: l.label }),
14545
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: l.valor })
14546
+ ] }, l.label)) }),
14547
+ adjunto && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-reactive-crud__contexto-adjunto", children: [
14548
+ adjunto.label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-reactive-crud__contexto-adjunto-label", children: adjunto.label }),
14549
+ estadoAdjunto === "cargando" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-reactive-crud__contexto-nota", children: "Cargando\u2026" }),
14550
+ estadoAdjunto === "error" && /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", className: "px-chat-reactive-crud__contexto-nota", children: "No se pudo cargar el archivo. M\xEDralo en \xABVer\xBB antes de confirmar." }),
14551
+ urlAdjunto && estadoAdjunto === "idle" && // Se abre aparte porque el comprobante de una transferencia puede ser un PDF o una
14552
+ // captura larga, y aquí solo cabe un vistazo.
14553
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: urlAdjunto, target: "_blank", rel: "noopener noreferrer", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: urlAdjunto, alt: adjunto.label ?? "Archivo adjunto", className: "px-chat-reactive-crud__contexto-imagen" }) })
14554
+ ] })
14555
+ ] });
14556
+ }
14557
+
14440
14558
  const RESPALDO = { miles: ".", decimal: "," };
14441
14559
  function separadoresDelLocale(locale) {
14442
14560
  if (!locale) return RESPALDO;
@@ -14592,6 +14710,7 @@ function ActionFormDrawer({ action, row, idField, eventBus, onExecute, onClose }
14592
14710
  };
14593
14711
  const uploading = Object.values(fileEntries).some((list) => list.some((e) => e.status === "uploading"));
14594
14712
  return /* @__PURE__ */ jsxRuntime.jsx(Drawer$1, { isOpen: true, onClose, title: action.label, placement: "right", children: loadingOptions ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-reactive-crud__action-loading", children: "Cargando\u2026" }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-reactive-crud__action-form", children: [
14713
+ action.form?.contexto && /* @__PURE__ */ jsxRuntime.jsx(ContextoDeLaAccion, { contexto: action.form.contexto, row, eventBus }),
14595
14714
  fileFields.map((f) => /* @__PURE__ */ jsxRuntime.jsx(
14596
14715
  FileField,
14597
14716
  {
@@ -15896,7 +16015,8 @@ function usePlaceSuggestions(query, enabled) {
15896
16015
  return {
15897
16016
  lat: typeof loc.lat === "function" ? loc.lat() : loc.lat,
15898
16017
  lng: typeof loc.lng === "function" ? loc.lng() : loc.lng,
15899
- address: String(place.formattedAddress ?? "")
16018
+ address: limpiarDireccion(String(place.formattedAddress ?? ""))
16019
+ // SPEC-131 T6
15900
16020
  };
15901
16021
  } catch {
15902
16022
  return null;
@@ -15906,13 +16026,27 @@ function usePlaceSuggestions(query, enabled) {
15906
16026
  );
15907
16027
  return { suggestions, resolve, clear: () => setSuggestions([]) };
15908
16028
  }
16029
+ function limpiarDireccion(address) {
16030
+ const normal = (s) => s.trim().toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
16031
+ const vistos = /* @__PURE__ */ new Set();
16032
+ const tramos = [];
16033
+ for (const tramo of address.split(",")) {
16034
+ const limpio = tramo.trim();
16035
+ if (!limpio) continue;
16036
+ const clave = normal(limpio);
16037
+ if (vistos.has(clave)) continue;
16038
+ vistos.add(clave);
16039
+ tramos.push(limpio);
16040
+ }
16041
+ return tramos.join(", ");
16042
+ }
15909
16043
  function reverseGeocode(lat, lng) {
15910
16044
  return new Promise((resolve) => {
15911
16045
  const maps = window.google?.maps;
15912
16046
  if (!maps?.Geocoder) return resolve(null);
15913
16047
  new maps.Geocoder().geocode({ location: { lat, lng } }, (results, status) => {
15914
16048
  if (status !== "OK" || !results?.length) return resolve(null);
15915
- resolve(String(results[0].formatted_address ?? "") || null);
16049
+ resolve(limpiarDireccion(String(results[0].formatted_address ?? "")) || null);
15916
16050
  });
15917
16051
  });
15918
16052
  }
@@ -16069,6 +16203,10 @@ function toRenderField(f, loaded, eventBus, mapsApiKey, loadError) {
16069
16203
  showInModes: f.showInModes,
16070
16204
  required: f.required,
16071
16205
  placeholder: f.placeholder,
16206
+ // SPEC-131 T7 — la fila entera: al elegir la dirección se despliega el mapa de verificación
16207
+ // y, a media fila, su campo hermano se quedaba con ~400px de blanco al lado. Medido entre
16208
+ // «¿En qué situación está?» y «¿A dónde lo llevamos?» del formulario de solicitud.
16209
+ gridSpan: 2,
16072
16210
  uiProps: {
16073
16211
  // SPEC-074 — `error` viene del render de CUSTOM (DynamicForm ya lo pasaba) y aquí se
16074
16212
  // desechaba: el campo obligatorio más importante era el único mudo.
@@ -16693,6 +16831,8 @@ const ServicesMap = ({ config, eventBus, apiKey, filter, fill }) => {
16693
16831
  const { service, url, method, payloadMap } = config.data.list;
16694
16832
  void fetchViaEventBus(eventBus, service, url, method ?? "POST", {
16695
16833
  ...payloadMap,
16834
+ // SPEC-131 T6 — el del config primero, el de la instancia después: quien acota manda.
16835
+ ...config.defaultFilter ?? {},
16696
16836
  ...filter ?? {}
16697
16837
  }).then((res) => {
16698
16838
  if (!alive) return;
@@ -18846,6 +18986,10 @@ const useInputFocus = ({
18846
18986
  };
18847
18987
  };
18848
18988
 
18989
+ function reservaNecesaria(clientHeight, scrollHeight, targetTop) {
18990
+ const debajoDelObjetivo = scrollHeight - targetTop;
18991
+ return Math.max(0, clientHeight - debajoDelObjetivo);
18992
+ }
18849
18993
  const useAutoScroll = ({
18850
18994
  messagesEndRef,
18851
18995
  messagesContainerRef
@@ -18864,11 +19008,12 @@ const useAutoScroll = ({
18864
19008
  const container = messagesContainerRef.current;
18865
19009
  const spacer = messagesEndRef.current;
18866
19010
  if (!container || !spacer || !target) return;
18867
- spacer.style.minHeight = `${container.clientHeight}px`;
19011
+ spacer.style.minHeight = "0px";
18868
19012
  const containerRect = container.getBoundingClientRect();
18869
19013
  const msgRect = target.getBoundingClientRect();
18870
19014
  const padTop = parseFloat(getComputedStyle(container).paddingTop) || 0;
18871
19015
  const targetTop = msgRect.top - containerRect.top + container.scrollTop - padTop;
19016
+ spacer.style.minHeight = `${reservaNecesaria(container.clientHeight, container.scrollHeight, targetTop)}px`;
18872
19017
  container.scrollTo({ top: targetTop, behavior: "smooth" });
18873
19018
  }, [messagesContainerRef, messagesEndRef]);
18874
19019
  const last = React.useCallback((selector) => {
@@ -18910,15 +19055,33 @@ const useAutoScroll = ({
18910
19055
  });
18911
19056
  }, 150);
18912
19057
  }, [doScroll]);
18913
- const scrollToBottom = React.useCallback(() => {
19058
+ const aterrizarAlFinal = React.useCallback(() => {
19059
+ const container = messagesContainerRef.current;
19060
+ if (!container) return;
19061
+ resetSpacer();
19062
+ const hasta = Date.now() + 1e3;
19063
+ let ultimoAlto = -1;
19064
+ const pegar = () => {
19065
+ const c = messagesContainerRef.current;
19066
+ if (!c) return;
19067
+ if (c.scrollHeight !== ultimoAlto) {
19068
+ ultimoAlto = c.scrollHeight;
19069
+ c.scrollTop = c.scrollHeight;
19070
+ }
19071
+ if (Date.now() < hasta) requestAnimationFrame(pegar);
19072
+ };
19073
+ requestAnimationFrame(pegar);
19074
+ }, [messagesContainerRef, resetSpacer]);
19075
+ const scrollToBottom = React.useCallback((suave = true) => {
18914
19076
  cancelPending();
18915
19077
  resetSpacer();
18916
19078
  requestAnimationFrame(() => {
19079
+ const comportamiento = suave ? "smooth" : "auto";
18917
19080
  const container = messagesContainerRef.current;
18918
19081
  if (container) {
18919
- container.scrollTo({ top: container.scrollHeight, behavior: "smooth" });
19082
+ container.scrollTo({ top: container.scrollHeight, behavior: comportamiento });
18920
19083
  } else {
18921
- messagesEndRef.current?.scrollIntoView({ behavior: "smooth", block: "end" });
19084
+ messagesEndRef.current?.scrollIntoView({ behavior: comportamiento, block: "end" });
18922
19085
  }
18923
19086
  });
18924
19087
  }, [cancelPending, resetSpacer, messagesContainerRef, messagesEndRef]);
@@ -18926,7 +19089,9 @@ const useAutoScroll = ({
18926
19089
  scrollToLastUserMessage,
18927
19090
  pinLastUserMessage,
18928
19091
  scrollToProactive,
18929
- scrollToBottom
19092
+ scrollToBottom,
19093
+ aterrizarAlFinal
19094
+ // SPEC-131 T3
18930
19095
  };
18931
19096
  };
18932
19097
 
@@ -19479,7 +19644,7 @@ const Chat = ({
19479
19644
  usePreventOverscroll();
19480
19645
  const { showThinking, scrollButtonState, scrollToTopPending } = useChatStore();
19481
19646
  const { attachments, handleAttachmentsChange, clearAttachments } = useAttachments();
19482
- const { scrollToBottom, scrollToLastUserMessage, pinLastUserMessage, scrollToProactive } = useAutoScroll({
19647
+ const { scrollToBottom, scrollToLastUserMessage, pinLastUserMessage, scrollToProactive, aterrizarAlFinal } = useAutoScroll({
19483
19648
  messagesEndRef,
19484
19649
  messagesContainerRef
19485
19650
  });
@@ -19507,11 +19672,23 @@ const Chat = ({
19507
19672
  }
19508
19673
  }, [scrollToTopPending, scrollToLastUserMessage]);
19509
19674
  React.useEffect(() => {
19675
+ if (messages[messages.length - 1]?.proactive) return;
19510
19676
  const spacer = messagesEndRef.current;
19511
19677
  if (spacer && spacer.style.minHeight && spacer.style.minHeight !== "0px") {
19512
19678
  pinLastUserMessage();
19513
19679
  }
19514
19680
  }, [messages]);
19681
+ const yaAterrizo = React.useRef(false);
19682
+ React.useEffect(() => {
19683
+ if (messages.length === 0) {
19684
+ yaAterrizo.current = false;
19685
+ return;
19686
+ }
19687
+ if (yaAterrizo.current) return;
19688
+ yaAterrizo.current = true;
19689
+ if (messages[messages.length - 1]?.proactive) return;
19690
+ aterrizarAlFinal();
19691
+ }, [messages, aterrizarAlFinal]);
19515
19692
  const lastProactiveIdRef = React.useRef(void 0);
19516
19693
  React.useEffect(() => {
19517
19694
  const last = messages[messages.length - 1];
@@ -19629,7 +19806,7 @@ const Chat = ({
19629
19806
  {
19630
19807
  className: `${COMPONENT_PREFIX$1}chat__scroll-to-bottom`,
19631
19808
  variant: "circle",
19632
- onClick: scrollToBottom,
19809
+ onClick: () => scrollToBottom(),
19633
19810
  "aria-label": "Ir al final",
19634
19811
  icon: /* @__PURE__ */ jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
19635
19812
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 8v8", strokeWidth: "2", strokeLinecap: "round" }),