@liquidcars/atlas 0.1.10 → 0.1.11

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
@@ -56,6 +56,7 @@ atlas.setTheme({
56
56
  lighting: { ambient: 1.8, directional: 3.0 },
57
57
  ui: { breadcrumbs: true, panel: true, toolbar: true, hint: false }
58
58
  });
59
+ atlas.setPalette({ channels: '#58b6ec', offers: '#48c8b0', connection: '#f4edba' });
59
60
  ```
60
61
 
61
62
  El mismo bloque `theme` puede formar parte del JSON del modelo. Los estilos
@@ -75,7 +76,9 @@ Eventos disponibles: `lc-ready`, `lc-error`, `lc-select`, `lc-relation-mode` y `
75
76
  - `s` son dimensiones absolutas locales `[x, y, z]`.
76
77
  - `relations` acepta objetos `{from, to, label}` o la forma corta `[from, to, label]`.
77
78
 
78
- 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`.
79
+ 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.
80
+
81
+ 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.
79
82
 
80
83
  ## Distribución
81
84
 
@@ -95,4 +98,4 @@ npm install ./liquidcars-atlas-0.1.10.tgz
95
98
 
96
99
  ## Estado
97
100
 
98
- `0.1.10` 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 incluye configuración programática de tema, iluminación y elementos de interfaz.
101
+ `0.1.11` 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. Esta versión corrige los sólidos truncados, normaliza sus dimensiones finales, refuerza la visibilidad de la toolbar y añade actualización programática de la paleta con `setPalette`.
@@ -1,4 +1,4 @@
1
- /*! LiquidCars Atlas 0.1.10 | MIT */
1
+ /*! LiquidCars Atlas 0.1.11 | MIT */
2
2
 
3
3
  // src/three.core.js
4
4
  var REVISION = "180";
@@ -29029,7 +29029,7 @@ function normalizeTheme(value) {
29029
29029
  }
29030
29030
  var CSS = [
29031
29031
  ":host{display:block;position:relative;height:var(--lc-height,680px);min-height:420px;color:var(--lc-panel-title,#e9f0f5);font:14px Inter,system-ui,-apple-system,sans-serif;background:var(--lc-bg,#0a111a);border:1px solid #294252;border-radius:12px;overflow:hidden;contain:content}",
29032
- "*{box-sizing:border-box}.atlas{display:grid;grid-template-columns:minmax(0,1fr) 300px;height:100%}.atlas[data-panel=hidden]{grid-template-columns:1fr}.viewport{position:relative;min-width:0;background:radial-gradient(ellipse at 50% 45%,var(--lc-bg-glow,#1c2b39),var(--lc-bg-fade,#0a111a) 82%)}canvas{display:block;width:100%;height:100%;cursor:grab;touch-action:none}.toolbar{position:absolute;z-index:2;bottom:16px;left:50%;transform:translateX(-50%);display:flex;gap:3px;padding:6px;background:var(--lc-toolbar-bg,#15232fe8);border:1px solid var(--lc-toolbar-border,#3a5062);border-radius:8px;white-space:nowrap}.toolbar[data-hidden=true],.panel[data-hidden=true],.crumbs[data-hidden=true],.hint[data-hidden=true]{display:none}.toolbar button{height:30px;padding:0 9px;border:0;border-radius:5px;background:none;color:var(--lc-toolbar-text,#dbe7ec);cursor:pointer}.toolbar button:hover,.toolbar button.active{background:var(--lc-toolbar-active,#1f3d4b);color:var(--lc-toolbar-active-text,#c5f2e9)}.toolbar i{display:block;width:1px;margin:2px 3px;background:#38515f}.hint{position:absolute;bottom:5px;left:0;right:0;text-align:center;font-size:11px;color:var(--lc-hint,#8da6b8);pointer-events:none}.panel{overflow:auto;padding:24px 20px;background:var(--lc-panel-bg,linear-gradient(140deg,#182633,#0e1925 65%));border-left:1px solid var(--lc-panel-border,#2a3a48)}.panel h2{font-size:26px;line-height:1.12;font-weight:470;margin:12px 0;color:var(--lc-panel-title,#e9f0f5)}.panel h3{font-size:11px;letter-spacing:1.4px;color:#91aabd;margin:24px 0 10px}.panel p{font-size:14px;line-height:1.55;color:var(--lc-panel-text,#b5c6d2)}.panel .eyebrow{font-size:11px;letter-spacing:1.7px;color:#6ad7c0}.panel .sub{font-size:13px;color:#8fa8bd;margin-bottom:16px}.panel button{display:block;width:100%;padding:10px 0;text-align:left;background:none;border:0;border-bottom:1px solid var(--lc-panel-border,#2a3a48);color:#d9e7eb;cursor:pointer}.panel button:hover{color:#6ad7c0}.panel .relation{padding:10px 0;border-bottom:1px solid var(--lc-panel-border,#2a3a48)}.panel .relation strong{display:block;font-weight:550}.panel .relation small{display:block;color:#86a6b9;font-size:12px;margin-top:4px}.panel .back{color:#9db7c8;font-size:13px}.panel .crumbs{display:flex;flex-wrap:wrap;gap:5px;align-items:center;margin:0 0 12px}.panel .crumbs button{display:inline;width:auto;padding:3px 7px;border:0;border-radius:4px;background:var(--lc-crumb-bg,#142733);color:var(--lc-crumb-text,#91b6c7);font-size:12px}.panel .crumbs button.current{background:var(--lc-crumb-active,#1c4a4d);color:var(--lc-crumb-active-text,#c5f2e9)}.panel .note{border-top:1px solid var(--lc-panel-border,#2a3a48);padding-top:16px;margin-top:22px;font-size:12px;color:#7692a6}.error{position:absolute;inset:20%;padding:18px;background:#172b3b;border:1px solid #7896a9;line-height:1.5;z-index:4}@media(max-width:900px){.toolbar{gap:0}.toolbar button{padding:0 6px;font-size:12px}}@media(max-width:760px){:host{height:680px}.atlas{grid-template-columns:1fr;grid-template-rows:minmax(420px,1fr) auto}.panel{max-height:250px;border-left:0;border-top:1px solid var(--lc-panel-border,#2a3a48);padding:18px}.toolbar{bottom:10px}.hint{display:none}}"
29032
+ "*{box-sizing:border-box}.atlas{display:grid;grid-template-columns:minmax(0,1fr) 300px;height:100%}.atlas[data-panel=hidden]{grid-template-columns:1fr}.viewport{position:relative;min-width:0;background:radial-gradient(ellipse at 50% 45%,var(--lc-bg-glow,#1c2b39),var(--lc-bg-fade,#0a111a) 82%)}canvas{display:block;width:100%;height:100%;cursor:grab;touch-action:none}.toolbar{position:absolute;z-index:10;bottom:16px;left:50%;transform:translateX(-50%);display:flex;gap:3px;padding:6px;background:var(--lc-toolbar-bg,#15232fe8);border:1px solid var(--lc-toolbar-border,#3a5062);border-radius:8px;white-space:nowrap}.toolbar[data-hidden=true],.toolbar[hidden],.panel[data-hidden=true],.panel[hidden],.crumbs[data-hidden=true],.crumbs[hidden],.hint[data-hidden=true],.hint[hidden]{display:none!important}.toolbar button{height:30px;padding:0 9px;border:0;border-radius:5px;background:none;color:var(--lc-toolbar-text,#dbe7ec);cursor:pointer}.toolbar button:hover,.toolbar button.active{background:var(--lc-toolbar-active,#1f3d4b);color:var(--lc-toolbar-active-text,#c5f2e9)}.toolbar i{display:block;width:1px;margin:2px 3px;background:#38515f}.hint{position:absolute;bottom:5px;left:0;right:0;text-align:center;font-size:11px;color:var(--lc-hint,#8da6b8);pointer-events:none}.panel{overflow:auto;padding:24px 20px;background:var(--lc-panel-bg,linear-gradient(140deg,#182633,#0e1925 65%));border-left:1px solid var(--lc-panel-border,#2a3a48)}.panel h2{font-size:26px;line-height:1.12;font-weight:470;margin:12px 0;color:var(--lc-panel-title,#e9f0f5)}.panel h3{font-size:11px;letter-spacing:1.4px;color:#91aabd;margin:24px 0 10px}.panel p{font-size:14px;line-height:1.55;color:var(--lc-panel-text,#b5c6d2)}.panel .eyebrow{font-size:11px;letter-spacing:1.7px;color:#6ad7c0}.panel .sub{font-size:13px;color:#8fa8bd;margin-bottom:16px}.panel button{display:block;width:100%;padding:10px 0;text-align:left;background:none;border:0;border-bottom:1px solid var(--lc-panel-border,#2a3a48);color:#d9e7eb;cursor:pointer}.panel button:hover{color:#6ad7c0}.panel .relation{padding:10px 0;border-bottom:1px solid var(--lc-panel-border,#2a3a48)}.panel .relation strong{display:block;font-weight:550}.panel .relation small{display:block;color:#86a6b9;font-size:12px;margin-top:4px}.panel .back{color:#9db7c8;font-size:13px}.panel .crumbs{display:flex;flex-wrap:wrap;gap:5px;align-items:center;margin:0 0 12px}.panel .crumbs button{display:inline;width:auto;padding:3px 7px;border:0;border-radius:4px;background:var(--lc-crumb-bg,#142733);color:var(--lc-crumb-text,#91b6c7);font-size:12px}.panel .crumbs button.current{background:var(--lc-crumb-active,#1c4a4d);color:var(--lc-crumb-active-text,#c5f2e9)}.panel .note{border-top:1px solid var(--lc-panel-border,#2a3a48);padding-top:16px;margin-top:22px;font-size:12px;color:#7692a6}.error{position:absolute;inset:20%;padding:18px;background:#172b3b;border:1px solid #7896a9;line-height:1.5;z-index:4}@media(max-width:900px){.toolbar{gap:0}.toolbar button{padding:0 6px;font-size:12px}}@media(max-width:760px){:host{height:680px}.atlas{grid-template-columns:1fr;grid-template-rows:minmax(420px,1fr) auto}.panel{max-height:250px;border-left:0;border-top:1px solid var(--lc-panel-border,#2a3a48);padding:18px}.toolbar{bottom:10px}.hint{display:none}}"
29033
29033
  ].join("");
29034
29034
  var TEMPLATE = '<div class="atlas"><section class="viewport"><canvas tabindex="0" aria-label="Modelo 3D interactivo"></canvas><div class="toolbar"><button data-action="minus" aria-label="Alejar">\u2212</button><button data-action="plus" aria-label="Acercar">+</button><i></i><button data-action="fit">\u2316 Encuadrar</button><button data-action="front">\u25B1 Frontal</button><button data-action="labels" class="active" aria-pressed="true">\u25A7 Etiquetas</button><i></i><button data-action="relations-all" aria-pressed="false">Todas</button><button data-action="relations-selected" class="active" aria-pressed="true">Selecci\xF3n</button><button data-action="relations-none" aria-pressed="false">Ocultar</button></div><div class="hint">Arrastra para girar \xB7 rueda para zoom \xB7 clic para seleccionar</div></section><aside class="panel" aria-live="polite"></aside></div>';
29035
29035
  function escapeHtml(value) {
@@ -29117,11 +29117,15 @@ function makeGeometry(kind, radius = 0, segments = 4, size = [1, 1, 1]) {
29117
29117
  case "frustum-cone":
29118
29118
  case "truncated-cone":
29119
29119
  return r ? roundedFrustum(0.28, 0.5, r, s) : new CylinderGeometry(0.28, 0.5, 1, 32);
29120
+ // A frustum is a solid with two finite parallel faces. ConeGeometry
29121
+ // leaves the narrow face collapsed to an apex, which made both truncated
29122
+ // pyramid variants look like ordinary pyramids. CylinderGeometry with
29123
+ // three/four radial segments gives us a real triangular/square frustum.
29120
29124
  case "frustum-pyramid":
29121
29125
  case "truncated-pyramid":
29122
- return softenPolyhedron(new ConeGeometry(0.24, 0.5, 4), r);
29126
+ return new CylinderGeometry(0.28, 0.5, 1, 4);
29123
29127
  case "frustum-triangle":
29124
- return softenPolyhedron(new ConeGeometry(0.24, 0.5, 3), r);
29128
+ return new CylinderGeometry(0.28, 0.5, 1, 3);
29125
29129
  case "prism":
29126
29130
  return r ? roundedPrism(6, r, s) : new CylinderGeometry(0.5, 0.5, 1, 6);
29127
29131
  case "pyramid-square":
@@ -29140,6 +29144,15 @@ function makeGeometry(kind, radius = 0, segments = 4, size = [1, 1, 1]) {
29140
29144
  return r ? roundedBox(r, s, size) : new BoxGeometry(1, 1, 1);
29141
29145
  }
29142
29146
  }
29147
+ function normalizeGeometry(geometry) {
29148
+ geometry.computeBoundingBox();
29149
+ const bounds = geometry.boundingBox;
29150
+ const size = bounds?.getSize(new Vector3());
29151
+ if (!size || !size.x || !size.y || !size.z) return geometry;
29152
+ geometry.scale(1 / size.x, 1 / size.y, 1 / size.z);
29153
+ geometry.computeBoundingBox();
29154
+ return geometry;
29155
+ }
29143
29156
  function makeLabel(text, color, dimensions, type = "") {
29144
29157
  const bitmap = document.createElement("canvas");
29145
29158
  const context = bitmap.getContext("2d");
@@ -29280,6 +29293,24 @@ var LiquidCarsAtlas = class extends HTMLElement {
29280
29293
  set model(value) {
29281
29294
  this.load(value);
29282
29295
  }
29296
+ get palette() {
29297
+ return this.modelData?.palette || { ...DEFAULT_PALETTE };
29298
+ }
29299
+ set palette(value) {
29300
+ this.setPalette(value);
29301
+ }
29302
+ setPalette(value = {}) {
29303
+ if (!value || typeof value !== "object") return this;
29304
+ if (!this.modelData) {
29305
+ this._pendingPalette = { ...this._pendingPalette || {}, ...value };
29306
+ return this;
29307
+ }
29308
+ this.modelData.palette = { ...this.modelData.palette, ...value };
29309
+ this._recolorNodes();
29310
+ this._rebuildRelations();
29311
+ this.invalidate();
29312
+ return this;
29313
+ }
29283
29314
  get styles() {
29284
29315
  return this.theme;
29285
29316
  }
@@ -29325,11 +29356,24 @@ var LiquidCarsAtlas = class extends HTMLElement {
29325
29356
  const panel = this.shadowRoot.querySelector(".panel");
29326
29357
  const hint = this.shadowRoot.querySelector(".hint");
29327
29358
  if (atlas) atlas.dataset.panel = theme.ui.panel === false ? "hidden" : "visible";
29328
- if (toolbar) toolbar.dataset.hidden = String(theme.ui.toolbar === false);
29329
- if (panel) panel.dataset.hidden = String(theme.ui.panel === false);
29330
- if (hint) hint.dataset.hidden = String(theme.ui.hint === false);
29359
+ const toolbarVisible = theme.ui.toolbar !== false;
29360
+ const panelVisible = theme.ui.panel !== false;
29361
+ const hintVisible = theme.ui.hint !== false;
29362
+ if (toolbar) {
29363
+ toolbar.dataset.hidden = String(!toolbarVisible);
29364
+ toolbar.hidden = !toolbarVisible;
29365
+ }
29366
+ if (panel) {
29367
+ panel.dataset.hidden = String(!panelVisible);
29368
+ panel.hidden = !panelVisible;
29369
+ }
29370
+ if (hint) {
29371
+ hint.dataset.hidden = String(!hintVisible);
29372
+ hint.hidden = !hintVisible;
29373
+ }
29331
29374
  this.shadowRoot.querySelectorAll(".crumbs").forEach((crumbs) => {
29332
29375
  crumbs.dataset.hidden = String(theme.ui.breadcrumbs === false);
29376
+ crumbs.hidden = theme.ui.breadcrumbs === false;
29333
29377
  });
29334
29378
  }
29335
29379
  async _loadInitial() {
@@ -29348,6 +29392,10 @@ var LiquidCarsAtlas = class extends HTMLElement {
29348
29392
  try {
29349
29393
  const data = typeof source === "string" ? await fetch(source).then((response) => response.json()) : source;
29350
29394
  this.modelData = normalizeModel(data);
29395
+ if (this._pendingPalette) {
29396
+ this.modelData.palette = { ...this.modelData.palette, ...this._pendingPalette };
29397
+ this._pendingPalette = null;
29398
+ }
29351
29399
  this.theme = this.modelData.theme;
29352
29400
  this._applyTheme();
29353
29401
  this._build();
@@ -29405,6 +29453,14 @@ var LiquidCarsAtlas = class extends HTMLElement {
29405
29453
  this.nodes.clear();
29406
29454
  this.relationGroup.clear();
29407
29455
  }
29456
+ _recolorNodes() {
29457
+ if (!this.modelData) return;
29458
+ for (const node of this.nodes.values()) {
29459
+ const color = this.modelData.palette[node.c] || node.c || this.modelData.palette.component;
29460
+ if (node.material) node.material.color.set(color);
29461
+ if (node.edge?.material) node.edge.material.color.set(color);
29462
+ }
29463
+ }
29408
29464
  _build() {
29409
29465
  if (!this._ensureRenderer()) return;
29410
29466
  this._clearObjects();
@@ -29416,6 +29472,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
29416
29472
  const geometryKind = entity.geometry || "box";
29417
29473
  const baked = geometryKind === "box" && Number(entity.radius) > 0;
29418
29474
  const geometry = makeGeometry(geometryKind, entity.radius, entity.segments, entity.s);
29475
+ if (!baked) normalizeGeometry(geometry);
29419
29476
  const color = this.modelData.palette[entity.c] || entity.c || this.modelData.palette.component;
29420
29477
  node.material = new MeshStandardMaterial({ color, roughness: 0.72, metalness: 0.06, transparent: true, opacity: entity.type === "shell" ? 0.96 : 1 });
29421
29478
  node.mesh = new Mesh(geometry, node.material);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liquidcars/atlas",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Reusable 3D conceptual atlas Web Component for LiquidCars-style models",
5
5
  "type": "module",
6
6
  "main": "./dist/liquidcars-atlas.js",