@lit-roa/custom-header 0.1.4 → 0.2.0

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.
@@ -4,16 +4,88 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { LitElement, css, html } from 'lit';
8
- import { customElement, property } from 'lit/decorators.js';
9
- import { estilosBase, iconoMenu2, tokens, iconoUser, iconoShoppingCart } from '@lit-roa/base';
7
+ import { LitElement, css, html, nothing } from 'lit';
8
+ import { customElement, property, query, state } from 'lit/decorators.js';
9
+ import { emitir, estilosBase, iconoChevronDown, iconoMenu2, iconoShoppingBag, iconoUser, iconoX, tokens, } from '@lit-roa/base';
10
+ import '@lit-roa/buscador';
10
11
  /**
11
- * TODO: describe el componente.
12
+ * Encabezado de la tienda: logo, ligas con submenús o mega menú, buscador,
13
+ * cuenta y carrito. En pantallas angostas muestra un menú lateral. Puede
14
+ * quedarse pegado arriba y hacerse más bajo al hacer scroll.
15
+ *
16
+ * Las ligas no navegan solas: el sitio escucha `navegar` y decide.
17
+ *
18
+ * @fires navegar - Al tocar una liga, el logo o una promoción. `detail`: `{ liga }`.
19
+ * @fires abrir-carrito - Al tocar el carrito.
20
+ * @fires abrir-cuenta - Al tocar "Entrar" o el nombre de la persona.
21
+ * @fires buscar-texto - (del buscador) Al escribir. `detail`: `{ texto }`.
22
+ * @fires buscar - (del buscador) Al presionar Enter. `detail`: `{ texto }`.
23
+ * @fires elegir-sugerencia - (del buscador) `detail`: `{ sugerencia }`.
24
+ * @slot anuncio - Arriba de todo, por ejemplo `<lr-barra-anuncio>`.
25
+ * @slot carrito - Reemplaza el botón del carrito (por ejemplo con `<lr-mini-carrito>`).
26
+ * @slot menu-pie - Contenido extra abajo del menú de celular.
12
27
  */
13
28
  let LrCustomHeader = class LrCustomHeader extends LitElement {
14
29
  constructor() {
15
30
  super(...arguments);
16
- this.titulo = 'lr-custom-header';
31
+ /** URL de la imagen del logo. */
32
+ this.logo = '';
33
+ /** Nombre de la tienda (texto junto al logo y texto alternativo). */
34
+ this.nombre = '';
35
+ /** Liga del logo. */
36
+ this.hrefInicio = '/';
37
+ /** Ligas del menú. Como atributo, en JSON. */
38
+ this.ligas = [];
39
+ /** Piezas en el carrito (globito). */
40
+ this.piezas = 0;
41
+ /** Nombre de la persona con sesión iniciada; vacío muestra "Entrar". */
42
+ this.usuario = '';
43
+ this.textoEntrar = 'Entrar';
44
+ this.textoCuenta = 'Mi cuenta y pedidos';
45
+ /** Oculta el buscador. */
46
+ this.sinBuscador = false;
47
+ this.placeholderBuscador = 'Buscar en la tienda';
48
+ /** Sugerencias para el buscador (ver `lr-buscador`). */
49
+ this.sugerencias = [];
50
+ this.buscando = false;
51
+ /** Se queda pegado arriba y se hace más bajo al hacer scroll. */
52
+ this.pegajoso = false;
53
+ /** Con `pegajoso`, se esconde al bajar y aparece al subir. */
54
+ this.ocultarAlBajar = false;
55
+ /** Ancho (px) del header debajo del cual se usa la vista de celular. */
56
+ this.anchoCelular = 900;
57
+ this.compacto = false;
58
+ this.escondido = false;
59
+ this.celular = false;
60
+ this.abierta = -1;
61
+ this.abiertasMenu = new Set();
62
+ this.conAnuncio = false;
63
+ this.ultimoScroll = 0;
64
+ this.alCuenta = () => {
65
+ this.cerrarMenu();
66
+ emitir(this, 'abrir-cuenta');
67
+ };
68
+ this.alCarrito = () => {
69
+ this.cerrarMenu();
70
+ emitir(this, 'abrir-carrito');
71
+ };
72
+ this.alClicFuera = (e) => {
73
+ if (this.abierta >= 0 && !e.composedPath().includes(this))
74
+ this.abierta = -1;
75
+ };
76
+ this.alEscape = (e) => {
77
+ if (e.key === 'Escape' && this.abierta >= 0)
78
+ this.abierta = -1;
79
+ };
80
+ this.alScroll = () => {
81
+ if (!this.pegajoso)
82
+ return;
83
+ const y = window.scrollY;
84
+ this.compacto = y > 40;
85
+ if (this.ocultarAlBajar)
86
+ this.escondido = y > 200 && y > this.ultimoScroll && this.abierta < 0;
87
+ this.ultimoScroll = y;
88
+ };
17
89
  }
18
90
  static { this.styles = [
19
91
  tokens,
@@ -21,56 +93,693 @@ let LrCustomHeader = class LrCustomHeader extends LitElement {
21
93
  css `
22
94
  :host {
23
95
  display: block;
96
+ --_fondo: var(--lr-header-fondo, #292929);
97
+ --_texto: var(--lr-header-texto, #ffffff);
98
+ --_alto: var(--lr-header-alto, 4.75rem);
99
+ --_alto-compacto: var(--lr-header-alto-compacto, 3.5rem);
100
+ position: relative;
101
+ z-index: var(--lr-header-capa, 50);
102
+ }
103
+ :host([pegajoso]) {
104
+ position: sticky;
105
+ top: 0;
106
+ transition: transform 0.25s ease;
107
+ }
108
+ :host([escondido]) {
109
+ transform: translateY(-100%);
110
+ }
111
+ header {
112
+ background: var(--_fondo);
113
+ color: var(--_texto);
114
+ transition: box-shadow 0.2s;
115
+ }
116
+ :host([compacto]) header {
117
+ box-shadow: 0 4px 14px rgb(0 0 0 / 0.25);
118
+ }
119
+ :host([compacto]) .anuncio {
120
+ display: none;
121
+ }
122
+ .fila {
123
+ display: flex;
124
+ align-items: center;
125
+ gap: 1.5rem;
126
+ max-width: var(--lr-header-ancho, 1200px);
127
+ height: var(--_alto);
128
+ margin: 0 auto;
129
+ padding: 0 1.5rem;
130
+ transition: height 0.2s;
131
+ }
132
+ :host([compacto]) .fila {
133
+ height: var(--_alto-compacto);
134
+ }
135
+ button,
136
+ a {
137
+ font: inherit;
138
+ color: inherit;
139
+ }
140
+ button {
141
+ all: unset;
142
+ box-sizing: border-box;
143
+ cursor: pointer;
144
+ }
145
+ a {
146
+ text-decoration: none;
147
+ }
148
+ button:focus-visible,
149
+ a:focus-visible {
150
+ outline: 2px solid currentColor;
151
+ outline-offset: 2px;
152
+ border-radius: 8px;
153
+ }
154
+ .logo {
155
+ display: flex;
156
+ align-items: center;
157
+ gap: 0.625rem;
158
+ flex: none;
159
+ font-weight: 800;
160
+ font-size: 1.1875rem;
161
+ white-space: nowrap;
162
+ }
163
+ .logo img {
164
+ height: 2.5rem;
165
+ width: auto;
166
+ max-width: 10rem;
167
+ object-fit: contain;
168
+ transition: height 0.2s;
169
+ }
170
+ :host([compacto]) .logo img {
171
+ height: 2rem;
172
+ }
173
+ .fila nav > ul {
174
+ display: flex;
175
+ gap: 0.25rem;
176
+ margin: 0;
177
+ padding: 0;
178
+ list-style: none;
179
+ }
180
+ .liga {
181
+ display: flex;
182
+ align-items: center;
183
+ gap: 0.25rem;
184
+ padding: 0.5rem 0.75rem;
185
+ border-radius: 8px;
186
+ font-weight: 600;
187
+ font-size: 0.9375rem;
188
+ white-space: nowrap;
189
+ opacity: 0.9;
190
+ }
191
+ .liga:hover,
192
+ .liga[aria-expanded='true'] {
193
+ opacity: 1;
194
+ background: rgb(255 255 255 / 0.1);
195
+ }
196
+ .liga svg {
197
+ transition: transform 0.2s;
198
+ }
199
+ .liga[aria-expanded='true'] svg {
200
+ transform: rotate(180deg);
201
+ }
202
+ li {
203
+ position: relative;
204
+ }
205
+ .submenu {
206
+ position: absolute;
207
+ top: calc(100% + 0.5rem);
208
+ left: 0;
209
+ min-width: 13rem;
210
+ margin: 0;
211
+ padding: 0.5rem;
212
+ list-style: none;
213
+ background: #fff;
214
+ color: #111827;
215
+ border-radius: 12px;
216
+ box-shadow: 0 18px 30px rgb(0 0 0 / 0.18);
217
+ }
218
+ .submenu a,
219
+ .mega a {
220
+ display: block;
221
+ padding: 0.5rem 0.75rem;
222
+ border-radius: 8px;
223
+ }
224
+ .submenu a:hover,
225
+ .mega a:hover {
226
+ background: #f3f4f6;
227
+ }
228
+ .mega {
229
+ position: absolute;
230
+ left: 0;
231
+ right: 0;
232
+ top: 100%;
233
+ background: #fff;
234
+ color: #111827;
235
+ box-shadow: 0 18px 30px rgb(0 0 0 / 0.15);
236
+ }
237
+ .mega .dentro {
238
+ display: grid;
239
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
240
+ gap: 2rem;
241
+ max-width: var(--lr-header-ancho, 1200px);
242
+ margin: 0 auto;
243
+ padding: 1.75rem 1.5rem;
244
+ }
245
+ .mega .dentro.con-promo {
246
+ grid-template-columns: repeat(var(--_columnas), minmax(9rem, 1fr)) minmax(14rem, 1.3fr);
247
+ }
248
+ .mega h3 {
249
+ margin: 0 0 0.375rem 0.75rem;
250
+ font-size: 0.75rem;
251
+ text-transform: uppercase;
252
+ letter-spacing: 0.06em;
253
+ color: #6b7280;
254
+ }
255
+ .mega ul {
256
+ margin: 0;
257
+ padding: 0;
258
+ list-style: none;
259
+ }
260
+ .mega .promo {
261
+ display: flex;
262
+ flex-direction: column;
263
+ justify-content: flex-end;
264
+ gap: 0.25rem;
265
+ min-height: 10rem;
266
+ padding: 1.25rem;
267
+ border-radius: 12px;
268
+ background: var(--lr-header-promo-fondo, linear-gradient(135deg, #7c2d12, #c2410c)) center / cover;
269
+ color: #fff;
270
+ }
271
+ .promo b {
272
+ font-size: 1.1875rem;
273
+ }
274
+ .promo span {
275
+ font-size: 0.8125rem;
276
+ opacity: 0.9;
277
+ }
278
+ lr-buscador {
279
+ flex: 1;
280
+ min-width: 9rem;
281
+ max-width: 22rem;
282
+ margin-left: auto;
283
+ --lr-color-texto: #111827;
284
+ --lr-buscador-borde: transparent;
285
+ }
286
+ .acciones {
287
+ display: flex;
288
+ align-items: center;
289
+ gap: 0.375rem;
290
+ flex: none;
291
+ }
292
+ .solo-acciones {
293
+ margin-left: auto;
294
+ }
295
+ .icono {
296
+ position: relative;
297
+ display: grid;
298
+ place-items: center;
299
+ width: 2.625rem;
300
+ height: 2.625rem;
301
+ border-radius: 999px;
302
+ font-size: 1.375rem;
303
+ }
304
+ .icono:hover,
305
+ .cuenta:hover {
306
+ background: rgb(255 255 255 / 0.1);
307
+ }
308
+ .cuenta {
309
+ display: flex;
310
+ align-items: center;
311
+ gap: 0.5rem;
312
+ height: 2.625rem;
313
+ padding: 0 0.75rem 0 0.5rem;
314
+ border-radius: 999px;
315
+ font-size: 0.875rem;
316
+ font-weight: 600;
317
+ }
318
+ .nombre-usuario {
319
+ max-width: 9rem;
320
+ overflow: hidden;
321
+ text-overflow: ellipsis;
322
+ white-space: nowrap;
323
+ }
324
+ .cuenta svg {
325
+ font-size: 1.25rem;
326
+ }
327
+ .avatar {
328
+ display: grid;
329
+ place-items: center;
330
+ width: 1.875rem;
331
+ height: 1.875rem;
332
+ border-radius: 999px;
333
+ background: var(--lr-header-avatar, #fbbf24);
334
+ color: #292929;
335
+ font-size: 0.75rem;
336
+ font-weight: 800;
337
+ }
338
+ .globo {
339
+ position: absolute;
340
+ top: 0.1875rem;
341
+ right: 0;
342
+ min-width: 1.1875rem;
343
+ height: 1.1875rem;
344
+ padding: 0 0.3125rem;
345
+ border-radius: 999px;
346
+ border: 2px solid var(--_fondo);
347
+ background: var(--lr-header-globo, #ea580c);
348
+ color: #fff;
349
+ font-size: 0.6875rem;
350
+ font-weight: 800;
351
+ line-height: 0.8rem;
352
+ text-align: center;
353
+ }
354
+ .solo-lectores {
355
+ position: absolute;
356
+ width: 1px;
357
+ height: 1px;
358
+ overflow: hidden;
359
+ clip: rect(0 0 0 0);
24
360
  }
25
361
 
26
- .bg-header {
27
- background-color: #292929;
28
- color: white;
29
- padding: var(--_espacio);
30
- height: 80px;
362
+ /* Celular */
363
+ .celular .fila {
364
+ gap: 0.25rem;
365
+ padding: 0 0.5rem;
366
+ height: 3.75rem;
367
+ }
368
+ :host([compacto]) .celular .fila {
369
+ height: 3.25rem;
370
+ }
371
+ .celular .logo {
372
+ flex: 1;
373
+ justify-content: center;
374
+ font-size: 1.0625rem;
375
+ min-width: 0;
376
+ }
377
+ .celular .logo img {
378
+ height: 2rem;
379
+ }
380
+ .celular .acciones {
381
+ gap: 0;
382
+ }
383
+ .celular .barra-buscar {
384
+ padding: 0 0.75rem 0.75rem;
385
+ }
386
+ :host([compacto]) .celular .barra-buscar {
387
+ display: none;
388
+ }
389
+ .celular lr-buscador {
390
+ max-width: none;
391
+ margin: 0;
31
392
  }
32
393
 
33
- .d-flex {
394
+ dialog {
395
+ position: fixed;
396
+ inset: 0 auto 0 0;
397
+ width: min(86vw, 21rem);
398
+ max-width: none;
399
+ height: 100%;
400
+ max-height: none;
401
+ margin: 0;
402
+ padding: 0;
403
+ border: 0;
404
+ background: #fff;
405
+ color: #111827;
406
+ display: flex;
407
+ flex-direction: column;
408
+ animation: entrar 0.25s ease-out;
409
+ }
410
+ dialog:not([open]) {
411
+ display: none;
412
+ }
413
+ dialog::backdrop {
414
+ background: rgb(0 0 0 / 0.45);
415
+ }
416
+ @keyframes entrar {
417
+ from {
418
+ transform: translateX(-100%);
419
+ }
420
+ }
421
+ .cab {
34
422
  display: flex;
35
423
  align-items: center;
36
424
  justify-content: space-between;
425
+ gap: 0.5rem;
426
+ padding: 0.75rem 0.75rem 0.75rem 1.125rem;
427
+ border-bottom: 1px solid #e5e7eb;
37
428
  }
38
-
39
- .me-2 {
40
- margin-right: var(--_espacio);
429
+ .cab .logo {
430
+ flex: initial;
41
431
  }
42
-
43
- .bg-header svg {
44
- font-size: 1.5rem;
432
+ .cab .icono:hover {
433
+ background: #f3f4f6;
45
434
  }
46
-
47
- .btn {
48
- background-color: transparent;
49
- border: none;
50
- color: white;
51
- cursor: pointer;
435
+ .menu {
436
+ flex: 1;
437
+ overflow-y: auto;
438
+ margin: 0;
439
+ padding: 0.25rem 0;
440
+ list-style: none;
441
+ }
442
+ .menu > li > a,
443
+ .menu > li > button {
444
+ display: flex;
445
+ justify-content: space-between;
446
+ align-items: center;
447
+ width: 100%;
448
+ padding: 0.875rem 1.125rem;
449
+ border-bottom: 1px solid #f3f4f6;
450
+ font-weight: 600;
451
+ }
452
+ .menu button svg {
453
+ transition: transform 0.2s;
454
+ }
455
+ .menu button[aria-expanded='true'] svg {
456
+ transform: rotate(180deg);
457
+ }
458
+ .menu ul {
459
+ margin: 0;
460
+ padding: 0.25rem 0;
461
+ list-style: none;
462
+ }
463
+ .menu ul a {
464
+ display: block;
465
+ padding: 0.6875rem 1.125rem 0.6875rem 2.125rem;
466
+ color: #374151;
467
+ }
468
+ .menu ul .grupo {
469
+ padding: 0.75rem 1.125rem 0.25rem 2.125rem;
470
+ font-size: 0.75rem;
471
+ font-weight: 700;
472
+ text-transform: uppercase;
473
+ letter-spacing: 0.05em;
474
+ color: #6b7280;
475
+ }
476
+ .pie {
477
+ display: grid;
478
+ gap: 0.75rem;
479
+ padding: 1rem 1.125rem;
480
+ border-top: 1px solid #e5e7eb;
481
+ }
482
+ .boton-cuenta {
483
+ display: flex;
484
+ justify-content: center;
485
+ align-items: center;
486
+ gap: 0.5rem;
487
+ height: 2.875rem;
488
+ border-radius: 10px;
489
+ background: var(--_fondo);
490
+ color: var(--_texto);
491
+ font-weight: 700;
492
+ }
493
+ @media (prefers-reduced-motion: reduce) {
494
+ :host([pegajoso]),
495
+ .fila,
496
+ dialog {
497
+ transition: none;
498
+ animation: none;
499
+ }
52
500
  }
53
501
  `,
54
502
  ]; }
503
+ connectedCallback() {
504
+ super.connectedCallback();
505
+ this.observador = new ResizeObserver(([e]) => (this.celular = e.contentRect.width < this.anchoCelular));
506
+ this.observador.observe(this);
507
+ window.addEventListener('scroll', this.alScroll, { passive: true });
508
+ document.addEventListener('click', this.alClicFuera);
509
+ document.addEventListener('keydown', this.alEscape);
510
+ }
511
+ disconnectedCallback() {
512
+ super.disconnectedCallback();
513
+ this.observador?.disconnect();
514
+ window.removeEventListener('scroll', this.alScroll);
515
+ document.removeEventListener('click', this.alClicFuera);
516
+ document.removeEventListener('keydown', this.alEscape);
517
+ }
55
518
  render() {
56
- return html `
57
- <div class="bg-header d-flex">
58
- <span class="btn">${iconoMenu2}</span>
59
- <div class="d-flex">
60
- <button class="btn me-2">Productos</button>
61
- <span class="btn me-2">${iconoUser}</span>
62
- <div class="btn d-flex">
63
- <span>${iconoShoppingCart}</span>
64
- <span>0</span>
65
- </div>
519
+ return html `<header class=${this.celular ? 'celular' : ''} part="header">
520
+ <div class="anuncio" ?hidden=${!this.conAnuncio}>
521
+ <slot name="anuncio" @slotchange=${this.alCambiarAnuncio}></slot>
522
+ </div>
523
+ ${this.celular ? this.vistaCelular() : this.vistaEscritorio()}
524
+ </header>`;
525
+ }
526
+ /** Abre el menú de celular. */
527
+ abrirMenu() {
528
+ this.dialogo?.showModal();
529
+ }
530
+ /** Cierra el menú de celular y los submenús. */
531
+ cerrarMenu() {
532
+ this.dialogo?.close();
533
+ this.abierta = -1;
534
+ }
535
+ vistaEscritorio() {
536
+ const liga = this.ligas[this.abierta];
537
+ return html `<div class="fila">
538
+ ${this.logoTpl()}
539
+ ${this.ligas.length
540
+ ? html `<nav aria-label="Principal">
541
+ <ul>
542
+ ${this.ligas.map((l, i) => this.ligaEscritorio(l, i))}
543
+ </ul>
544
+ </nav>`
545
+ : nothing}
546
+ ${this.buscadorTpl()}
547
+ <div class="acciones ${this.sinBuscador ? 'solo-acciones' : ''}">${this.cuentaTpl(true)} ${this.carritoTpl()}</div>
548
+ </div>
549
+ ${liga?.columnas?.length ? this.megaTpl(liga) : nothing}`;
550
+ }
551
+ vistaCelular() {
552
+ return html `<div class="fila">
553
+ <button class="icono" aria-label="Abrir menú" aria-haspopup="dialog" @click=${this.abrirMenu}>${iconoMenu2}</button>
554
+ ${this.logoTpl()}
555
+ <div class="acciones">${this.cuentaTpl(false)} ${this.carritoTpl()}</div>
556
+ </div>
557
+ ${this.sinBuscador ? nothing : html `<div class="barra-buscar">${this.buscadorTpl()}</div>`}
558
+ <dialog aria-label="Menú" @click=${this.alClicDialogo} @close=${() => (this.abierta = -1)}>
559
+ <div class="cab">
560
+ ${this.logoTpl()}
561
+ <button class="icono" aria-label="Cerrar menú" @click=${this.cerrarMenu}>${iconoX}</button>
562
+ </div>
563
+ <nav aria-label="Principal" style="display:contents">
564
+ <ul class="menu">
565
+ ${this.ligas.map((l, i) => this.ligaCelular(l, i))}
566
+ </ul>
567
+ </nav>
568
+ <div class="pie">
569
+ <slot name="menu-pie"></slot>
570
+ <button class="boton-cuenta" @click=${this.alCuenta}>
571
+ ${this.usuario ? html `${iconoUser} ${this.textoCuenta}` : this.textoEntrar}
572
+ </button>
66
573
  </div>
574
+ </dialog>`;
575
+ }
576
+ logoTpl() {
577
+ const inicio = { texto: this.nombre || 'Inicio', href: this.hrefInicio, id: 'inicio' };
578
+ return html `<a class="logo" href=${this.hrefInicio} part="logo" @click=${(e) => this.alNavegar(e, inicio)}>
579
+ ${this.logo ? html `<img src=${this.logo} alt=${this.nombre ? '' : 'Inicio'} />` : nothing}
580
+ ${this.nombre ? html `<span>${this.nombre}</span>` : nothing}
581
+ </a>`;
582
+ }
583
+ buscadorTpl() {
584
+ if (this.sinBuscador)
585
+ return nothing;
586
+ return html `<lr-buscador
587
+ placeholder=${this.placeholderBuscador}
588
+ .sugerencias=${this.sugerencias}
589
+ ?cargando=${this.buscando}
590
+ ver-todo
591
+ ></lr-buscador>`;
592
+ }
593
+ cuentaTpl(conTexto) {
594
+ if (!conTexto) {
595
+ return html `<button class="icono" aria-label=${this.usuario ? `${this.textoCuenta}: ${this.usuario}` : this.textoEntrar} @click=${this.alCuenta}>
596
+ ${this.usuario ? html `<span class="avatar">${this.iniciales()}</span>` : iconoUser}
597
+ </button>`;
598
+ }
599
+ return html `<button class="cuenta" @click=${this.alCuenta}>
600
+ ${this.usuario
601
+ ? html `<span class="avatar" aria-hidden="true">${this.iniciales()}</span><span class="solo-lectores">${this.textoCuenta}:</span><span class="nombre-usuario">${this.usuario.split(/\s+/)[0]}</span>`
602
+ : html `${iconoUser}${this.textoEntrar}`}
603
+ </button>`;
604
+ }
605
+ carritoTpl() {
606
+ return html `<slot name="carrito">
607
+ <button class="icono" aria-label="Carrito, ${this.piezas} ${this.piezas === 1 ? 'pieza' : 'piezas'}" @click=${this.alCarrito}>
608
+ ${iconoShoppingBag}${this.piezas > 0 ? html `<span class="globo" aria-hidden="true">${this.piezas > 99 ? '99+' : this.piezas}</span>` : nothing}
609
+ </button>
610
+ </slot>`;
611
+ }
612
+ ligaEscritorio(liga, i) {
613
+ const conSub = !!(liga.columnas?.length || liga.hijos?.length);
614
+ if (!conSub) {
615
+ return html `<li><a class="liga" href=${liga.href || '#'} @click=${(e) => this.alNavegar(e, liga)}>${liga.texto}</a></li>`;
616
+ }
617
+ const abierta = this.abierta === i;
618
+ return html `<li @mouseenter=${() => (this.abierta = i)} @mouseleave=${() => this.alSalirLiga(i)}>
619
+ <button class="liga" aria-expanded=${abierta} @click=${(e) => this.alClicLiga(e, i)}>${liga.texto} ${iconoChevronDown}</button>
620
+ ${abierta && !liga.columnas?.length
621
+ ? html `<ul class="submenu">
622
+ ${liga.hijos.map((h) => html `<li><a href=${h.href || '#'} @click=${(e) => this.alNavegar(e, h)}>${h.texto}</a></li>`)}
623
+ </ul>`
624
+ : nothing}
625
+ </li>`;
626
+ }
627
+ megaTpl(liga) {
628
+ const promo = liga.promo;
629
+ const indice = this.abierta;
630
+ return html `<div class="mega" @mouseenter=${() => (this.abierta = indice)} @mouseleave=${() => (this.abierta = -1)}>
631
+ <div class="dentro ${promo ? 'con-promo' : ''}" style="--_columnas: ${liga.columnas.length}">
632
+ ${liga.columnas.map((c) => html `<div>
633
+ <h3>${c.titulo}</h3>
634
+ <ul>
635
+ ${c.ligas.map((l) => html `<li><a href=${l.href || '#'} @click=${(e) => this.alNavegar(e, l)}>${l.texto}</a></li>`)}
636
+ </ul>
637
+ </div>`)}
638
+ ${promo
639
+ ? html `<a
640
+ class="promo"
641
+ href=${promo.href || '#'}
642
+ style=${promo.imagen ? `background-image: linear-gradient(0deg, rgb(0 0 0 / .55), rgb(0 0 0 / 0) 70%), url(${promo.imagen})` : ''}
643
+ @click=${(e) => this.alNavegar(e, { texto: promo.titulo, href: promo.href, id: promo.id })}
644
+ >
645
+ <b>${promo.titulo}</b>${promo.texto ? html `<span>${promo.texto}</span>` : nothing}
646
+ </a>`
647
+ : nothing}
67
648
  </div>
68
- `;
649
+ </div>`;
650
+ }
651
+ ligaCelular(liga, i) {
652
+ const hijos = liga.columnas?.length
653
+ ? liga.columnas.flatMap((c) => [{ grupo: c.titulo }, ...c.ligas])
654
+ : (liga.hijos ?? []);
655
+ if (!hijos.length) {
656
+ return html `<li><a href=${liga.href || '#'} @click=${(e) => this.alNavegar(e, liga)}>${liga.texto}</a></li>`;
657
+ }
658
+ const abierta = this.abiertasMenu.has(i);
659
+ return html `<li>
660
+ <button aria-expanded=${abierta} @click=${() => this.alternarMenu(i)}>${liga.texto} ${iconoChevronDown}</button>
661
+ ${abierta
662
+ ? html `<ul>
663
+ ${hijos.map((h) => 'grupo' in h
664
+ ? html `<li class="grupo" aria-hidden="true">${h.grupo}</li>`
665
+ : html `<li><a href=${h.href || '#'} @click=${(e) => this.alNavegar(e, h)}>${h.texto}</a></li>`)}
666
+ ${liga.href
667
+ ? html `<li><a href=${liga.href} @click=${(e) => this.alNavegar(e, liga)}>Ver todo ${liga.texto.toLowerCase()}</a></li>`
668
+ : nothing}
669
+ </ul>`
670
+ : nothing}
671
+ </li>`;
672
+ }
673
+ alternarMenu(i) {
674
+ const s = new Set(this.abiertasMenu);
675
+ if (s.has(i))
676
+ s.delete(i);
677
+ else
678
+ s.add(i);
679
+ this.abiertasMenu = s;
680
+ }
681
+ iniciales() {
682
+ return this.usuario
683
+ .split(/\s+/)
684
+ .filter(Boolean)
685
+ .slice(0, 2)
686
+ .map((p) => p[0].toUpperCase())
687
+ .join('');
688
+ }
689
+ alNavegar(evento, liga) {
690
+ if (liga.href && (evento.ctrlKey || evento.metaKey))
691
+ return;
692
+ evento.preventDefault();
693
+ this.cerrarMenu();
694
+ emitir(this, 'navegar', { liga });
695
+ }
696
+ alClicLiga(evento, i) {
697
+ // Con mouse el hover ya lo abrió: el clic no debe cerrarlo. Con teclado (detail 0) alterna.
698
+ if (evento.detail === 0)
699
+ this.abierta = this.abierta === i ? -1 : i;
700
+ else
701
+ this.abierta = i;
702
+ }
703
+ alSalirLiga(i) {
704
+ // El mega menú vive fuera del <li>; se cierra cuando el mouse sale de él.
705
+ if (!this.ligas[i]?.columnas?.length)
706
+ this.abierta = -1;
707
+ }
708
+ alCambiarAnuncio(e) {
709
+ this.conAnuncio = e.target.assignedElements().length > 0;
710
+ }
711
+ alClicDialogo(e) {
712
+ // Clic en el fondo (fuera del panel) cierra el menú.
713
+ if (e.target === this.dialogo)
714
+ this.cerrarMenu();
69
715
  }
70
716
  };
71
717
  __decorate([
72
718
  property()
73
- ], LrCustomHeader.prototype, "titulo", void 0);
719
+ ], LrCustomHeader.prototype, "logo", void 0);
720
+ __decorate([
721
+ property()
722
+ ], LrCustomHeader.prototype, "nombre", void 0);
723
+ __decorate([
724
+ property({ attribute: 'href-inicio' })
725
+ ], LrCustomHeader.prototype, "hrefInicio", void 0);
726
+ __decorate([
727
+ property({ type: Array })
728
+ ], LrCustomHeader.prototype, "ligas", void 0);
729
+ __decorate([
730
+ property({ type: Number })
731
+ ], LrCustomHeader.prototype, "piezas", void 0);
732
+ __decorate([
733
+ property()
734
+ ], LrCustomHeader.prototype, "usuario", void 0);
735
+ __decorate([
736
+ property({ attribute: 'texto-entrar' })
737
+ ], LrCustomHeader.prototype, "textoEntrar", void 0);
738
+ __decorate([
739
+ property({ attribute: 'texto-cuenta' })
740
+ ], LrCustomHeader.prototype, "textoCuenta", void 0);
741
+ __decorate([
742
+ property({ type: Boolean, attribute: 'sin-buscador' })
743
+ ], LrCustomHeader.prototype, "sinBuscador", void 0);
744
+ __decorate([
745
+ property({ attribute: 'placeholder-buscador' })
746
+ ], LrCustomHeader.prototype, "placeholderBuscador", void 0);
747
+ __decorate([
748
+ property({ type: Array })
749
+ ], LrCustomHeader.prototype, "sugerencias", void 0);
750
+ __decorate([
751
+ property({ type: Boolean, attribute: 'buscando' })
752
+ ], LrCustomHeader.prototype, "buscando", void 0);
753
+ __decorate([
754
+ property({ type: Boolean, reflect: true })
755
+ ], LrCustomHeader.prototype, "pegajoso", void 0);
756
+ __decorate([
757
+ property({ type: Boolean, attribute: 'ocultar-al-bajar' })
758
+ ], LrCustomHeader.prototype, "ocultarAlBajar", void 0);
759
+ __decorate([
760
+ property({ type: Number, attribute: 'ancho-celular' })
761
+ ], LrCustomHeader.prototype, "anchoCelular", void 0);
762
+ __decorate([
763
+ property({ type: Boolean, reflect: true })
764
+ ], LrCustomHeader.prototype, "compacto", void 0);
765
+ __decorate([
766
+ property({ type: Boolean, reflect: true })
767
+ ], LrCustomHeader.prototype, "escondido", void 0);
768
+ __decorate([
769
+ state()
770
+ ], LrCustomHeader.prototype, "celular", void 0);
771
+ __decorate([
772
+ state()
773
+ ], LrCustomHeader.prototype, "abierta", void 0);
774
+ __decorate([
775
+ state()
776
+ ], LrCustomHeader.prototype, "abiertasMenu", void 0);
777
+ __decorate([
778
+ state()
779
+ ], LrCustomHeader.prototype, "conAnuncio", void 0);
780
+ __decorate([
781
+ query('dialog')
782
+ ], LrCustomHeader.prototype, "dialogo", void 0);
74
783
  LrCustomHeader = __decorate([
75
784
  customElement('lr-custom-header')
76
785
  ], LrCustomHeader);