@arquimedes.co/eureka-forms 3.0.56-new-steps → 3.0.58-new-steps

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.
@@ -11,7 +11,7 @@ import { validateRestrictions } from '../../../Shared/ErkLocationField/locationR
11
11
  import styles from './MaterialLocationStep.module.css';
12
12
  function MaterialLocationStep({ step, editable }) {
13
13
  const currentBreakPoint = useAppSelector(selectBreakPoint);
14
- const { postview } = useAppSelector((state) => state.global);
14
+ const { postview, formStyle } = useAppSelector((state) => state.global);
15
15
  const form = useContext(FormContext);
16
16
  const isReadOnly = !editable || postview;
17
17
  const widthStyle = currentBreakPoint <= step.size ? '100%' : calcStepWidth(step.size, form.size);
@@ -42,6 +42,13 @@ function MaterialLocationStep({ step, editable }) {
42
42
  },
43
43
  },
44
44
  });
45
- return (_jsxs("div", { className: styles.container, style: { width: widthStyle }, children: [step.label && _jsxs("div", { className: styles.label, children: [step.label, step.required && _jsx("span", { className: styles.required, children: " *" })] }), _jsx(ErkLocationField, { value: value, onChange: onChange, allowedModes: step.allowedModes, defaultMode: step.defaultMode, addressFields: step.addressFields, helperText: error?.message ?? step.description, required: step.required, error: !!error, readOnly: isReadOnly, inputRef: ref })] }));
45
+ return (_jsxs("div", { className: styles.container, style: {
46
+ width: widthStyle,
47
+ '--eureka-outline': formStyle.outlineColor,
48
+ '--eureka-primary': formStyle.primaryColor,
49
+ '--eureka-text': formStyle.textColor,
50
+ '--eureka-error': formStyle.errorColor,
51
+ '--eureka-bg': formStyle.standAloneBackgroundColor ?? '#fff',
52
+ }, children: [step.label && _jsxs("div", { className: styles.label, children: [step.label, step.required && _jsx("span", { className: styles.required, children: " *" })] }), _jsx(ErkLocationField, { value: value, onChange: onChange, allowedModes: step.allowedModes, defaultMode: step.defaultMode, addressFields: step.addressFields, helperText: error?.message ?? step.description, required: step.required, error: !!error, readOnly: isReadOnly, inputRef: ref })] }));
46
53
  }
47
54
  export default MaterialLocationStep;
@@ -1,7 +1,7 @@
1
1
  .input {
2
2
  width: 100%;
3
3
  box-sizing: border-box;
4
- height: 40px;
4
+ height: 33px;
5
5
  padding: 6px 12px;
6
6
  font-size: 1rem;
7
7
  color: var(--eureka-text, #333);
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ export interface ErkIconButtonProps {
3
+ /** Tooltip y nombre accesible del botón. */
4
+ title: string;
5
+ onClick?: () => void;
6
+ disabled?: boolean;
7
+ /** Muestra un spinner en lugar del icono (y deshabilita el botón). */
8
+ loading?: boolean;
9
+ children: React.ReactNode;
10
+ className?: string;
11
+ }
12
+ /**
13
+ * Botón de icono de la casa: cuadrado redondeado con la misma altura y borde
14
+ * que los inputs del form (theming vía variables --eureka-*), con tooltip.
15
+ */
16
+ declare function ErkIconButton({ title, onClick, disabled, loading, children, className }: ErkIconButtonProps): JSX.Element;
17
+ export default ErkIconButton;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { CircularProgress, Tooltip } from '@mui/material';
3
+ import styles from './ErkIconButton.module.css';
4
+ /**
5
+ * Botón de icono de la casa: cuadrado redondeado con la misma altura y borde
6
+ * que los inputs del form (theming vía variables --eureka-*), con tooltip.
7
+ */
8
+ function ErkIconButton({ title, onClick, disabled = false, loading = false, children, className }) {
9
+ return (_jsx(Tooltip, { title: title, children: _jsx("span", { className: styles.wrap, children: _jsx("button", { type: "button", "aria-label": title, disabled: disabled || loading, className: className ? `${styles.button} ${className}` : styles.button, onClick: onClick, children: loading ? _jsx(CircularProgress, { size: 16 }) : children }) }) }));
10
+ }
11
+ export default ErkIconButton;
@@ -0,0 +1,41 @@
1
+ .wrap {
2
+ display: inline-flex;
3
+ flex-shrink: 0;
4
+ }
5
+
6
+ /* Mismo alto, radio y borde que los inputs del form (ErkTextField small) */
7
+ .button {
8
+ height: 33px;
9
+ width: 33px;
10
+ box-sizing: border-box;
11
+ border: 1px solid var(--eureka-outline, #d9dee6);
12
+ border-radius: 10px;
13
+ background-color: var(--eureka-bg, #fff);
14
+ color: var(--eureka-text, #333);
15
+ display: grid;
16
+ place-items: center;
17
+ padding: 0;
18
+ cursor: pointer;
19
+ transition: all 0.2s ease-in-out;
20
+ }
21
+
22
+ .button svg {
23
+ width: 18px;
24
+ height: 18px;
25
+ }
26
+
27
+ .button:hover:not(:disabled) {
28
+ background-color: rgba(0, 0, 0, 0.04);
29
+ border-color: var(--eureka-primary, #1976d2);
30
+ color: var(--eureka-primary, #1976d2);
31
+ }
32
+
33
+ .button:focus-visible {
34
+ outline: 2px solid var(--eureka-primary, #1976d2);
35
+ outline-offset: 1px;
36
+ }
37
+
38
+ .button:disabled {
39
+ opacity: 0.5;
40
+ cursor: default;
41
+ }
@@ -15,9 +15,10 @@ export interface ErkLocationFieldProps {
15
15
  inputRef?: React.Ref<HTMLInputElement>;
16
16
  }
17
17
  /**
18
- * Campo de ubicación (rehecho con Base UI). Ofrece los modos habilitados
19
- * (buscador de Google Places, GPS, entrada manual) y aplica la config por campo
20
- * (fijo / omitir) al valor resultante.
18
+ * Campo de ubicación: una sola fila con el buscador de direcciones (Base UI +
19
+ * Google Places) y botones de acción GPS y edición manual (en modal). Al
20
+ * seleccionar, el campo muestra la dirección con opciones de editar/limpiar.
21
+ * La config por campo (fijo / omitir) se aplica a todos los modos.
21
22
  */
22
- declare function ErkLocationField({ value, onChange, allowedModes, defaultMode, addressFields, regionCodes, error, helperText, required, readOnly, inputRef, }: ErkLocationFieldProps): JSX.Element;
23
+ declare function ErkLocationField({ value, onChange, allowedModes, addressFields, regionCodes, error, helperText, required, readOnly, inputRef, }: ErkLocationFieldProps): JSX.Element;
23
24
  export default ErkLocationField;
@@ -1,29 +1,48 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useCallback, useState } from 'react';
3
+ import { Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
3
4
  import { AddressFieldMode, LocationInputMode, } from '../../@Types/LocationFormStep';
4
5
  import ErkAddressAutocomplete from '../ErkAddressAutocomplete/ErkAddressAutocomplete';
5
6
  import { reverseGeocode } from '../ErkAddressAutocomplete/googlePlaces';
6
- import { ADDRESS_FIELDS, applyAddressFields, fieldConfig } from './addressFields';
7
+ import { applyAddressFields, fieldConfig } from './addressFields';
7
8
  import ErkTextField from '../ErkTextField/ErkTextField';
8
9
  import ErkButton from '../ErkButton/ErkButton';
10
+ import ErkIconButton from '../ErkIconButton/ErkIconButton';
9
11
  import LocationIcon from '../../Icons/LocationIcon';
12
+ import EditIcon from '../../Icons/EditIcon';
13
+ import CloseIcon from '../../Icons/CloseIcon';
10
14
  import styles from './ErkLocationField.module.css';
11
- const MODE_LABELS = {
12
- [LocationInputMode.GOOGLE_PLACES]: 'Buscar',
13
- [LocationInputMode.GPS]: 'Mi ubicación',
14
- [LocationInputMode.MANUAL]: 'Manual',
15
- };
15
+ /** Claves que el formulario manual exige (si no están omitidas ni fijas). */
16
+ const MANUAL_REQUIRED_KEYS = ['street', 'city', 'country'];
17
+ /** Dirección legible a partir del desglose, en el orden natural de una dirección. */
18
+ function composeAddress(detail) {
19
+ return [
20
+ detail.street,
21
+ detail.streetNumber,
22
+ detail.unit ? `Apto/Int ${detail.unit}` : '',
23
+ detail.neighborhood,
24
+ detail.city,
25
+ detail.state,
26
+ detail.country,
27
+ ]
28
+ .filter(Boolean)
29
+ .join(', ');
30
+ }
16
31
  /**
17
- * Campo de ubicación (rehecho con Base UI). Ofrece los modos habilitados
18
- * (buscador de Google Places, GPS, entrada manual) y aplica la config por campo
19
- * (fijo / omitir) al valor resultante.
32
+ * Campo de ubicación: una sola fila con el buscador de direcciones (Base UI +
33
+ * Google Places) y botones de acción GPS y edición manual (en modal). Al
34
+ * seleccionar, el campo muestra la dirección con opciones de editar/limpiar.
35
+ * La config por campo (fijo / omitir) se aplica a todos los modos.
20
36
  */
21
- function ErkLocationField({ value, onChange, allowedModes, defaultMode, addressFields, regionCodes = ['co'], error = false, helperText, required = false, readOnly = false, inputRef, }) {
37
+ function ErkLocationField({ value, onChange, allowedModes, addressFields, regionCodes = ['co'], error = false, helperText, required = false, readOnly = false, inputRef, }) {
22
38
  const apiKey = import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? '';
23
- const initialMode = defaultMode && allowedModes.includes(defaultMode) ? defaultMode : allowedModes[0];
24
- const [mode, setMode] = useState(initialMode);
39
+ const canGoogle = allowedModes.includes(LocationInputMode.GOOGLE_PLACES);
40
+ const canGps = allowedModes.includes(LocationInputMode.GPS);
41
+ const canManual = allowedModes.includes(LocationInputMode.MANUAL);
25
42
  const [gpsLoading, setGpsLoading] = useState(false);
26
43
  const [gpsError, setGpsError] = useState(null);
44
+ const [manualOpen, setManualOpen] = useState(false);
45
+ const [manualDetail, setManualDetail] = useState({});
27
46
  const handleGoogle = useCallback((resolved) => {
28
47
  onChange({ ...resolved, addressDetail: applyAddressFields(resolved.addressDetail ?? {}, addressFields) });
29
48
  }, [onChange, addressFields]);
@@ -48,26 +67,60 @@ function ErkLocationField({ value, onChange, allowedModes, defaultMode, addressF
48
67
  });
49
68
  })
50
69
  .finally(() => setGpsLoading(false));
51
- }, () => {
52
- setGpsError('No se pudo obtener la ubicación.');
70
+ }, (err) => {
71
+ let message = 'No se pudo obtener la ubicación GPS.';
72
+ if (err.code === err.PERMISSION_DENIED) {
73
+ message =
74
+ 'Permiso denegado para acceder a la ubicación. Asegúrate de habilitar los permisos en tu navegador y en los Ajustes del Sistema (Privacidad y seguridad > Localización).';
75
+ }
76
+ else if (err.code === err.TIMEOUT) {
77
+ message =
78
+ 'Tiempo de espera agotado al obtener la ubicación. Si estás en Mac, asegúrate de activar el Wi-Fi para ayudar a la localización, o introduce la dirección manualmente.';
79
+ }
80
+ setGpsError(message);
53
81
  setGpsLoading(false);
54
82
  }, { enableHighAccuracy: false, timeout: 15000, maximumAge: 300000 });
55
83
  }, [apiKey, onChange, addressFields]);
56
- const updateManualField = useCallback((key, text) => {
57
- const base = value?.source === LocationInputMode.MANUAL ? (value.addressDetail ?? {}) : {};
58
- const nextDetail = applyAddressFields({ ...base, [key]: text || undefined }, addressFields);
59
- const address = ADDRESS_FIELDS.map(({ key: k }) => nextDetail[k])
60
- .filter(Boolean)
61
- .join(', ');
62
- onChange({ source: LocationInputMode.MANUAL, address, addressDetail: nextDetail });
63
- }, [value, onChange, addressFields]);
64
- const manualDetail = value?.source === LocationInputMode.MANUAL ? (value.addressDetail ?? {}) : {};
65
- return (_jsxs("div", { className: styles.container, children: [allowedModes.length > 1 && (_jsx("div", { className: styles.modeSwitch, children: allowedModes.map((m) => (_jsx(ErkButton, { color: m === mode ? 'primary' : 'secondary', disabled: readOnly, onClick: () => setMode(m), children: MODE_LABELS[m] }, m))) })), mode === LocationInputMode.GOOGLE_PLACES && (_jsx(ErkAddressAutocomplete, { value: value, onChange: handleGoogle, regionCodes: regionCodes, error: error, required: required, readOnly: readOnly, inputRef: inputRef })), mode === LocationInputMode.GPS && (_jsxs(ErkButton, { color: "primary", loading: gpsLoading, disabled: readOnly, onClick: handleGps, children: [_jsx(LocationIcon, { fill: "currentColor" }), " Usar mi ubicaci\u00F3n actual"] })), mode === LocationInputMode.MANUAL && (_jsx("div", { className: styles.manualGrid, children: ADDRESS_FIELDS.map(({ key, label }) => {
66
- const config = fieldConfig(addressFields, key);
67
- if (config.mode === AddressFieldMode.OMIT)
68
- return null;
69
- const fixed = config.mode === AddressFieldMode.FIXED;
70
- return (_jsx(ErkTextField, { label: fixed ? `${label} (fijo)` : label, value: fixed ? (config.fixedValue ?? '') : (manualDetail[key] ?? ''), readOnly: fixed || readOnly, onChange: (text) => updateManualField(key, text) }, key));
71
- }) })), (gpsError ?? helperText) && (_jsx("div", { className: error || gpsError ? styles.errorText : styles.helperText, children: gpsError ?? helperText }))] }));
84
+ const openManual = useCallback(() => {
85
+ setManualDetail(value?.addressDetail ?? {});
86
+ setManualOpen(true);
87
+ }, [value]);
88
+ const manualFieldValue = useCallback((key) => {
89
+ const config = fieldConfig(addressFields, key);
90
+ if (config.mode === AddressFieldMode.FIXED)
91
+ return config.fixedValue ?? '';
92
+ return manualDetail[key] ?? '';
93
+ }, [addressFields, manualDetail]);
94
+ const canSaveManual = MANUAL_REQUIRED_KEYS.every((key) => {
95
+ const config = fieldConfig(addressFields, key);
96
+ if (config.mode === AddressFieldMode.OMIT)
97
+ return true;
98
+ if (config.mode === AddressFieldMode.FIXED)
99
+ return !!config.fixedValue;
100
+ return !!manualDetail[key];
101
+ });
102
+ const saveManual = () => {
103
+ if (!canSaveManual)
104
+ return;
105
+ const detail = applyAddressFields(manualDetail, addressFields);
106
+ onChange({ source: LocationInputMode.MANUAL, address: composeAddress(detail), addressDetail: detail });
107
+ setManualOpen(false);
108
+ };
109
+ const clearValue = () => {
110
+ onChange(null);
111
+ setGpsError(null);
112
+ };
113
+ const manualField = (key, label, requiredField = false) => {
114
+ const config = fieldConfig(addressFields, key);
115
+ if (config.mode === AddressFieldMode.OMIT)
116
+ return null;
117
+ const fixed = config.mode === AddressFieldMode.FIXED;
118
+ return (_jsx(ErkTextField, { label: fixed ? `${label} (fijo)` : label, value: manualFieldValue(key), readOnly: fixed, required: requiredField && !fixed, onChange: (text) => setManualDetail((detail) => ({ ...detail, [key]: text || undefined })) }));
119
+ };
120
+ // Vista de solo lectura: tarjeta con el resumen de la ubicación
121
+ if (readOnly) {
122
+ return (_jsx("div", { className: styles.readOnlyContainer, children: _jsxs("div", { className: styles.readOnlyCard, children: [_jsx("div", { className: styles.readOnlyIcon, children: _jsx(LocationIcon, { fill: "var(--eureka-primary)" }) }), _jsxs("div", { className: styles.readOnlyDetails, children: [value?.name && _jsx("span", { className: styles.readOnlyName, children: value.name }), _jsx("span", { className: styles.readOnlyAddress, children: value?.address ?? 'Ubicación no proporcionada' }), value?.latitude !== undefined && value?.longitude !== undefined && (_jsxs("span", { className: styles.readOnlyCoords, children: ["Coordenadas: ", value.latitude.toFixed(6), ", ", value.longitude.toFixed(6)] }))] })] }) }));
123
+ }
124
+ return (_jsxs("div", { className: styles.container, children: [_jsx("div", { className: styles.inputRow, children: value?.address ? (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.grow, children: _jsx(ErkTextField, { label: "Ubicaci\u00F3n seleccionada", value: value.address, readOnly: true, error: error, helperText: helperText, required: required, inputRef: inputRef }) }), canManual && (_jsx(ErkIconButton, { title: "Editar direcci\u00F3n manualmente", className: styles.withLabelOffset, onClick: openManual, children: _jsx(EditIcon, { fill: "currentColor" }) })), _jsx(ErkIconButton, { title: "Limpiar selecci\u00F3n", className: styles.withLabelOffset, onClick: clearValue, children: _jsx(CloseIcon, { fill: "currentColor" }) })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.grow, children: canGoogle ? (_jsx(ErkAddressAutocomplete, { value: value, onChange: handleGoogle, regionCodes: regionCodes, error: error, required: required, inputRef: inputRef })) : canManual ? (_jsx(ErkTextField, { label: "Ingresa una ubicaci\u00F3n...", value: value?.address ?? '', onChange: (text) => onChange(text ? { source: LocationInputMode.MANUAL, address: text } : null), error: error, helperText: helperText, required: required, inputRef: inputRef })) : (_jsx(ErkTextField, { label: "Usa el bot\u00F3n para obtener tu ubicaci\u00F3n", value: "", readOnly: true, error: error, helperText: helperText, required: required, inputRef: inputRef })) }), canGps && (_jsx(ErkIconButton, { title: "Obtener ubicaci\u00F3n por GPS", className: canGoogle ? undefined : styles.withLabelOffset, loading: gpsLoading, onClick: handleGps, children: _jsx(LocationIcon, { fill: "currentColor" }) })), canManual && (_jsx(ErkIconButton, { title: "Ingresar direcci\u00F3n manualmente", className: canGoogle ? undefined : styles.withLabelOffset, onClick: openManual, children: _jsx(EditIcon, { fill: "currentColor" }) }))] })) }), (gpsError ?? (!value?.address && canGoogle && helperText)) && (_jsx("div", { className: error || gpsError ? styles.errorText : styles.helperText, children: gpsError ?? helperText })), _jsxs(Dialog, { open: manualOpen, onClose: () => setManualOpen(false), maxWidth: "sm", fullWidth: true, slotProps: { paper: { className: styles.dialogPaper } }, children: [_jsx(DialogTitle, { className: styles.dialogTitle, children: "Ingresar Direcci\u00F3n" }), _jsx(DialogContent, { className: styles.dialogContent, children: _jsxs("div", { className: styles.manualGrid, children: [_jsx("div", { className: styles.manualRow, children: _jsx("div", { className: styles.col12, children: manualField('street', 'Calle / Carrera / Avenida (Línea 1)', true) }) }), _jsxs("div", { className: styles.manualRow, children: [_jsx("div", { className: styles.col6, children: manualField('streetNumber', 'Número / Portal') }), _jsx("div", { className: styles.col6, children: manualField('unit', 'Apto / Oficina / Interior') })] }), _jsxs("div", { className: styles.manualRow, children: [_jsx("div", { className: styles.col6, children: manualField('neighborhood', 'Barrio / Zona') }), _jsx("div", { className: styles.col6, children: manualField('city', 'Ciudad', true) })] }), _jsxs("div", { className: styles.manualRow, children: [_jsx("div", { className: styles.col4, children: manualField('state', 'Estado / Provincia / Dpto') }), _jsx("div", { className: styles.col4, children: manualField('postalCode', 'Código Postal') }), _jsx("div", { className: styles.col4, children: manualField('country', 'País', true) })] })] }) }), _jsxs(DialogActions, { style: { padding: '16px 0 0 0', gap: '12px' }, children: [_jsx(ErkButton, { text: "Cancelar", variant: "outlined", onClick: () => setManualOpen(false) }), _jsx(ErkButton, { text: "Guardar", variant: "contained", disabled: !canSaveManual, onClick: saveManual })] })] })] }));
72
125
  }
73
126
  export default ErkLocationField;
@@ -1,20 +1,26 @@
1
1
  .container {
2
+ width: 100%;
2
3
  display: flex;
3
4
  flex-direction: column;
5
+ gap: 8px;
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ .inputRow {
10
+ display: flex;
11
+ align-items: flex-start;
4
12
  gap: 10px;
5
13
  width: 100%;
6
14
  }
7
15
 
8
- .modeSwitch {
9
- display: flex;
10
- gap: 8px;
11
- flex-wrap: wrap;
16
+ .grow {
17
+ flex: 1;
18
+ min-width: 0;
12
19
  }
13
20
 
14
- .manualGrid {
15
- display: grid;
16
- grid-template-columns: repeat(2, 1fr);
17
- gap: 10px;
21
+ /* Compensa el margen superior que ErkTextField agrega cuando tiene label */
22
+ .withLabelOffset {
23
+ margin-top: 5px;
18
24
  }
19
25
 
20
26
  .helperText {
@@ -27,3 +33,103 @@
27
33
  font-size: 12px;
28
34
  color: var(--eureka-error, #e53935);
29
35
  }
36
+
37
+ /* Formulario manual (modal) */
38
+ .manualGrid {
39
+ display: flex;
40
+ flex-direction: column;
41
+ gap: 12px;
42
+ width: 100%;
43
+ }
44
+
45
+ .manualRow {
46
+ display: flex;
47
+ gap: 12px;
48
+ width: 100%;
49
+ }
50
+
51
+ .col12 {
52
+ width: 100%;
53
+ }
54
+ .col6 {
55
+ width: 50%;
56
+ }
57
+ .col4 {
58
+ width: 33.333%;
59
+ }
60
+
61
+ @media (max-width: 600px) {
62
+ .manualRow {
63
+ flex-direction: column;
64
+ gap: 12px;
65
+ }
66
+ .col6,
67
+ .col4 {
68
+ width: 100%;
69
+ }
70
+ }
71
+
72
+ /* Vista de solo lectura */
73
+ .readOnlyContainer {
74
+ width: 100%;
75
+ box-sizing: border-box;
76
+ }
77
+
78
+ .readOnlyCard {
79
+ display: flex;
80
+ align-items: flex-start;
81
+ gap: 15px;
82
+ padding: 15px;
83
+ border: 1px solid var(--eureka-outline, #d9dee6);
84
+ border-radius: 10px;
85
+ background-color: var(--eureka-bg, #fff);
86
+ }
87
+
88
+ .readOnlyIcon {
89
+ flex-shrink: 0;
90
+ margin-top: 2px;
91
+ width: 24px;
92
+ height: 24px;
93
+ }
94
+
95
+ .readOnlyDetails {
96
+ display: flex;
97
+ flex-direction: column;
98
+ gap: 4px;
99
+ color: var(--eureka-text, #333);
100
+ }
101
+
102
+ .readOnlyName {
103
+ font-size: 1rem;
104
+ font-weight: 600;
105
+ }
106
+
107
+ .readOnlyAddress {
108
+ font-size: 0.9rem;
109
+ }
110
+
111
+ .readOnlyCoords {
112
+ font-size: 0.8rem;
113
+ opacity: 0.65;
114
+ font-family: monospace;
115
+ }
116
+
117
+ /* Modal de dirección manual (portal: usa fallbacks del theme del form) */
118
+ .dialogPaper {
119
+ border-radius: 20px !important;
120
+ padding: 16px 24px;
121
+ background-color: var(--eureka-bg, #fff) !important;
122
+ }
123
+
124
+ .dialogTitle {
125
+ font-weight: 700 !important;
126
+ padding: 0 0 16px 0 !important;
127
+ color: var(--eureka-text, #333);
128
+ font-family: inherit !important;
129
+ }
130
+
131
+ .dialogContent {
132
+ padding: 16px 0 !important;
133
+ border-top: 1px solid var(--eureka-outline, #d9dee6);
134
+ border-bottom: 1px solid var(--eureka-outline, #d9dee6);
135
+ }
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1,82 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+ import '@testing-library/jest-dom';
6
+ import ErkLocationField from './ErkLocationField';
7
+ import MaterialProviders from '../../Utils/MaterialProviders';
8
+ import InternalFormStyle from '../../constants/InternalFormStyle';
9
+ import { AddressFieldMode, LocationInputMode } from '../../@Types/LocationFormStep';
10
+ const ALL_MODES = [LocationInputMode.GOOGLE_PLACES, LocationInputMode.GPS, LocationInputMode.MANUAL];
11
+ function renderField(props = {}) {
12
+ const onChange = vi.fn();
13
+ render(_jsx(MaterialProviders, { formStyle: InternalFormStyle, children: _jsx(ErkLocationField, { value: null, onChange: onChange, allowedModes: ALL_MODES, ...props }) }));
14
+ return { onChange };
15
+ }
16
+ describe('ErkLocationField', () => {
17
+ it('renders the single-row search with GPS and manual action buttons', () => {
18
+ renderField();
19
+ expect(screen.getByPlaceholderText('Buscar dirección…')).toBeInTheDocument();
20
+ expect(screen.getByRole('button', { name: 'Obtener ubicación por GPS' })).toBeInTheDocument();
21
+ expect(screen.getByRole('button', { name: 'Ingresar dirección manualmente' })).toBeInTheDocument();
22
+ });
23
+ it('saves a manual address from the dialog, composing the readable address', async () => {
24
+ const user = userEvent.setup();
25
+ const { onChange } = renderField();
26
+ await user.click(screen.getByRole('button', { name: 'Ingresar dirección manualmente' }));
27
+ expect(await screen.findByText('Ingresar Dirección')).toBeInTheDocument();
28
+ // Guardar bloqueado hasta llenar calle/ciudad/país
29
+ expect(screen.getByRole('button', { name: 'Guardar' })).toBeDisabled();
30
+ await user.type(screen.getByLabelText(/Calle \/ Carrera/), 'Calle 100');
31
+ await user.type(screen.getByLabelText(/^Ciudad/), 'Bogotá');
32
+ await user.type(screen.getByLabelText(/^País/), 'Colombia');
33
+ await user.click(screen.getByRole('button', { name: 'Guardar' }));
34
+ expect(onChange).toHaveBeenCalledWith({
35
+ source: LocationInputMode.MANUAL,
36
+ address: 'Calle 100, Bogotá, Colombia',
37
+ addressDetail: { street: 'Calle 100', city: 'Bogotá', country: 'Colombia' },
38
+ });
39
+ });
40
+ it('respects fixed and omitted fields in the manual dialog', async () => {
41
+ const user = userEvent.setup();
42
+ renderField({
43
+ addressFields: {
44
+ country: { mode: AddressFieldMode.FIXED, fixedValue: 'Colombia' },
45
+ postalCode: { mode: AddressFieldMode.OMIT },
46
+ },
47
+ });
48
+ await user.click(screen.getByRole('button', { name: 'Ingresar dirección manualmente' }));
49
+ await screen.findByText('Ingresar Dirección');
50
+ expect(screen.getByLabelText('País (fijo)')).toHaveValue('Colombia');
51
+ expect(screen.queryByLabelText('Código Postal')).not.toBeInTheDocument();
52
+ });
53
+ it('shows the selected address with edit and clear actions', async () => {
54
+ const user = userEvent.setup();
55
+ const value = {
56
+ source: LocationInputMode.GOOGLE_PLACES,
57
+ address: 'Carrera 7 # 71-21, Bogotá',
58
+ latitude: 4.65,
59
+ longitude: -74.05,
60
+ };
61
+ const { onChange } = renderField({ value });
62
+ expect(screen.getByDisplayValue('Carrera 7 # 71-21, Bogotá')).toBeInTheDocument();
63
+ expect(screen.getByRole('button', { name: 'Editar dirección manualmente' })).toBeInTheDocument();
64
+ await user.click(screen.getByRole('button', { name: 'Limpiar selección' }));
65
+ expect(onChange).toHaveBeenCalledWith(null);
66
+ });
67
+ it('renders the read-only summary card', () => {
68
+ renderField({
69
+ readOnly: true,
70
+ value: {
71
+ source: LocationInputMode.GPS,
72
+ name: 'Oficina',
73
+ address: 'Calle 93 # 12-34, Bogotá',
74
+ latitude: 4.678901,
75
+ longitude: -74.048123,
76
+ },
77
+ });
78
+ expect(screen.getByText('Oficina')).toBeInTheDocument();
79
+ expect(screen.getByText('Calle 93 # 12-34, Bogotá')).toBeInTheDocument();
80
+ expect(screen.getByText(/Coordenadas: 4.678901, -74.048123/)).toBeInTheDocument();
81
+ });
82
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arquimedes.co/eureka-forms",
3
3
  "repository": "git://github.com/Arquimede5/Eureka-Forms.git",
4
- "version":"3.0.56-new-steps",
4
+ "version":"3.0.58-new-steps",
5
5
  "scripts": {
6
6
  "watch": "tsgo --noEmit --watch --project tsconfig.app.json",
7
7
  "start": "vite",