@liquidcars/atlas
v0.1.51
Published
Reusable 3D conceptual atlas Web Component for LiquidCars-style models
Maintainers
Readme
@liquidcars/atlas
Componente web reutilizable para explorar modelos conceptuales en 3D. El componente lee una isla de datos con palette, entities y relations, resuelve coordenadas relativas mediante parent y permite explorar geometrías, contenedores, relaciones animadas y selección contextual.
Prueba local
Desde la carpeta del paquete:
python3 -m http.server 4173Después abre http://localhost:4173/ (o http://localhost:4173/demo/). La demo usa el mismo modelo LiquidCars que la página actual, pero carga el componente como módulo ESM independiente. Es importante servirlo por HTTP, no abrir el HTML directamente con file://.
Uso directo en una página
<script type="module" src="./dist/liquidcars-atlas.js"></script>
<liquidcars-atlas height="720" relation-mode="selected" relation-marker="cone" shell-labels="on" toolbar="on">
<script type="application/json">
{
"palette": {
"channels": "#58b6ec",
"offers": "#48c8b0"
},
"entities": [
{
"id": "offers",
"name": "OFFERS",
"sub": "Stock y servicios",
"p": [0, -3, 0],
"s": [11, 3.4, 5],
"c": "#48c8b0",
"type": "container",
"text": "..."
}
],
"relations": [
{
"from": "dealer",
"to": "base-offer",
"label": "mantiene",
"text": "Mantiene sincronizada la oferta base del concesionario."
}
]
}
</script>
</liquidcars-atlas>También se puede cargar el modelo desde JavaScript:
const atlas = document.querySelector('liquidcars-atlas');
atlas.model = model;
atlas.select('distribution-agreement'); // selecciona sin mover la cámara
atlas.focusSelection(); // centra y amplía la selección explícitamente
atlas.setSelectionNavigation('preserve'); // 'preserve' | 'center' | 'fit'
atlas.selectRelation(0); // también acepta el id de una relación
atlas.getRelationBundle(); // conexiones A↔B que comparten el tubo de la selección
atlas.setRelationMode('all'); // 'all' | 'selected' | 'callers' | 'called' | 'none'
atlas.setRelationMarker('cone'); // 'cone' por defecto; 'ring' conserva el indicador clásico
atlas.setVisibleRelationGroups(['electric', 'water']); // null vuelve a mostrar todos
atlas.setToolbarVisible(true); // también se puede usar toolbar="on" | "off"
atlas.setPanelVisible(true); // también se puede usar panel="on" | "off"
atlas.setItemFacing('camera'); // valor por defecto; usa 'model' para conservar la orientación original
atlas.zoomIn();
atlas.zoomOut();
atlas.panBy(-1.5, 0); // desplazamiento horizontal/vertical en el plano de cámara
atlas.panTo(0, 0, 0); // centra la cámara en un punto del mundo
atlas.fit();
atlas.frontView();
const initialView = atlas.getCameraView();
atlas.setCameraView(initialView, { animate: false });
atlas.restoreInitialCamera({ animate: false });
atlas.setShellLabels(true);
atlas.setContainerOpen('distribution-agreements', true);
atlas.toggleContainer('distribution-agreements');
console.log(atlas.isContainerOpen('distribution-agreements'));
console.log(atlas.getOpenContainerIds());
atlas.restoreInitialContainerState();
atlas.openAllContainers();
atlas.closeAllContainers();
atlas.setTheme({
background: { color: '#07121e', glow: '#20394b' },
lighting: { ambient: 1.8, directional: 3.0 },
ui: { breadcrumbs: true, panel: true, toolbar: true, hint: false }
});
atlas.setPalette({ channels: '#58b6ec', offers: '#48c8b0', connection: '#f4edba' });
atlas.setSelectionMode('event'); // el clic emite lc-select sin navegar internamente
// El componente se puede modificar sin volver a crearlo.
atlas.addEntity({ id: 'service', name: 'Service', geometry: 'cone', p: [4, 0, 0], s: [2, 2, 2] });
atlas.updateEntity('service', { text: 'Servicio actualizado', c: '#ed8f98' });
atlas.addRelation({ from: 'service', to: 'offers', label: 'publica', text: 'Publica ofertas validadas.', mode: 'bidirectional' });
atlas.updateRelation(0, { mode: 'broken' });
atlas.removeRelation(0);
atlas.removeEntity('service');
atlas.addEventListener('lc-select', event => {
if (event.detail.kind === 'entity') {
console.log('Entidad seleccionada:', event.detail.id, event.detail.entity);
} else if (event.detail.kind === 'relation') {
console.log('Relación seleccionada:', event.detail.index, event.detail.relation);
}
});
atlas.addEventListener('lc-model-change', event => {
console.log('Modelo actualizado:', event.detail.kind);
});El mismo bloque theme puede formar parte del JSON del modelo. Los estilos
principales también están expuestos como variables CSS (--lc-bg,
--lc-bg-glow, --lc-panel-bg, --lc-toolbar-bg, --lc-crumb-bg, etc.),
por lo que una aplicación puede adaptar la apariencia sin modificar el bundle.
Las propiedades atlas.theme y atlas.styles son alias prácticos de esta configuración.
Eventos disponibles: lc-ready, lc-error, lc-warning, lc-select, lc-model-change,
lc-relation-mode, lc-relation-marker-change, lc-relation-groups-change, lc-item-facing-change, lc-focus-change, lc-print-ready, lc-print-error,
lc-print-restored y lc-disconnect. lc-select usa un detalle discriminado:
las entidades incluyen { kind: 'entity', id, entity, source } y las relaciones
{ kind: 'relation', id, index, relation, source }. Los campos históricos id
y entity se conservan para las selecciones de entidad. Al volver a la vista
general, kind, id, entity y relation son null. Los eventos de selección y cambios de modelo son bubbles y
composed, por lo que funcionan también al integrar el componente en otras
capas de Web Components.
El comportamiento del clic se puede elegir con selection-mode="internal"
(por defecto) o selection-mode="event". En el segundo caso el componente
emite lc-select con source: "pointer", pero deja la decisión a la página
anfitriona. La API equivalente es setSelectionMode() (también disponible
como setClickBehavior()).
La selección interna conserva la cámara por defecto. Un doble clic, el botón
Enfocar selección del panel o focusSelection() centran y amplían el
elemento seleccionado. El comportamiento automático puede cambiarse con el
atributo selection-navigation="preserve|center|fit", mediante
setSelectionNavigation() o declarativamente en el modelo:
render:
relationMarker: cone # ring conserva el indicador circular clásico
navigation:
onSelect: preserve
itemFacing: model # opt-out; camera es el valor por defectoImpresión
Atlas prepara automáticamente una representación WYSIWYG al entrar en el modo
de impresión del navegador. La captura conserva el ángulo, el zoom, el foco y
los objetos visibles, pero omite la toolbar, el panel de información y las
indicaciones de interacción. Al terminar se restaura el canvas WebGL.
Los conos animados indican el sentido incluso en una captura estática. Un
modelo puede recuperar los aros clásicos con render.relationMarker: ring.
La misma funcionalidad puede controlarse explícitamente:
const atlas = document.querySelector("liquidcars-atlas");
const transparent = atlas.captureFrame(); // canvas 2D con alfa
const themed = atlas.captureFrame({ background: "theme" }); // añade el fondo visual del componente
const white = atlas.captureFrame({ background: "#ffffff" }); // fondo sólido opcional
const presentation = atlas.captureFrame({ background: "transparent", scale: 2 }); // salida HD
const copied = await atlas.copyFrame({ background: "theme", scale: 2 }); // portapapeles PNG
const portable = await atlas.copyFrame({ background: "transparent", scale: 2, fallback: "download" });
atlas.prepareForPrint();
atlas.restoreAfterPrint();captureFrame() usa WebGLRenderingContext.readPixels() para funcionar también
en navegadores que no permiten copiar directamente un canvas WebGL sin
preserveDrawingBuffer. El framebuffer permanece transparente: el fondo de tema
se compone en el canvas 2D únicamente cuando se solicita y no recibe luces ni
sombras de la escena. scale vuelve a renderizar temporalmente entre 1× y 4×,
respetando el límite de framebuffer del navegador, y restaura inmediatamente el
canvas interactivo. Los colores WebGL semitransparentes se convierten de alfa
premultiplicado a alfa directo para conservar el tono de los contenedores
abiertos cuando el PNG se coloca sobre un fondo claro. Para evitar que un bloque se divida entre páginas, el
componente aplica break-inside: avoid durante la impresión.
La toolbar incorpora las variantes PNG con fondo y transparente. Ambas usan
copyFrame() a resolución 2×; si el portapapeles de imágenes no está disponible,
la acción descarga atlas-view.png. La API emite lc-image-export con el método
empleado (clipboard o download) y lc-image-export-error si tampoco puede
generar el archivo.
Contrato de datos
palette: mapa de nombres de color a valores CSS/hex.theme(opcional): configuración debackground,lighting,panel,toolbar,breadcrumbs,hintyui.uipermite mostrar u ocultarpanel,toolbar,breadcrumbsyhint.render.camera(opcional): vista inicial conposition,targetyzoom. Las coordenadas pertenecen al espacio del modelo;zoomconserva el encuadre relativo entre tamaños de visor.render.initialMode:2do3d.render.views."2d".themeadmiteauto(hereda el fondo del documento),lightodark;render.views."2d".gridactiva o desactiva los puntos (por defecto,true).render.views."2d".backgroundpermite un color CSS concreto.render.ui.toolbaraceptatrue,falseo una lista como[mode, zoom, fit]para mostrar solo esos controles.render.focus(opcional): filtrado visual por selección.modeaceptaalloconnected,effectaceptadimohide, ylayoutdistingue posiciones estables (preserve) de una proyección recompilada (reflow).render.relationMarker(opcional): forma de los indicadores animados de relación. Admitecone(por defecto, conserva el sentido en capturas estáticas) oring.style.label.anchor(opcional): posición predeterminada de las etiquetas de los elementos (auto,top,bottom,leftoright). Si se omite, los elementos usanbottom; una entidad puede sobrescribirlo con su propiostyle.label.anchor. Los títulos de contenedores mantienen su colocación automática salvo override explícito.entities: objetos conid,name,p,s,c,type,text,urly opcionalmenteparent,geometry,radius,segments,frontLabel,anchoryports. Un contenedor puede declararopen: truepara comenzar abierto; el valor predeterminado es cerrado y se omite al serializar. Las URL HTTP(S) se abren en una pestaña nueva desde el panel de información.pes la posición local respecto aparent; una entidad sinparentusa coordenadas de escena.sson dimensiones absolutas locales[x, y, z].relationsacepta objetos{from, to, label, text, url}o la forma corta[from, to, label].labeles el título corto de la conexión,textsu descripción yurlsu documentación externa en el panel de información.relationGroupsdeclara capas temáticas{id, label, color, description}y cada relación puede referenciar una congroup.render.visibleRelationGroupslas filtra sin alterar el modo de relaciones. Las conexiones paralelas visibles comparten automáticamente un portador translúcido, pero cada conductor conserva selección, dirección, marcador e inspector propios. El campo visual opcionalmodeadmitedirected(por defecto),bidirectionalybroken. En modo bidireccional los indicadores animados circulan en ambos sentidos por el mismo tubo; en modo broken los dos tramos se sustituyen por un único tubo rojo con tres indicadores que vibran localmente. distribuidos a lo largo del recorrido.
Geometrías incluidas en esta primera versión: box, cylinder, sphere, cone, frustum-cone, frustum-pyramid, frustum-triangle, prism, pyramid-square, pyramid-triangle, octahedron, dodecahedron e icosahedron. Las variantes frustum-* son sólidos truncados con dos caras finitas paralelas; las dimensiones s se aplican sobre su caja final normalizada.
Las etiquetas se rasterizan con relleno claro y un contorno oscuro proporcional para mantener el contraste sobre geometrías tanto claras como oscuras.
Geometry packs
Las geometrías pueden distribuirse en paquetes independientes. El componente
expone registerGeometryPack(), unregisterGeometryPack(),
getGeometryCatalog() y getGeometryPackCatalog(); los mismos métodos están
disponibles como estáticos en LiquidCarsAtlas. Los identificadores usan un
namespace (infra.server, emoji-fluent.robot, architecture.hexagonal.port, etc.) y las primitivas
integradas también se publican como geo.box, geo.sphere, etc. Los nombres
históricos sin namespace siguen siendo compatibles. Los namespaces pueden contener segmentos separados por puntos, de modo que packs especializados como architecture.hexagonal convivan con architecture.
getGeometryCatalog() conserva el catálogo operativo de geometrías.
getGeometryPackCatalog() describe las librerías registradas, incluyendo
autoría, licencia, procedencia y recursos, sin duplicar estos datos en cada
geometría.
El módulo exporta también ATLAS_VERSION, y la clase registrada expone la
misma versión mediante customElements.get("liquidcars-atlas").version. Esta
segunda forma identifica la implementación que el navegador utiliza realmente
si accidentalmente se cargan varias versiones del core.
import { registerGeometryPack } from "@liquidcars/atlas";
const robotImage = new URL("./assets/robot.svg", import.meta.url).href;
registerGeometryPack({
namespace: "example",
name: "Example visual assets",
version: "0.1.0",
authors: [{ name: "Example Studio", url: "https://example.com" }],
license: { spdx: "MIT", url: "https://opensource.org/license/mit" },
sources: [{
id: "example-artwork",
name: "Example artwork",
authors: ["Example Studio"],
license: "MIT"
}],
resources: [{
id: "robot",
kind: "image",
mimeType: "image/svg+xml",
path: "./assets/robot.svg",
url: robotImage,
source: "example-artwork"
}],
palette: { backplate: "#193142", image: "#ffffff" },
geometries: [{
id: "robot",
canonicalSize: [2.4, 2.4, .5],
resource: "robot",
create({ resources }) {
return { parts: [
{ shape: "box", size: [.92, .92, .12], color: "backplate", radius: .16 },
{ shape: "plane", size: [.84, .84, 1], position: [0, 0, .07],
color: "image", image: resources.robot.url, imageFit: "contain",
transparent: true, edges: false }
] };
}
}]
});El modelo configura cada instancia sin conocer la implementación del paquete:
{
"geometry": "example.robot",
"geometryPalette": { "backplate": "#284b63" }
}Cada definición puede declarar parámetros tipados, una paleta local,
proporciones canónicas y anchors. Los recursos declarados por el pack se
entregan a create({ resources }) indexados por id; pueden estar
empaquetados mediante new URL("./assets/...", import.meta.url), ser data URIs
o apuntar a un recurso remoto. sources permite atribuir una colección de
terceros una sola vez y cada recurso puede sobrescribir authors o license.
Las piezas declarativas admiten primitivas, posición, rotación, escala,
materiales e imágenes por URL/data URI; los SVG también pueden proporcionarse
inline. Para planos con imagen, imageFit admite cover, contain y
stretch. Desde 0.1.40, shape: "svg-extrude" convierte rutas SVG en una
malla con profundidad real, por lo que una marca puede formar parte del volumen
sin convertirse en texto ni en una textura plana:
{
shape: "svg-extrude",
svg: resources.postgresql.content,
size: [.24, .24, .05],
position: [.2, -.04, .44],
color: "#4169e1",
emissive: "#4169e1",
emissiveIntensity: .4,
bevel: .04,
curveSegments: 6,
edges: false
}emissive y emissiveIntensity permiten conservar colores vivos bajo la
iluminación 3D sin eliminar el relieve ni las sombras. El helper exportado
createSvgExtrudeGeometry(svg, options) admite rutas con
líneas, curvas cuadráticas y cúbicas, arcos elípticos y subtrazados. El renderer
mantiene Three.js encapsulado para evitar que los packs
dependan de su versión interna. Las relaciones parten por defecto de la
superficie orientada hacia el otro extremo. anchor: center conserva el
comportamiento centrado cuando sea necesario, y fromAnchor y toAnchor
permiten elegir puertos explícitos declarados por una geometría.
Si un namespace no está registrado, Atlas conserva el modelo renderizable con
una caja roja de fallback y emite lc-warning con el código
GEOMETRY_NOT_REGISTERED. Si la factoría del pack falla se usa el código
GEOMETRY_BUILD_FAILED.
La paleta puede cambiarse después de cargar el modelo mediante atlas.setPalette({ nombre: color }) o asignando atlas.palette = { ... }. El cambio actualiza las piezas, aristas y relaciones sin reconstruir el modelo.
Las acciones de navegación y exploración también están disponibles para la página anfitriona: zoomIn(), zoomOut(), panBy(), panTo(), fit(), frontView(), getCameraView(), setCameraView(), restoreInitialCamera(), setRelationMarker(), setItemFacing(), toggleItemFacing(), setShellLabels(), toggleShellLabels(), setPanelVisible(), setFocusFilter(), clearFocusFilter(), getFocusedIds(), isContainerOpen(), getOpenContainerIds(), setContainerOpen(), setOpenContainers(), toggleContainer(), restoreInitialContainerState(), openAllContainers() y closeAllContainers(). El usuario puede desplazar la vista con el botón derecho, con Shift + arrastre o mediante dos dedos en una pantalla táctil. Así se puede construir una barra de controles propia sin depender de la toolbar interna.
Cada cambio del estado vivo emite lc-container-state-change. Su detalle contiene
openContainers, changedContainers, changedContainer, open y source, de
modo que el host puede consultar o persistir una interacción sin acceder a
detalles internos del componente.
Una vista inicial portable puede declararse dentro del modelo:
entities:
- id: distribution-agreements
type: container
open: true
children: []
render:
focus:
mode: connected
effect: dim
layout: preserve
opacity: 0.12
camera:
position: [12.4, 7.2, 18.6]
target: [0, 1.5, 0]
zoom: 1.35La API equivalente es setFocusFilter({ mode, effect, layout, opacity }).
getFocusedIds() devuelve la selección, sus vecinos directos y los contenedores
ancestros necesarios. El evento lc-focus-change permite que un host conectado a
@liquidcars/atlas-layout recompile una proyección cuando layout es reflow.
position y target fijan la dirección observada. Cuando existe zoom, éste
prevalece para calcular la distancia final y mantener el encuadre; si se omite,
Atlas lo deriva de la distancia declarada.
La escena también se puede editar en caliente. updateModel(patch) reemplaza
las secciones indicadas; addEntity, updateEntity y removeEntity gestionan
entidades, mientras addRelation, updateRelation y removeRelation
gestionan relaciones. Cada operación reconstruye la geometría necesaria,
conserva la cámara y la selección cuando sigue siendo válida, y emite
lc-model-change con el tipo de operación y el modelo resultante.
getRenderStats() devuelve una instantánea de entidades, relaciones, objetos
animados, draw calls y contadores de geometrías, texturas y programas del
renderer. Resulta útil para comprobar que una secuencia repetida de mutaciones
vuelve a una línea base estable. Al reconstruir o desconectar el componente se
liberan geometrías, materiales, texturas, cargas de imagen pendientes y el
contexto WebGL.
Distribución
El archivo dist/liquidcars-atlas.js es un módulo ESM autocontenido: Three.js se incluye dentro del bundle, por lo que el consumidor no necesita instalar otra dependencia para probarlo. El bundle no contiene ningún modelo de negocio ni datos de LiquidCars Atlas; los modelos se suministran aparte mediante model, src o un <script type="application/json">. Para una distribución privada se puede servir ese archivo desde un CDN propio o desde el mismo sitio. Para publicar en npm, después de revisar el API estable:
npm publish --access publicUna vez publicado, los consumidores podrán instalarlo con npm install @liquidcars/atlas o cargar el bundle desde un CDN compatible con npm.
Para probar el paquete sin publicarlo, basta con instalar el tarball generado:
npm install ./liquidcars-atlas-0.1.40.tgzEstado
0.1.40 añade SVG extruido nativo para que los geometry packs puedan construir
marcas volumétricas a partir de recursos vectoriales sin recurrir a campos de
texto. 0.1.39 incorpora metadatos normalizados de autoría, licencia,
procedencia y recursos para los geometry packs, añade
getGeometryPackCatalog() y entrega los recursos declarados a
create({ resources }). Se mantiene intacto getGeometryCatalog() para
conservar la compatibilidad. 0.1.28 rasteriza explícitamente los SVG sobre un canvas antes de subirlos a
WebGL y emite diagnósticos cuando falla la carga o la conversión. 0.1.27 añade carga automática de imágenes SVG remotas, SVG mediante data URI
y SVG inline, manteniendo las mismas opciones de cara y ajuste que las imágenes
raster. 0.1.26 mantiene el centro como anclaje implícito de toda relación y utiliza
los puertos de geometrías externas únicamente cuando el modelo los solicita.
También evita recorrer los materiales de geometrías compuestas en cada frame,
refuerza la liberación de recursos y expone getRenderStats().
0.1.24 incorporó geometry packs independientes, namespaces, parámetros
personalizados, paletas locales, anchors e imágenes declarativas. La
versión 0.1.23 añadió desplazamiento lateral de cámara mediante ratón, teclado
modificador y gestos táctiles, además de la API pública panBy() y panTo().
Se conserva la selección contextual de relaciones introducida en 0.1.22: el usuario puede
seleccionar un tubo directamente. Cuando varias conexiones comparten extremos,
los clics sucesivos recorren sus conductores y el panel enumera el tubo virtual
completo para permitir una selección precisa. La API pública añade
selectRelation(), getRelationBundle(), selectedRelation y selection, y la
selección se conserva durante las mutaciones del modelo siempre que el elemento
siga existiendo. Se mantienen las correcciones de geometría, toolbar, etiquetas,
paleta externa, edición en caliente y los modos directed, bidirectional y
broken introducidos en las versiones anteriores.
Hyperlinked Atlas documents
Entities and relations can keep their external url and also declare an
atlasRef such as atlas:actors/dealer.atlas.md#inventory. A leading slash
(atlas:/overview.atlas.md) starts at the root of the host's document
collection; otherwise the path resolves beside the current document. The
optional fragment selects an entity in the destination. Paths cannot escape
the collection root.
The information panel accepts content: { format: "markdown", value: "…" }
or content: { format: "html", value: "…" }. It also accepts a source
instead of an inline value when a content resolver is configured. HTML is
restricted to text formatting and safe atlas: or HTTP(S) links. Existing
text remains the fallback when content is absent.
The host supplies documents and optional external content:
atlas.setDocumentResolver(async ({ uri, from }) => ({
uri,
model: await loadAndCompileDocument(uri, from)
}));
atlas.setContentResolver(async ({ source, from, format }) =>
loadContent(source, from, format));
await atlas.load(initialModel);
atlas.setDocumentUri("atlas:/overview.atlas.md");The component exposes openAtlas(reference), goHome(), goBack(),
goForward(), goToVisit(index) and getDocumentHistory().
Navigation and failures emit lc-navigation and lc-navigation-error.
Home, Back and Forward appear in a top navigation bar; holding Back or Forward
opens a visit list. Hosts with their own top bar can set
navigation-controls="external" and connect those controls to the public
history methods, as Atlas Studio does. Selecting a linked entity or relation shows a floating link button
beside it. The information panel remains focused on descriptive content and
external documentation. Studio's Markdown and visual views can open a local
folder; they treat that folder as the root of atlas:/.
Where supported, Studio requests read and write directory access. It discovers
document names, reads files only as they are opened or referenced, and caches
their text in the current tab. Save writes the active Markdown document back
to its original file, including edits from the visual editor; switching editors
preserves the document and visit history. Nothing is sent to a server. Browsers
without the directory picker use a file-input fallback, whose native dialog may
call the selection “Upload”; Studio still reads the files locally on demand.
In that fallback, Save opens a picker or downloads a copy because the selected
files cannot be written in place.
See examples/hyperlinks for two linked documents and an external Markdown
information file.
Named models and cloud destinations
The optional model selector identifies a technical model key within the
destination document. Titles may change without changing the reference:
| Destination | Reference |
| --- | --- |
| Current document | atlas:?model=process |
| Local collection | atlas:actors/dealer.atlas.md?model=inventory |
| Current cloud publication | atlas://cloud/DOCUMENT_ID@published?model=process |
| Fixed cloud revision | atlas://cloud/DOCUMENT_ID@r3?model=process |
| Authorized cloud Draft | atlas://cloud/DOCUMENT_ID@draft?model=process |
Append #entityId to select an entity. URI-encode model keys and fragments.
Omitting model uses the destination document's defaultModel. A cloud origin
cannot resolve local document or content paths; a local origin may open cloud
documents through its host resolver.
The exported atlasReferenceTarget(reference, from) parses the document kind,
version and model selector. The resolver may return context alongside uri
and model; the runtime carries that context in history and navigation events.
Return a canonical revision URI when resolving @published to pin the visit.
Cloud history visits invoke the resolver again so the host can recheck access.
The runtime does not grant authorization or contact a cloud deployment itself.
Use setNavigationGuard(async () => { ... }) to finish pending saves before
navigation. Resolution errors, missing models and missing entity fragments leave
the origin active. History retains 2D/3D mode, camera, diagram zoom and scroll,
selection, open containers and filters. Hosts that rebuild the component can use
restoreNavigationState(state, { restorePresentation: true }) to restore its
presentation as well as its history.
