@liquidcars/atlas 0.1.15 → 0.1.16

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/README.md CHANGED
@@ -65,6 +65,21 @@ atlas.setTheme({
65
65
  ui: { breadcrumbs: true, panel: true, toolbar: true, hint: false }
66
66
  });
67
67
  atlas.setPalette({ channels: '#58b6ec', offers: '#48c8b0', connection: '#f4edba' });
68
+
69
+ // El componente se puede modificar sin volver a crearlo.
70
+ atlas.addEntity({ id: 'service', name: 'Service', geometry: 'cone', p: [4, 0, 0], s: [2, 2, 2] });
71
+ atlas.updateEntity('service', { text: 'Servicio actualizado', c: '#ed8f98' });
72
+ atlas.addRelation({ from: 'service', to: 'offers', label: 'publica', mode: 'bidirectional' });
73
+ atlas.updateRelation(0, { mode: 'broken' });
74
+ atlas.removeRelation(0);
75
+ atlas.removeEntity('service');
76
+
77
+ atlas.addEventListener('lc-select', event => {
78
+ console.log('Entidad seleccionada:', event.detail.id, event.detail.entity);
79
+ });
80
+ atlas.addEventListener('lc-model-change', event => {
81
+ console.log('Modelo actualizado:', event.detail.kind);
82
+ });
68
83
  ```
69
84
 
70
85
  El mismo bloque `theme` puede formar parte del JSON del modelo. Los estilos
@@ -73,7 +88,12 @@ principales también están expuestos como variables CSS (`--lc-bg`,
73
88
  por lo que una aplicación puede adaptar la apariencia sin modificar el bundle.
74
89
  Las propiedades `atlas.theme` y `atlas.styles` son alias prácticos de esta configuración.
75
90
 
76
- Eventos disponibles: `lc-ready`, `lc-error`, `lc-select`, `lc-relation-mode` y `lc-disconnect`.
91
+ Eventos disponibles: `lc-ready`, `lc-error`, `lc-select`, `lc-model-change`,
92
+ `lc-relation-mode` y `lc-disconnect`. `lc-select` incluye `{ id, entity,
93
+ source }` en `event.detail`; cuando se vuelve a la vista general emite `id:
94
+ null`. Los eventos de selección y cambios de modelo son `bubbles` y
95
+ `composed`, por lo que funcionan también al integrar el componente en otras
96
+ capas de Web Components.
77
97
 
78
98
  ## Contrato de datos
79
99
 
@@ -83,6 +103,10 @@ Eventos disponibles: `lc-ready`, `lc-error`, `lc-select`, `lc-relation-mode` y `
83
103
  - `p` es la posición local respecto a `parent`; una entidad sin `parent` usa coordenadas de escena.
84
104
  - `s` son dimensiones absolutas locales `[x, y, z]`.
85
105
  - `relations` acepta objetos `{from, to, label}` o la forma corta `[from, to, label]`.
106
+ El campo visual opcional `mode` admite `directed` (por defecto),
107
+ `bidirectional` y `broken`. En modo bidireccional los indicadores animados
108
+ circulan en ambos sentidos por el mismo tubo; en modo broken el tubo aparece
109
+ interrumpido en su punto medio.
86
110
 
87
111
  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.
88
112
 
@@ -90,6 +114,13 @@ La paleta puede cambiarse después de cargar el modelo mediante `atlas.setPalett
90
114
 
91
115
  Las acciones de navegación y exploración de la toolbar también están disponibles para la página anfitriona: `zoomIn()`, `zoomOut()`, `fit()`, `frontView()`, `setShellLabels()`, `toggleShellLabels()`, `openAllContainers()` y `closeAllContainers()`. Así se puede construir una barra de controles propia sin depender de la toolbar interna.
92
116
 
117
+ La escena también se puede editar en caliente. `updateModel(patch)` reemplaza
118
+ las secciones indicadas; `addEntity`, `updateEntity` y `removeEntity` gestionan
119
+ entidades, mientras `addRelation`, `updateRelation` y `removeRelation`
120
+ gestionan relaciones. Cada operación reconstruye la geometría necesaria,
121
+ conserva la cámara y la selección cuando sigue siendo válida, y emite
122
+ `lc-model-change` con el tipo de operación y el modelo resultante.
123
+
93
124
  ## Distribución
94
125
 
95
126
  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:
@@ -108,4 +139,4 @@ npm install ./liquidcars-atlas-0.1.15.tgz
108
139
 
109
140
  ## Estado
110
141
 
111
- `0.1.15` mantiene la corrección del desplazamiento interno del texto y ancla las etiquetas a las dimensiones reales de las geometrías redondeadas. Además fija un margen superior absoluto para las etiquetas secundarias de contenedores abiertos, situado dentro del frontal del contenedor y sin depender de sus dimensiones. También corrige los sólidos truncados, normaliza sus dimensiones finales, refuerza la visibilidad de la toolbar con el atributo `toolbar="on"`/`"off"` y `setToolbarVisible()`, desacopla la paleta por defecto de nombres de dominio, añade actualización programática de la paleta con `setPalette`, expone las acciones de navegación para controles externos y mantiene siempre visibles las ayudas de interacción dentro del viewport.
142
+ `0.1.16` mantiene la corrección del desplazamiento interno del texto y ancla las etiquetas a las dimensiones reales de las geometrías redondeadas. Además fija un margen superior absoluto para las etiquetas secundarias de contenedores abiertos, situado dentro del frontal del contenedor y sin depender de sus dimensiones. También corrige los sólidos truncados, normaliza sus dimensiones finales, refuerza la visibilidad de la toolbar con el atributo `toolbar="on"`/`"off"` y `setToolbarVisible()`, desacopla la paleta por defecto de nombres de dominio, añade actualización programática de la paleta con `setPalette`, expone las acciones de navegación para controles externos y mantiene siempre visibles las ayudas de interacción dentro del viewport. Esta versión añade edición en caliente de entidades y relaciones, eventos compuestos de selección y los modos de relación `directed`, `bidirectional` y `broken`.
@@ -1,6 +1,11 @@
1
- /*! LiquidCars Atlas 0.1.15 | MIT */
1
+ /*! LiquidCars Atlas 0.1.16 | MIT */
2
2
 
3
3
  // src/three.core.js
4
+ /**
5
+ * @license
6
+ * Copyright 2010-2025 Three.js Authors
7
+ * SPDX-License-Identifier: MIT
8
+ */
4
9
  var REVISION = "180";
5
10
  var CullFaceNone = 0;
6
11
  var CullFaceBack = 1;
@@ -19224,6 +19229,11 @@ if (typeof window !== "undefined") {
19224
19229
  }
19225
19230
 
19226
19231
  // src/three.module.js
19232
+ /**
19233
+ * @license
19234
+ * Copyright 2010-2025 Three.js Authors
19235
+ * SPDX-License-Identifier: MIT
19236
+ */
19227
19237
  function WebGLAnimation() {
19228
19238
  let context = null;
19229
19239
  let isAnimating = false;
@@ -29371,6 +29381,13 @@ function normalizeModel(input) {
29371
29381
  const relations = Array.isArray(source.relations) ? source.relations.map((relation, index) => Array.isArray(relation) ? { from: relation[0], to: relation[1], label: relation[2] || "", index } : { ...relation, from: relation.from, to: relation.to, label: relation.label || "", index }) : [];
29372
29382
  return { ...source, palette: { ...DEFAULT_PALETTE, ...source.palette || {} }, theme: normalizeTheme(source.theme || source.styles), entities, relations };
29373
29383
  }
29384
+ function relationVisualMode(relation) {
29385
+ if (!relation || typeof relation !== "object") return "directed";
29386
+ const raw = String(relation.mode || relation.visual || relation.render || relation.type || relation.direction || "").toLowerCase();
29387
+ if (relation.bidirectional === true || ["bidirectional", "both", "two-way", "two_way", "duplex"].includes(raw)) return "bidirectional";
29388
+ if (relation.broken === true || ["broken", "interrupted", "\u65AD"].includes(raw)) return "broken";
29389
+ return "directed";
29390
+ }
29374
29391
  function roundedBox(radius, segments, size = [1, 1, 1]) {
29375
29392
  const r = Math.min(Math.max(0, Number(radius) || 0), size[0] / 2, size[1] / 2, size[2] / 2);
29376
29393
  if (!r) return new BoxGeometry(1, 1, 1);
@@ -29629,6 +29646,119 @@ var LiquidCarsAtlas = class extends HTMLElement {
29629
29646
  set model(value) {
29630
29647
  this.load(value);
29631
29648
  }
29649
+ get selectedId() {
29650
+ return this.selected?.id || null;
29651
+ }
29652
+ _emit(type, detail = {}) {
29653
+ this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
29654
+ }
29655
+ _publicEntity(id) {
29656
+ const entity = this.modelData?.entities?.find((item) => item.id === id);
29657
+ return entity ? {
29658
+ ...entity,
29659
+ p: Array.isArray(entity.p) ? [...entity.p] : [0, 0, 0],
29660
+ s: Array.isArray(entity.s) ? [...entity.s] : [1, 1, 1]
29661
+ } : null;
29662
+ }
29663
+ _commitModelChange(kind, detail = {}, options = {}) {
29664
+ const selectedId = options.preserveSelection === false ? null : this.selected?.id;
29665
+ this.modelData = normalizeModel(this.modelData);
29666
+ this._build({ preserveSelection: selectedId });
29667
+ this._emit("lc-model-change", {
29668
+ kind,
29669
+ model: this.modelData,
29670
+ ...detail
29671
+ });
29672
+ return this;
29673
+ }
29674
+ /** Replace selected parts of the model and rebuild the scene in place. */
29675
+ updateModel(patch = {}, options = {}) {
29676
+ if (!patch || typeof patch !== "object" || Array.isArray(patch)) throw new TypeError("El parche del modelo debe ser un objeto.");
29677
+ const current = this.modelData || normalizeModel({});
29678
+ const next = {
29679
+ ...current,
29680
+ ...patch,
29681
+ palette: patch.palette ? { ...current.palette, ...patch.palette } : current.palette,
29682
+ theme: patch.theme || patch.styles ? patch.theme || patch.styles : current.theme,
29683
+ entities: Array.isArray(patch.entities) ? patch.entities : current.entities,
29684
+ relations: Array.isArray(patch.relations) ? patch.relations : current.relations
29685
+ };
29686
+ this.modelData = normalizeModel(next);
29687
+ this.theme = this.modelData.theme;
29688
+ return this._commitModelChange("model-update", { patch }, options);
29689
+ }
29690
+ addEntity(entity, options = {}) {
29691
+ if (!entity || typeof entity !== "object" || !entity.id) throw new TypeError("La entidad debe incluir un id.");
29692
+ if (!this.modelData) this.modelData = normalizeModel({});
29693
+ if (this.modelData.entities.some((item) => item.id === entity.id)) throw new Error(`Ya existe una entidad con id "${entity.id}".`);
29694
+ if (entity.parent && !this.modelData.entities.some((item) => item.id === entity.parent)) throw new Error(`No existe el contenedor padre "${entity.parent}".`);
29695
+ this.modelData.entities = [...this.modelData.entities, { ...entity }];
29696
+ return this._commitModelChange("entity-add", { entity: this._publicEntity(entity.id) }, options);
29697
+ }
29698
+ updateEntity(id, patch = {}, options = {}) {
29699
+ if (!this.modelData) throw new Error("No hay ning\xFAn modelo cargado.");
29700
+ const index = this.modelData.entities.findIndex((item) => item.id === id);
29701
+ if (index < 0) throw new Error(`No existe una entidad con id "${id}".`);
29702
+ if (!patch || typeof patch !== "object" || Array.isArray(patch)) throw new TypeError("Los cambios de una entidad deben ser un objeto.");
29703
+ if (patch.id && patch.id !== id) throw new Error("El id de una entidad no se puede modificar.");
29704
+ if (patch.parent && patch.parent !== id && !this.modelData.entities.some((item) => item.id === patch.parent)) throw new Error(`No existe el contenedor padre "${patch.parent}".`);
29705
+ this.modelData.entities = this.modelData.entities.map((item, itemIndex) => itemIndex === index ? { ...item, ...patch, id } : item);
29706
+ return this._commitModelChange("entity-update", { id, patch, entity: this._publicEntity(id) }, options);
29707
+ }
29708
+ removeEntity(id, options = {}) {
29709
+ if (!this.modelData) throw new Error("No hay ning\xFAn modelo cargado.");
29710
+ const cascade = options.cascade !== false;
29711
+ const removed = /* @__PURE__ */ new Set([id]);
29712
+ if (cascade) {
29713
+ let changed = true;
29714
+ while (changed) {
29715
+ changed = false;
29716
+ for (const entity of this.modelData.entities) {
29717
+ if (entity.parent && removed.has(entity.parent) && !removed.has(entity.id)) {
29718
+ removed.add(entity.id);
29719
+ changed = true;
29720
+ }
29721
+ }
29722
+ }
29723
+ }
29724
+ if (!this.modelData.entities.some((item) => item.id === id)) throw new Error(`No existe una entidad con id "${id}".`);
29725
+ this.modelData.entities = this.modelData.entities.filter((item) => !removed.has(item.id));
29726
+ this.modelData.relations = this.modelData.relations.filter((relation) => !removed.has(relation.from) && !removed.has(relation.to));
29727
+ for (const removedId of removed) this.open.delete(removedId);
29728
+ if (this.selected && removed.has(this.selected.id)) this.selected = null;
29729
+ return this._commitModelChange("entity-remove", { id, removed: [...removed] });
29730
+ }
29731
+ _relationIndex(reference) {
29732
+ if (Number.isInteger(reference)) return reference;
29733
+ if (typeof reference === "string") return this.modelData?.relations?.findIndex((relation) => relation.id === reference) ?? -1;
29734
+ return -1;
29735
+ }
29736
+ addRelation(relation, options = {}) {
29737
+ if (!relation || typeof relation !== "object" || !relation.from || !relation.to) throw new TypeError("La relaci\xF3n debe incluir from y to.");
29738
+ if (!this.modelData) throw new Error("No hay ning\xFAn modelo cargado.");
29739
+ const ids = new Set(this.modelData.entities.map((entity) => entity.id));
29740
+ if (!ids.has(relation.from) || !ids.has(relation.to)) throw new Error("Los extremos de la relaci\xF3n deben existir en el modelo.");
29741
+ this.modelData.relations = [...this.modelData.relations, { ...relation }];
29742
+ return this._commitModelChange("relation-add", { relation: { ...relation } }, options);
29743
+ }
29744
+ updateRelation(reference, patch = {}, options = {}) {
29745
+ if (!this.modelData) throw new Error("No hay ning\xFAn modelo cargado.");
29746
+ const index = this._relationIndex(reference);
29747
+ if (index < 0 || index >= this.modelData.relations.length) throw new Error("No existe la relaci\xF3n indicada.");
29748
+ if (!patch || typeof patch !== "object" || Array.isArray(patch)) throw new TypeError("Los cambios de una relaci\xF3n deben ser un objeto.");
29749
+ const next = { ...this.modelData.relations[index], ...patch };
29750
+ const ids = new Set(this.modelData.entities.map((entity) => entity.id));
29751
+ if (!ids.has(next.from) || !ids.has(next.to)) throw new Error("Los extremos de la relaci\xF3n deben existir en el modelo.");
29752
+ this.modelData.relations = this.modelData.relations.map((item, itemIndex) => itemIndex === index ? next : item);
29753
+ return this._commitModelChange("relation-update", { index, patch, relation: { ...next } }, options);
29754
+ }
29755
+ removeRelation(reference, options = {}) {
29756
+ if (!this.modelData) throw new Error("No hay ning\xFAn modelo cargado.");
29757
+ const index = this._relationIndex(reference);
29758
+ if (index < 0 || index >= this.modelData.relations.length) throw new Error("No existe la relaci\xF3n indicada.");
29759
+ const [relation] = this.modelData.relations.splice(index, 1);
29760
+ return this._commitModelChange("relation-remove", { index, relation: { ...relation } }, options);
29761
+ }
29632
29762
  get palette() {
29633
29763
  return this.modelData?.palette || { ...DEFAULT_PALETTE };
29634
29764
  }
@@ -29802,6 +29932,8 @@ var LiquidCarsAtlas = class extends HTMLElement {
29802
29932
  try {
29803
29933
  const data = typeof source === "string" ? await fetch(source).then((response) => response.json()) : source;
29804
29934
  this.modelData = normalizeModel(data);
29935
+ this.open.clear();
29936
+ this.selected = null;
29805
29937
  if (this._pendingPalette) {
29806
29938
  this.modelData.palette = { ...this.modelData.palette, ...this._pendingPalette };
29807
29939
  this._pendingPalette = null;
@@ -29861,6 +29993,14 @@ var LiquidCarsAtlas = class extends HTMLElement {
29861
29993
  this.scene.remove(node.group);
29862
29994
  }
29863
29995
  this.nodes.clear();
29996
+ this._clearRelationGroup();
29997
+ }
29998
+ _clearRelationGroup() {
29999
+ for (const object of this.relationGroup.children) {
30000
+ object.geometry?.dispose();
30001
+ if (Array.isArray(object.material)) object.material.forEach((material) => material.dispose());
30002
+ else object.material?.dispose();
30003
+ }
29864
30004
  this.relationGroup.clear();
29865
30005
  }
29866
30006
  _recolorNodes() {
@@ -29871,10 +30011,12 @@ var LiquidCarsAtlas = class extends HTMLElement {
29871
30011
  if (node.edge?.material) node.edge.material.color.set(color);
29872
30012
  }
29873
30013
  }
29874
- _build() {
30014
+ _build(options = {}) {
29875
30015
  if (!this._ensureRenderer()) return;
30016
+ const selectedId = options.preserveSelection || null;
29876
30017
  this._clearObjects();
29877
30018
  for (const entity of this.modelData.entities) this.nodes.set(entity.id, { ...entity });
30019
+ this.open = new Set([...this.open].filter((id) => this.nodes.has(id)));
29878
30020
  for (const entity of this.modelData.entities) {
29879
30021
  const node = this.nodes.get(entity.id);
29880
30022
  node.group = new Group();
@@ -29906,6 +30048,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
29906
30048
  node.group.add(node.label);
29907
30049
  this.scene.add(node.group);
29908
30050
  }
30051
+ this.selected = selectedId && this.nodes.has(selectedId) ? this.nodes.get(selectedId) : null;
29909
30052
  this.relations = this.modelData.relations;
29910
30053
  this._renderPanel();
29911
30054
  this._rebuildRelations();
@@ -29945,7 +30088,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
29945
30088
  return this.relations.map((relation, index) => relation.from === this.selected.id || relation.to === this.selected.id ? index : -1).filter((index) => index >= 0);
29946
30089
  }
29947
30090
  _rebuildRelations() {
29948
- this.relationGroup.clear();
30091
+ this._clearRelationGroup();
29949
30092
  const active = this._activeRelationIndexes();
29950
30093
  const color = new Color(this.modelData?.palette.connection || DEFAULT_PALETTE.connection);
29951
30094
  for (const index of active) {
@@ -29957,14 +30100,28 @@ var LiquidCarsAtlas = class extends HTMLElement {
29957
30100
  const end = this._endpoint(to, from, false);
29958
30101
  const delta = end.clone().sub(start);
29959
30102
  if (delta.lengthSq() < 0.01) continue;
29960
- const tube = new Mesh(new TubeGeometry(new LineCurve3(start, end), 10, 0.045, 6, false), new MeshBasicMaterial({ color, transparent: true, opacity: 0.82 }));
29961
- this.relationGroup.add(tube);
30103
+ const visualMode = relationVisualMode(relation);
30104
+ const makeTube = (from2, to2, opacity = 0.82) => {
30105
+ const tube = new Mesh(new TubeGeometry(new LineCurve3(from2, to2), 10, 0.045, 6, false), new MeshBasicMaterial({ color, transparent: true, opacity }));
30106
+ tube.userData.relationIndex = index;
30107
+ this.relationGroup.add(tube);
30108
+ };
30109
+ if (visualMode === "broken") {
30110
+ const direction = delta.clone().normalize();
30111
+ const midpoint = start.clone().lerp(end, 0.5);
30112
+ const gap = Math.min(delta.length() * 0.16, 0.72);
30113
+ makeTube(start, midpoint.clone().sub(direction.clone().multiplyScalar(gap / 2)), 0.55);
30114
+ makeTube(midpoint.clone().add(direction.clone().multiplyScalar(gap / 2)), end, 0.55);
30115
+ continue;
30116
+ }
30117
+ makeTube(start, end);
29962
30118
  for (let ringIndex = 0; ringIndex < 4; ringIndex++) {
29963
30119
  const ring = new Mesh(new TorusGeometry(0.068, 0.018, 8, 20), new MeshBasicMaterial({ color, transparent: true, opacity: 0.9 }));
29964
30120
  ring.userData.phase = ringIndex / 4;
30121
+ ring.userData.direction = visualMode === "bidirectional" && ringIndex % 2 ? -1 : 1;
29965
30122
  ring.userData.start = start.clone();
29966
30123
  ring.userData.end = end.clone();
29967
- ring.quaternion.setFromUnitVectors(new Vector3(0, 0, 1), delta.normalize());
30124
+ ring.quaternion.setFromUnitVectors(new Vector3(0, 0, 1), delta.clone().normalize());
29968
30125
  this.relationGroup.add(ring);
29969
30126
  }
29970
30127
  }
@@ -30012,7 +30169,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
30012
30169
  });
30013
30170
  this._applyTheme();
30014
30171
  }
30015
- select(id) {
30172
+ select(id, options = {}) {
30016
30173
  const node = this.nodes.get(id);
30017
30174
  if (!node) return;
30018
30175
  let parent = node.parent ? this.nodes.get(node.parent) : null;
@@ -30027,7 +30184,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
30027
30184
  this._renderPanel();
30028
30185
  this._rebuildRelations();
30029
30186
  this.invalidate();
30030
- this.dispatchEvent(new CustomEvent("lc-select", { detail: { id, entity: node } }));
30187
+ this._emit("lc-select", { id, entity: this._publicEntity(id), source: options.source || "api" });
30031
30188
  }
30032
30189
  setRelationMode(mode) {
30033
30190
  this.relationMode = ["all", "selected", "none"].includes(mode) ? mode : "selected";
@@ -30045,6 +30202,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
30045
30202
  this.dispatchEvent(new CustomEvent("lc-relation-mode", { detail: { mode: this.relationMode } }));
30046
30203
  }
30047
30204
  reset() {
30205
+ const previousId = this.selected?.id || null;
30048
30206
  this.selected = null;
30049
30207
  this.open.clear();
30050
30208
  this.relationMode = "selected";
@@ -30067,6 +30225,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
30067
30225
  this._renderPanel();
30068
30226
  this._rebuildRelations();
30069
30227
  this.invalidate();
30228
+ if (previousId) this._emit("lc-select", { id: null, entity: null, previousId, source: "reset" });
30070
30229
  }
30071
30230
  resize() {
30072
30231
  if (!this.renderer) return;
@@ -30125,7 +30284,10 @@ var LiquidCarsAtlas = class extends HTMLElement {
30125
30284
  top.visible = displayTransparentLabel && cameraAbove;
30126
30285
  const scale = displayTransparentLabel ? 0.52 : 1;
30127
30286
  front.scale.setScalar(scale);
30128
- front.position.x = displayTransparentLabel ? -labelSize.x / 2 + 0.18 + labelWidth * scale / 2 - textOffsetX * scale : 0;
30287
+ back.scale.setScalar(scale);
30288
+ const openLabelX = displayTransparentLabel ? -labelSize.x / 2 + 0.18 + labelWidth * scale / 2 - textOffsetX * scale : 0;
30289
+ front.position.x = openLabelX;
30290
+ back.position.x = displayTransparentLabel ? -openLabelX : 0;
30129
30291
  const shellFrontTopMargin = 0.18;
30130
30292
  front.position.y = displayTransparentLabel ? labelSize.y / 2 - shellFrontTopMargin - labelHeight * scale / 2 : labelSize.y / 2 - 0.09 - labelHeight / 2;
30131
30293
  front.position.z = displayTransparentLabel ? labelSize.z / 2 + 0.025 : labelSize.z / 2 + 0.012;
@@ -30145,16 +30307,23 @@ var LiquidCarsAtlas = class extends HTMLElement {
30145
30307
  back.visible = !cameraOnFront && !opened;
30146
30308
  top.visible = false;
30147
30309
  front.scale.setScalar(1);
30310
+ back.scale.setScalar(1);
30148
30311
  front.position.x = 0;
30312
+ back.position.x = 0;
30149
30313
  front.position.y = labelSize.y / 2 - 0.09 - labelHeight / 2;
30314
+ back.position.y = front.position.y;
30315
+ back.position.z = -labelSize.z / 2 - 0.012;
30150
30316
  front.material.color.set(15923964);
30151
30317
  front.material.opacity = 1;
30318
+ back.material.color.set(15923964);
30319
+ back.material.opacity = 1;
30152
30320
  }
30153
30321
  }
30154
30322
  for (const child of this.relationGroup.children) {
30155
30323
  if (!child.userData.start) continue;
30156
30324
  const phase = child.userData.phase || 0;
30157
- const factor = (time * 22e-5 + phase) % 1;
30325
+ const direction = child.userData.direction || 1;
30326
+ const factor = ((time * 22e-5 * direction + phase) % 1 + 1) % 1;
30158
30327
  child.position.lerpVectors(child.userData.start, child.userData.end, factor);
30159
30328
  }
30160
30329
  this.renderer.render(this.scene, this.camera);
@@ -30208,7 +30377,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
30208
30377
  this.canvas.style.cursor = "grab";
30209
30378
  if (drag && !drag.moved) {
30210
30379
  const node = this._pick(event);
30211
- if (node) this.select(node.id);
30380
+ if (node) this.select(node.id, { source: "pointer" });
30212
30381
  }
30213
30382
  this.invalidate();
30214
30383
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liquidcars/atlas",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "Reusable 3D conceptual atlas Web Component for LiquidCars-style models",
5
5
  "type": "module",
6
6
  "main": "./dist/liquidcars-atlas.js",