Tipos TypeScript
@mapvx/web-components-ssr incluye declaraciones de TypeScript para los datos que intercambian tu página y <mapvx-website>: el contenido del evento userAction, la estructura de initial-data y los modelos de datos del SDK de mapas a los que hacen referencia. Todo lo que puedes usar de ellas es un tipo, así que impórtalo con import type. Las declaraciones no describen el elemento en sí; Tipado del elemento muestra qué agregar a tu proyecto.
import type { InitialData, UserAction } from '@mapvx/web-components-ssr'Importar los tipos
Las declaraciones vienen dentro del paquete de npm; no hay un paquete @types aparte. Instala la versión exacta que probaste (consulta npm):
npm install --save-exact @mapvx/web-components-ssr@1.6.1- Punto de entrada: el campo
typesdel paquete apunta adist/types/public-api.d.ts. Es un único archivo sin imports, así que TypeScript no necesita ningún otro paquete para resolverlo. - Ruta de importación: siempre la raíz del paquete,
'@mapvx/web-components-ssr', conimport type. Se resuelve conmoduleResolutionenbundler,node16onodenext, y también connode10, que TypeScript 6.0 marca como obsoleto (ahí exige"ignoreDeprecations": "6.0"). - Biblioteca DOM: el archivo hace referencia a un tipo del DOM (
GeolocationPosition). En untsconfig.jsonsin la bibliotecaDOM, como uno para código que solo corre en el servidor, TypeScript muestra el errorCannot find name 'GeolocationPosition'dentro del archivo. Agrega"DOM"alibo activaskipLibCheck. - JavaScript sin TypeScript: para tener los tipos en un editor que revisa JavaScript, haz referencia a ellos desde JSDoc. Los imports de JSDoc son comentarios, así que nunca cargan el bundle.
/** @typedef {import('@mapvx/web-components-ssr').UserAction} UserAction */Qué exporta el archivo:
| Grupo | Exportaciones | Detalles |
|---|---|---|
| Contenido de eventos | UserAction, SelectFilterAction, ShowPlaceAction, SelectDestinationAction, SearchAction, ReturnToHomeAction | UserAction |
| Datos iniciales | InitialData, PlaceResponse, InstitutionResponse | InitialData |
| Modelos del componente | AnimationState, CategoryFiltersData, CurrentLocation, FormattedSchedule, RoutePoint, SearchData | Otros modelos |
| Tipos de datos del SDK de mapas | MVXPlace, Institution, Banner, MVXRoute, MVXRouteLeg, MVXRouteStep, MVXTransport, MVXLocation, CityFilterOption, OpeningHours, OpeningTime, DaysOfTheWeek, LatLng, InnerFloor, Property | Tipos del SDK de mapas |
| Valores declarados, no importables | EMPTY_SEARCH_DATA, FALLBACK_IMAGE_URL, MEASUREMENT_ID, extractBannerColor, getSchedule, isCurrentLocation, isDealOrEventBanner, isPlace, prepareAllSchedules, toCurrentLocation | Solo tipos |
Solo tipos
El punto de entrada en tiempo de ejecución del paquete (main) es dist/browser/main.js, el bundle del componente. Cargarlo registra <mapvx-website>; no es una biblioteca. Sus únicas exportaciones son nombres internos que importan sus propios chunks diferidos, así que ninguno de los nombres del archivo de declaraciones se puede importar en tiempo de ejecución.
Aun así, el archivo de declaraciones incluye algunos valores de tiempo de ejecución: las constantes y funciones de la última fila de la tabla anterior, el enum AnimationState y las clases del SDK (MVXPlace, Institution, Banner, MVXRoute, MVXRouteLeg, MVXRouteStep, MVXTransport). TypeScript acepta importar cualquiera de ellos como valor, pero la importación falla después:
- Un bundler detiene la compilación porque
main.jsno tiene esa exportación. esbuild, por ejemplo, muestraNo matching export in "node_modules/@mapvx/web-components-ssr/dist/browser/main.js" for import "isPlace". - Un navegador que carga el módulo de forma nativa lanza un
SyntaxErrorporque el módulo no ofrece la exportación pedida. - Cualquier import de
'@mapvx/web-components-ssr'que quede en el código compilado cargamain.js, que registra el elemento. En código de servidor lanzaReferenceError: HTMLElement is not defined(consulta Solución de problemas).
Los nombres importados con import type solo se pueden usar como tipos. new MVXPlace(...), value instanceof MVXPlace y AnimationState.Playing no compilan (TS1361: ... cannot be used as a value because it was imported using 'import type'). Lo que te entrega el componente, como event.detail, son datos simples, así que no necesitas ningún constructor.
Escribe import type, no el modificador type en línea:
// Correcto: se elimina por completo del código compilado
import type { UserAction } from '@mapvx/web-components-ssr'
// Evítalo: con verbatimModuleSyntax se compila como
// import {} from '@mapvx/web-components-ssr'
import { type UserAction } from '@mapvx/web-components-ssr'Con verbatimModuleSyntax activado, si todos los especificadores de un import llevan el modificador type en línea, TypeScript lo conserva como un import vacío, y un import vacío de todos modos carga el bundle.
UserAction
El detail de cada evento userAction es un UserAction, una unión discriminada por type. Las declaraciones:
export interface FilterSelection {
/** Primera categoría seleccionada, o 'All' si no hay ninguna. Se mantiene por compatibilidad. */
filter: string
/** La selección completa. Vacía significa sin filtro de categoría. */
filters: string[]
}
export interface SelectFilterAction {
type: 'select-filter'
data: FilterSelection
}
export interface ShowPlaceAction {
type: 'show-place'
data: { placeId: string; alias: string | undefined }
}
export interface SelectDestinationAction {
type: 'select-destination'
data: { destinationId: string; alias: string | undefined }
}
export interface SearchAction {
type: 'search'
data: { searchTerm: string }
}
export interface ReturnToHomeAction {
type: 'return-to-home'
data: { filter: string }
}
export type UserAction =
| SelectFilterAction
| ShowPlaceAction
| SelectDestinationAction
| SearchAction
| ReturnToHomeActiontype | data | Se emite cuando |
|---|---|---|
select-filter | { filter, filters } | El visitante prende o apaga una categoría. filters es la selección completa. |
show-place | { placeId, alias } | Se abre el perfil de un lugar: clic en una tarjeta o en un marcador del mapa, un deep link tenant o Back desde la pantalla de ruta. |
select-destination | { destinationId, alias } | El visitante hace clic en Pedestrian-friendly directions en un perfil. |
search | { searchTerm } | Se ejecuta una búsqueda con un término nuevo. |
return-to-home | { filter } | El componente vuelve a su pantalla de inicio desde un perfil o desde la búsqueda. filter es el nombre de la categoría seleccionada, o un string vacío cuando no hay categorías. |
- Identificadores:
placeIdydestinationIdson el id de MapVX del lugar, que esmapvxIdenMVXPlaceeidenPlaceResponse.aliases elclientIddel lugar: sualiasen los datos de MapVX, o suexternalIdcuando no tiene alias, yundefinedcuando no tiene ninguno de los dos. La clavealiassiempre está presente. - cardSelected: este evento heredado no tiene un tipo exportado. Su
detailes el id de MapVX del lugar, así que usa el tipoCustomEvent<string>(consulta cardSelected). - Detalles: cuándo se dispara cada acción, y con qué frecuencia, se describe en Tipos de acción.
Un listener tipado, sin tipar el elemento:
import type { UserAction } from '@mapvx/web-components-ssr'
const el = document.querySelector('mapvx-website')
el?.addEventListener('userAction', (event) => {
const action = (event as CustomEvent<UserAction>).detail
switch (action.type) {
case 'select-filter':
case 'return-to-home':
console.log('Categoría:', action.data.filter)
break
case 'show-place':
console.log('Lugar:', action.data.placeId, action.data.alias)
break
case 'select-destination':
console.log('Indicaciones a:', action.data.destinationId)
break
case 'search':
console.log('Búsqueda:', action.data.searchTerm)
break
default: {
// Error de compilación aquí si un tipo de acción nuevo no se maneja arriba
const unhandled: never = action
console.warn('Acción no manejada', unhandled)
}
}
})Cada case acota action.data a la estructura de ese miembro. La asignación a never convierte un miembro sin manejar en un error de compilación, por ejemplo después de una actualización que agregue uno. Agrega el listener al propio elemento con el nombre exacto userAction, porque el evento no se propaga hacia los elementos padre (consulta Escuchar eventos).
InitialData
La estructura del atributo initial-data:
export interface InitialData {
availablePlaces: PlaceResponse[] | null
institutions: InstitutionResponse[] | null
}- Un string JSON: el atributo y la propiedad
initialDatareciben este objeto serializado como JSON.prepareInitialDatade@mapvx/web-components-ssr-helpersya se resuelve con ese string, así que pásalo sin cambios. Un objeto asignado a la propiedad lanza unTypeErrordentro del componente. - Objetos sin procesar de la API:
PlaceResponseeInstitutionResponseson los objetos que devuelve la API de MapVX, con el id enid. El componente los convierte enMVXPlaceeInstitution, donde el id esmapvxId. - Solo estas dos claves: las demás claves se ignoran, y una clave en
nullse omite. El helper deja una clave ennullcuando su petición recibe un código de error HTTP. - Dónde está el tipo: el paquete del helper no exporta
InitialData; impórtalo desde@mapvx/web-components-ssr.
Código de servidor que revisa los datos antes de renderizar:
import type { InitialData } from '@mapvx/web-components-ssr'
import { prepareInitialData } from '@mapvx/web-components-ssr-helpers'
const initialData = await prepareInitialData('YOUR_API_KEY') // string JSON
const { availablePlaces, institutions } = JSON.parse(initialData) as InitialData
console.log(`${availablePlaces?.length ?? 0} lugares, ${institutions?.length ?? 0} instituciones`)
// Pasa initialData (el string) al atributo initial-data, no el objeto parseadoEl manejo de errores y los tiempos de espera del helper se explican en prepareInitialData.
En 1.6.1, usar initial-data junto con api-key muestra cada lugar dos veces. Consulta el problema conocido.
Otros modelos
Las exportaciones restantes describen el estado interno del componente. Ninguna aparece en los atributos ni en los eventos del elemento.
| Tipo | Estructura | Describe |
|---|---|---|
AnimationState | enum: NotStarted = 0, Playing = 1, Paused = 2, Finished = 3, Stepping = 4 | La animación de la ruta. Stepping, agregado en 1.4.0 como último miembro, hace que la ruta avance un paso por cada acción del visitante en lugar de reproducirse sola. |
CategoryFiltersData | { name: string; placeData: string[] } | Una categoría en oferta: su etiqueta y los ids de MapVX de sus lugares (vacío para All). También es el payload de categoriesChange. |
FormattedSchedule | { day: string; schedule: string[] } | Una fila del horario semanal. day puede ser un rango como Monday - Friday. |
SearchData | { term: string; results: MVXPlace[] } | Un término de búsqueda y sus resultados. |
CurrentLocation | { type: 'current-location'; title: 'Current location'; position: LatLng } | La posición del visitante, usada como punto de una ruta. |
RoutePoint | MVXPlace o CurrentLocation | El origen o el destino de una ruta. |
Estos valores declarados existen dentro del bundle, pero main.js no los exporta (consulta Solo tipos):
| Nombre | Qué es |
|---|---|
EMPTY_SEARCH_DATA | Un SearchData con el término vacío y sin resultados. |
FALLBACK_IMAGE_URL | La imagen de reemplazo que muestran las tarjetas y los perfiles cuando un lugar no tiene fotos ni logo. |
MEASUREMENT_ID | El ID de medición de la propiedad de Google Analytics de MapVX, a la que reporta el componente (consulta Google Analytics). |
toCurrentLocation(position) | Convierte un GeolocationPosition en un CurrentLocation. |
isCurrentLocation(point) | Type guard: el punto es un CurrentLocation. |
isPlace(point) | Type guard: el punto es un MVXPlace (tiene mapvxId). |
isDealOrEventBanner(banner) | Si un banner tiene la etiqueta deals o events. |
extractBannerColor(description) | El color declarado en el primer atributo style en línea de la descripción de un banner, o #EE5845. |
getSchedule(openingHours) | El horario de hoy como string HTML: el del día anterior mientras sigue abierto un intervalo nocturno, o un texto como Open on Monday cuando el lugar no abre hoy. Se calcula con el reloj y la zona horaria del visitante. |
prepareAllSchedules(openingHours) | El horario semanal como FormattedSchedule[], a partir del lunes. Los días consecutivos con el mismo horario comparten una fila (la última fila, que termina el domingo, se une a la primera cuando tienen el mismo horario), y los días sin horario muestran Closed. Los horarios son strings HTML. |
Tipos del SDK de mapas
El componente está construido sobre @mapvx/web-js, el SDK de mapas de MapVX, que va incluido dentro de main.js (1.6.1 incluye la versión 3.1.0 del SDK). El paquete reexporta los tipos de datos del SDK, y sus declaraciones están copiadas en public-api.d.ts, así que no necesitas instalar @mapvx/web-js para usarlos.
| Tipo | Declaración | Descripción |
|---|---|---|
MVXPlace | class | Un lugar: mapvxId, clientId, title, position, category, localizedCategory, address, openingHours, images, logo, banners, pisos, parentId y parentInstitutionId. |
Institution | class | Un conjunto de lugares, como un centro comercial: mapvxId, title, country, city opcional, category, properties. |
Banner | class | Un banner de un lugar: url, tags, urlAction, sameLink y una description opcional que puede contener HTML. |
MVXRoute | class | Una ruta formada por legs, con los getters steps, duration (segundos) y length (metros). |
MVXRouteLeg | class | El tramo de una ruta entre dos puntos: distance, duration, ubicaciones de inicio y fin, steps. |
MVXRouteStep | class | Un paso de navegación: instrucciones, distancia, duración, ubicaciones, pisos y elevatorStep. |
MVXTransport | class | Horarios de transporte y recorridos en tiempo real. |
MVXLocation | interface | Un LatLng con floorId y placeId opcionales. |
CityFilterOption | unión | Una entrada del filtro de ciudad: una opción de institución (type: 'institution') o una opción de lugar padre (type: 'parent-place', con parentPlaceId), cada una con filterId, name e institutionId. |
OpeningHours | type | Los intervalos de apertura de cada día, indexados por DaysOfTheWeek. |
OpeningTime | interface | Un intervalo: open y close como horas en formato de 24 horas, por ejemplo 09:00. |
DaysOfTheWeek | type | 'mo', 'tu', 'we', 'th', 'fr', 'sa' o 'su'. |
LatLng | interface | { lat: number; lng: number } |
InnerFloor | interface | Un piso de un edificio: key, index, level, name, defaultFloor. |
Property | interface | Una propiedad de un lugar o una institución: title, subtitle y los opcionales icon, url y type. Las pestañas de categoría salen de las propiedades de la institución. |
PlaceResponse | interface | Un lugar tal como lo devuelve la API de MapVX (id, title, lat, lng y más). Se usa en InitialData. |
InstitutionResponse | interface | Una institución tal como la devuelve la API de MapVX. Se usa en InitialData. |
MVXPlace, Institution, Banner y los tipos de rutas y transporte están declarados como clases. Úsalos solo como tipos: sus constructores y métodos estáticos no están disponibles para tu código (Solo tipos).
Tipado del elemento
El paquete no tipa el elemento <mapvx-website>: no tiene una entrada en HTMLElementTagNameMap, ni una entrada JSX en IntrinsicElements, ni un mapa de eventos. Sin ellos, document.querySelector('mapvx-website') queda tipado como un Element | null genérico, y TSX muestra el error Property 'mapvx-website' does not exist on type 'JSX.IntrinsicElements'. Agrega un archivo de declaraciones a tu proyecto. Las declaraciones de abajo se verificaron con TypeScript 5.9 y 6.0.
- Sirve cualquier archivo
.d.tsque cubra elincludede tutsconfig.json. No le pongas el mismo nombre que un archivo.tso.tsxde la misma carpeta (por ejemplo,mapvx-website.d.tsjunto amapvx-website.tsx): en ese caso TypeScript ignora el.d.tssin ningún aviso. - Las declaraciones del DOM y de React pueden ir en el mismo archivo.
DOM y eventos
Esta declaración tipa querySelector, createElement, las propiedades del elemento y sus dos eventos:
// types/mapvx-website.d.ts
import type { UserAction } from '@mapvx/web-components-ssr'
export interface MapvxWebsiteEventMap extends HTMLElementEventMap {
userAction: CustomEvent<UserAction>
cardSelected: CustomEvent<string>
}
export interface MapvxWebsiteElement extends HTMLElement {
apiKey: string
institutionId: string
initialData: string // un string JSON, nunca un objeto
showCategoryFilters: boolean
showCityFilter: boolean
defaultToMap: boolean
inheritFontFamily: boolean
hideDeals: boolean
showDealLabels: boolean
cardWidth: number | undefined
rowsPerPage: number
addEventListener<K extends keyof MapvxWebsiteEventMap>(
type: K,
listener: (this: MapvxWebsiteElement, event: MapvxWebsiteEventMap[K]) => void,
options?: boolean | AddEventListenerOptions,
): void
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions,
): void
removeEventListener<K extends keyof MapvxWebsiteEventMap>(
type: K,
listener: (this: MapvxWebsiteElement, event: MapvxWebsiteEventMap[K]) => void,
options?: boolean | EventListenerOptions,
): void
removeEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | EventListenerOptions,
): void
}
declare global {
interface HTMLElementTagNameMap {
'mapvx-website': MapvxWebsiteElement
}
}Con ella, no hace falta ningún cast:
const el = document.querySelector('mapvx-website') // MapvxWebsiteElement | null
el?.addEventListener('userAction', (event) => {
// event: CustomEvent<UserAction>
if (event.detail.type === 'search') console.log('Búsqueda:', event.detail.data.searchTerm)
})
el?.addEventListener('cardSelected', (event) => console.log('Tarjeta:', event.detail)) // string
const created = document.createElement('mapvx-website')
created.rowsPerPage = 3- Los tipos de las propiedades son los valores que usa el componente después de interpretar sus entradas. Cómo acepta y devuelve valores cada propiedad se describe en Propiedades JavaScript.
- Un nombre de evento mal escrito, como
'useraction', no coincide con ninguna entrada del mapa de eventos. El listener recibe entonces unEventgenérico, así que leerevent.detailse marca como error.
Función auxiliar para escuchar
Cuando la referencia al elemento no está tipada, por ejemplo una ref de un framework tipada como HTMLElement, esta función te da un UserAction tipado y devuelve una función que quita el listener:
// on-user-action.ts
import type { UserAction } from '@mapvx/web-components-ssr'
export function onUserAction(element: Element, handler: (action: UserAction) => void): () => void {
const listener = (event: Event) => handler((event as CustomEvent<UserAction>).detail)
element.addEventListener('userAction', listener)
return () => element.removeEventListener('userAction', listener)
}import { onUserAction } from './on-user-action'
const el = document.querySelector('mapvx-website')
if (el) {
const stop = onUserAction(el, (action) => {
if (action.type === 'show-place') console.log('Lugar:', action.data.placeId)
})
// Llama a stop() para quitar el listener, por ejemplo cuando se desmonta tu vista
}React
TSX necesita una declaración JSX para la etiqueta. Ambas versiones usan la forma declare module 'react'; el archivo debe tener un import de nivel superior, como estos, para que TypeScript lo trate como una ampliación del módulo react.
React 19
// types/mapvx-website-react.d.ts
import type { DetailedHTMLProps, HTMLAttributes } from 'react'
import type { UserAction } from '@mapvx/web-components-ssr'
// React 19 renderiza {true} como "" y omite {false}: acepta solo strings
type BooleanAttribute = 'true' | 'false'
interface MapvxWebsiteProps extends DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement> {
'api-key'?: string
'institution-id'?: string
'initial-data'?: string
'show-category-filters'?: BooleanAttribute
'show-city-filter'?: BooleanAttribute
'show-view-toggle'?: BooleanAttribute
'show-search-bar'?: BooleanAttribute
'show-drop-pin'?: BooleanAttribute
'selected-categories'?: string
'search-term'?: string
'full-width-map'?: BooleanAttribute
'hide-unselected-markers'?: BooleanAttribute
'default-to-map'?: BooleanAttribute
'inherit-font-family'?: BooleanAttribute
'hide-deals'?: BooleanAttribute
'show-deal-labels'?: BooleanAttribute
'card-width'?: number | string
'rows-per-page'?: number | string
'zoom'?: number | string
'center'?: string
// React 19 escucha el evento cuyo nombre sigue a "on", respetando mayúsculas y minúsculas
onuserAction?: (event: CustomEvent<UserAction>) => void
oncardSelected?: (event: CustomEvent<string>) => void
}
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'mapvx-website': MapvxWebsiteProps
}
}
}import type { UserAction } from '@mapvx/web-components-ssr'
export function Directory({ initialData }: { initialData?: string }) {
const handleAction = (event: CustomEvent<UserAction>) => {
const action = event.detail
if (action.type === 'show-place') console.log('Lugar:', action.data.placeId)
}
return (
<mapvx-website
api-key="YOUR_API_KEY"
institution-id="YOUR_INSTITUTION_ID"
initial-data={initialData}
default-to-map="true"
show-city-filter="false"
rows-per-page={3}
onuserAction={handleAction}
oncardSelected={(event) => console.log('Tarjeta:', event.detail)}
/>
)
}- React 19 renderiza
{true}como un atributo vacío, que el componente lee como false, y omite{false}, así que en el primer render se aplica el valor por defecto (true parashow-category-filtersyshow-city-filter). Tipar los booleanos como'true' | 'false'convierte ambos errores en errores de compilación. - Las props de evento son
onuserActionyoncardSelected:onseguido del nombre del evento con sus mayúsculas y minúsculas exactas.onUserActionnunca se dispara, y esta declaración lo rechaza. @types/react19 no tiene un namespaceJSXglobal, así que un bloquedeclare global { namespace JSX { ... } }no tiene ningún efecto ahí.- Esta forma se verificó con
@types/react19.
Carga el bundle solo en el navegador; consulta React y Next.js.
La declaración JSX se aplica al TSX de React. Con otros frameworks, la declaración del DOM y la función auxiliar tipan el elemento en tu código de script. Cómo cargar el bundle y configurar las plantillas se explica en Vue, Angular y Otros frameworks.