@liquidcars/atlas 0.1.10 → 0.1.12

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,11 +76,13 @@ 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
 
82
- 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. 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:
85
+ 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:
83
86
 
84
87
  ```bash
85
88
  npm publish --access public
@@ -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.12` 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, desacopla la paleta por defecto de nombres de dominio y añade actualización programática de la paleta con `setPalette`.
package/demo/index.html CHANGED
@@ -14,12 +14,12 @@
14
14
  liquidcars-atlas { display: block; width: 100%; max-width: 1480px; margin: 0 auto; }
15
15
  code { color: #85e5d3; }
16
16
  </style>
17
- <script type="module" src="../dist/liquidcars-atlas.js?v=0.1.10"></script>
17
+ <script type="module" src="../dist/liquidcars-atlas.js?v=0.1.12"></script>
18
18
  </head>
19
19
  <body>
20
20
  <header>
21
21
  <h1>LiquidCars Atlas</h1>
22
- <p>Prueba de distribución del componente <code>&lt;liquidcars-atlas&gt;</code> · v0.1.10</p>
22
+ <p>Prueba de distribución del componente <code>&lt;liquidcars-atlas&gt;</code> · v0.1.12</p>
23
23
  </header>
24
24
  <main>
25
25
  <liquidcars-atlas id="atlas" height="720" relation-mode="selected" shell-labels="on"></liquidcars-atlas>
@@ -1,4 +1,4 @@
1
- /*! LiquidCars Atlas 0.1.10 | MIT */
1
+ /*! LiquidCars Atlas 0.1.12 | MIT */
2
2
 
3
3
  // src/three.core.js
4
4
  var REVISION = "180";
@@ -28993,15 +28993,7 @@ var WebGLRenderer = class {
28993
28993
 
28994
28994
  // src/liquidcars-atlas.js
28995
28995
  var DEFAULT_PALETTE = {
28996
- offers: "#48c8b0",
28997
- ole: "#a995f1",
28998
- agreements: "#edb36f",
28999
- channels: "#60b5e6",
29000
- buyer: "#9cc95e",
29001
- dealer: "#48c8b0",
29002
- serviceProvider: "#ed8f98",
29003
28996
  component: "#8ae4d0",
29004
- channelOffer: "#d2c4ff",
29005
28997
  connection: "#f4edba"
29006
28998
  };
29007
28999
  var DEFAULT_THEME = {
@@ -29029,7 +29021,7 @@ function normalizeTheme(value) {
29029
29021
  }
29030
29022
  var CSS = [
29031
29023
  ":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}}"
29024
+ "*{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
29025
  ].join("");
29034
29026
  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
29027
  function escapeHtml(value) {
@@ -29117,11 +29109,15 @@ function makeGeometry(kind, radius = 0, segments = 4, size = [1, 1, 1]) {
29117
29109
  case "frustum-cone":
29118
29110
  case "truncated-cone":
29119
29111
  return r ? roundedFrustum(0.28, 0.5, r, s) : new CylinderGeometry(0.28, 0.5, 1, 32);
29112
+ // A frustum is a solid with two finite parallel faces. ConeGeometry
29113
+ // leaves the narrow face collapsed to an apex, which made both truncated
29114
+ // pyramid variants look like ordinary pyramids. CylinderGeometry with
29115
+ // three/four radial segments gives us a real triangular/square frustum.
29120
29116
  case "frustum-pyramid":
29121
29117
  case "truncated-pyramid":
29122
- return softenPolyhedron(new ConeGeometry(0.24, 0.5, 4), r);
29118
+ return new CylinderGeometry(0.28, 0.5, 1, 4);
29123
29119
  case "frustum-triangle":
29124
- return softenPolyhedron(new ConeGeometry(0.24, 0.5, 3), r);
29120
+ return new CylinderGeometry(0.28, 0.5, 1, 3);
29125
29121
  case "prism":
29126
29122
  return r ? roundedPrism(6, r, s) : new CylinderGeometry(0.5, 0.5, 1, 6);
29127
29123
  case "pyramid-square":
@@ -29140,6 +29136,15 @@ function makeGeometry(kind, radius = 0, segments = 4, size = [1, 1, 1]) {
29140
29136
  return r ? roundedBox(r, s, size) : new BoxGeometry(1, 1, 1);
29141
29137
  }
29142
29138
  }
29139
+ function normalizeGeometry(geometry) {
29140
+ geometry.computeBoundingBox();
29141
+ const bounds = geometry.boundingBox;
29142
+ const size = bounds?.getSize(new Vector3());
29143
+ if (!size || !size.x || !size.y || !size.z) return geometry;
29144
+ geometry.scale(1 / size.x, 1 / size.y, 1 / size.z);
29145
+ geometry.computeBoundingBox();
29146
+ return geometry;
29147
+ }
29143
29148
  function makeLabel(text, color, dimensions, type = "") {
29144
29149
  const bitmap = document.createElement("canvas");
29145
29150
  const context = bitmap.getContext("2d");
@@ -29280,6 +29285,24 @@ var LiquidCarsAtlas = class extends HTMLElement {
29280
29285
  set model(value) {
29281
29286
  this.load(value);
29282
29287
  }
29288
+ get palette() {
29289
+ return this.modelData?.palette || { ...DEFAULT_PALETTE };
29290
+ }
29291
+ set palette(value) {
29292
+ this.setPalette(value);
29293
+ }
29294
+ setPalette(value = {}) {
29295
+ if (!value || typeof value !== "object") return this;
29296
+ if (!this.modelData) {
29297
+ this._pendingPalette = { ...this._pendingPalette || {}, ...value };
29298
+ return this;
29299
+ }
29300
+ this.modelData.palette = { ...this.modelData.palette, ...value };
29301
+ this._recolorNodes();
29302
+ this._rebuildRelations();
29303
+ this.invalidate();
29304
+ return this;
29305
+ }
29283
29306
  get styles() {
29284
29307
  return this.theme;
29285
29308
  }
@@ -29325,11 +29348,25 @@ var LiquidCarsAtlas = class extends HTMLElement {
29325
29348
  const panel = this.shadowRoot.querySelector(".panel");
29326
29349
  const hint = this.shadowRoot.querySelector(".hint");
29327
29350
  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);
29351
+ const toolbarVisible = theme.ui.toolbar !== false;
29352
+ const panelVisible = theme.ui.panel !== false;
29353
+ const hintVisible = theme.ui.hint !== false;
29354
+ if (toolbar) {
29355
+ toolbar.dataset.hidden = String(!toolbarVisible);
29356
+ toolbar.hidden = !toolbarVisible;
29357
+ toolbar.style.display = toolbarVisible ? "flex" : "none";
29358
+ }
29359
+ if (panel) {
29360
+ panel.dataset.hidden = String(!panelVisible);
29361
+ panel.hidden = !panelVisible;
29362
+ }
29363
+ if (hint) {
29364
+ hint.dataset.hidden = String(!hintVisible);
29365
+ hint.hidden = !hintVisible;
29366
+ }
29331
29367
  this.shadowRoot.querySelectorAll(".crumbs").forEach((crumbs) => {
29332
29368
  crumbs.dataset.hidden = String(theme.ui.breadcrumbs === false);
29369
+ crumbs.hidden = theme.ui.breadcrumbs === false;
29333
29370
  });
29334
29371
  }
29335
29372
  async _loadInitial() {
@@ -29348,6 +29385,10 @@ var LiquidCarsAtlas = class extends HTMLElement {
29348
29385
  try {
29349
29386
  const data = typeof source === "string" ? await fetch(source).then((response) => response.json()) : source;
29350
29387
  this.modelData = normalizeModel(data);
29388
+ if (this._pendingPalette) {
29389
+ this.modelData.palette = { ...this.modelData.palette, ...this._pendingPalette };
29390
+ this._pendingPalette = null;
29391
+ }
29351
29392
  this.theme = this.modelData.theme;
29352
29393
  this._applyTheme();
29353
29394
  this._build();
@@ -29405,6 +29446,14 @@ var LiquidCarsAtlas = class extends HTMLElement {
29405
29446
  this.nodes.clear();
29406
29447
  this.relationGroup.clear();
29407
29448
  }
29449
+ _recolorNodes() {
29450
+ if (!this.modelData) return;
29451
+ for (const node of this.nodes.values()) {
29452
+ const color = this.modelData.palette[node.c] || node.c || this.modelData.palette.component;
29453
+ if (node.material) node.material.color.set(color);
29454
+ if (node.edge?.material) node.edge.material.color.set(color);
29455
+ }
29456
+ }
29408
29457
  _build() {
29409
29458
  if (!this._ensureRenderer()) return;
29410
29459
  this._clearObjects();
@@ -29416,6 +29465,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
29416
29465
  const geometryKind = entity.geometry || "box";
29417
29466
  const baked = geometryKind === "box" && Number(entity.radius) > 0;
29418
29467
  const geometry = makeGeometry(geometryKind, entity.radius, entity.segments, entity.s);
29468
+ if (!baked) normalizeGeometry(geometry);
29419
29469
  const color = this.modelData.palette[entity.c] || entity.c || this.modelData.palette.component;
29420
29470
  node.material = new MeshStandardMaterial({ color, roughness: 0.72, metalness: 0.06, transparent: true, opacity: entity.type === "shell" ? 0.96 : 1 });
29421
29471
  node.mesh = new Mesh(geometry, node.material);
package/index.html CHANGED
@@ -14,12 +14,12 @@
14
14
  liquidcars-atlas { display: block; width: 100%; max-width: 1480px; margin: 0 auto; }
15
15
  code { color: #85e5d3; }
16
16
  </style>
17
- <script type="module" src="./dist/liquidcars-atlas.js?v=0.1.10"></script>
17
+ <script type="module" src="./dist/liquidcars-atlas.js?v=0.1.12"></script>
18
18
  </head>
19
19
  <body>
20
20
  <header>
21
21
  <h1>LiquidCars Atlas</h1>
22
- <p>Prueba de distribución del componente <code>&lt;liquidcars-atlas&gt;</code> · v0.1.10</p>
22
+ <p>Prueba de distribución del componente <code>&lt;liquidcars-atlas&gt;</code> · v0.1.12</p>
23
23
  </header>
24
24
  <main>
25
25
  <liquidcars-atlas id="atlas" height="720" relation-mode="selected" shell-labels="on"></liquidcars-atlas>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liquidcars/atlas",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Reusable 3D conceptual atlas Web Component for LiquidCars-style models",
5
5
  "type": "module",
6
6
  "main": "./dist/liquidcars-atlas.js",