@lit-roa/linea-carrito 0.0.0-stage → 0.1.1

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
@@ -1,3 +1,56 @@
1
- # Temporary Holding Version
1
+ # @lit-roa/linea-carrito
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Una línea del carrito: imagen, nombre, variante, precio (con descuento
4
+ tachado), selector de cantidad y botón para quitar. Usa
5
+ [`@lit-roa/cantidad`](../cantidad/README.md).
6
+
7
+ ```bash
8
+ npm install @lit-roa/linea-carrito
9
+ ```
10
+
11
+ ```html
12
+ <lr-linea-carrito
13
+ id-producto="cafe-250"
14
+ nombre="Café de Chiapas 250 g"
15
+ variante="Molido · Tueste medio"
16
+ precio="189"
17
+ cantidad="2"
18
+ max="6"
19
+ imagen="/img/cafe.jpg"
20
+ href="/productos/cafe-250"
21
+ ></lr-linea-carrito>
22
+ ```
23
+
24
+ El componente no guarda el carrito: avisa y tu sitio actualiza su estado
25
+ (por ejemplo, el carrito de `core/` en init-template).
26
+
27
+ ## Propiedades
28
+
29
+ | Atributo | Propiedad | Tipo | Por omisión |
30
+ | --- | --- | --- | --- |
31
+ | `id-producto` | `idProducto` | `string` | `''` |
32
+ | `nombre` / `variante` / `imagen` / `href` | | `string` | `''` |
33
+ | `precio` | | `number` (unitario) | `0` |
34
+ | `precio-antes` | `precioAntes` | `number` (unitario) | — |
35
+ | `cantidad` | | `number` | `1` |
36
+ | `max` | | `number` (existencias) | — |
37
+ | `moneda` | | `string` | `'MXN'` |
38
+ | `solo-lectura` | `soloLectura` | `boolean` (sin cantidad ni quitar) | `false` |
39
+ | `compacta` | | `boolean` (imagen chica, quitar solo con ícono) | `false` |
40
+
41
+ ## Eventos
42
+
43
+ | Evento | `detail` |
44
+ | --- | --- |
45
+ | `cambio-cantidad` | `{ id, cantidad }` |
46
+ | `quitar` | `{ id }` |
47
+ | `ver-producto` | `{ id }` (al tocar la imagen o el nombre) |
48
+
49
+ Slot `extra`: debajo de la variante (ej. "¡Solo quedan 2!").
50
+ Estilo: `--lr-linea-carrito-imagen` (ancho de la imagen), `::part(linea)`.
51
+
52
+ ## Desarrollo
53
+
54
+ ```bash
55
+ npm run dev -w @lit-roa/linea-carrito
56
+ ```
@@ -0,0 +1,2 @@
1
+ export { LrLineaCarrito, type DetalleCambioLinea, type DetalleLinea } from './linea-carrito.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { LrLineaCarrito } from './linea-carrito.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAA8C,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,50 @@
1
+ import { LitElement } from 'lit';
2
+ import '@lit-roa/cantidad';
3
+ export interface DetalleCambioLinea {
4
+ id: string;
5
+ cantidad: number;
6
+ }
7
+ export interface DetalleLinea {
8
+ id: string;
9
+ }
10
+ /**
11
+ * Una línea del carrito: imagen, nombre, variante, precio, selector de
12
+ * cantidad y botón para quitar.
13
+ *
14
+ * @fires cambio-cantidad - `detail`: `{ id, cantidad }`.
15
+ * @fires quitar - `detail`: `{ id }`.
16
+ * @fires ver-producto - Al tocar la imagen o el nombre. `detail`: `{ id }`.
17
+ * @slot extra - Debajo de la variante (ej. "Envío gratis" o avisos de existencias).
18
+ */
19
+ export declare class LrLineaCarrito extends LitElement {
20
+ static styles: import("lit").CSSResult[];
21
+ idProducto: string;
22
+ nombre: string;
23
+ /** Texto de la variante, ej. "Molido · 250 g". */
24
+ variante: string;
25
+ imagen: string;
26
+ /** Precio unitario. */
27
+ precio: number;
28
+ /** Precio unitario antes del descuento. */
29
+ precioAntes?: number;
30
+ cantidad: number;
31
+ /** Máximo para el selector (existencias). */
32
+ max?: number;
33
+ moneda: string;
34
+ /** Liga al producto (solo viaja en el evento; el sitio navega). */
35
+ href: string;
36
+ /** Sin cambiar cantidad ni quitar (ej. en el resumen del checkout). */
37
+ soloLectura: boolean;
38
+ /** Imagen más chica y botón de quitar solo con ícono, para el mini carrito. */
39
+ compacta: boolean;
40
+ render(): import("lit-html").TemplateResult<1>;
41
+ private alCambiarCantidad;
42
+ private quitar;
43
+ private verProducto;
44
+ }
45
+ declare global {
46
+ interface HTMLElementTagNameMap {
47
+ 'lr-linea-carrito': LrLineaCarrito;
48
+ }
49
+ }
50
+ //# sourceMappingURL=linea-carrito.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linea-carrito.d.ts","sourceRoot":"","sources":["../src/linea-carrito.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAGrD,OAAO,mBAAmB,CAAC;AAG3B,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;GAQG;AACH,qBACa,cAAe,SAAQ,UAAU;IAC5C,OAAgB,MAAM,4BA0GpB;IAEsC,UAAU,SAAM;IAE5C,MAAM,SAAM;IAExB,kDAAkD;IACtC,QAAQ,SAAM;IAEd,MAAM,SAAM;IAExB,uBAAuB;IACK,MAAM,SAAK;IAEvC,2CAA2C;IACY,WAAW,CAAC,EAAE,MAAM,CAAC;IAEhD,QAAQ,SAAK;IAEzC,6CAA6C;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IAE7B,MAAM,SAAS;IAE3B,mEAAmE;IACvD,IAAI,SAAM;IAEtB,uEAAuE;IACf,WAAW,UAAS;IAE5E,+EAA+E;IACnC,QAAQ,UAAS;IAEpD,MAAM;IAoCf,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,WAAW;CAKpB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,cAAc,CAAC;KACpC;CACF"}
@@ -0,0 +1,236 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { LitElement, css, html, nothing } from 'lit';
8
+ import { customElement, property } from 'lit/decorators.js';
9
+ import { emitir, estilosBase, formatearPrecio, iconoTrash, tokens } from '@lit-roa/base';
10
+ import '@lit-roa/cantidad';
11
+ /**
12
+ * Una línea del carrito: imagen, nombre, variante, precio, selector de
13
+ * cantidad y botón para quitar.
14
+ *
15
+ * @fires cambio-cantidad - `detail`: `{ id, cantidad }`.
16
+ * @fires quitar - `detail`: `{ id }`.
17
+ * @fires ver-producto - Al tocar la imagen o el nombre. `detail`: `{ id }`.
18
+ * @slot extra - Debajo de la variante (ej. "Envío gratis" o avisos de existencias).
19
+ */
20
+ let LrLineaCarrito = class LrLineaCarrito extends LitElement {
21
+ constructor() {
22
+ super(...arguments);
23
+ this.idProducto = '';
24
+ this.nombre = '';
25
+ /** Texto de la variante, ej. "Molido · 250 g". */
26
+ this.variante = '';
27
+ this.imagen = '';
28
+ /** Precio unitario. */
29
+ this.precio = 0;
30
+ this.cantidad = 1;
31
+ this.moneda = 'MXN';
32
+ /** Liga al producto (solo viaja en el evento; el sitio navega). */
33
+ this.href = '';
34
+ /** Sin cambiar cantidad ni quitar (ej. en el resumen del checkout). */
35
+ this.soloLectura = false;
36
+ /** Imagen más chica y botón de quitar solo con ícono, para el mini carrito. */
37
+ this.compacta = false;
38
+ }
39
+ static { this.styles = [
40
+ tokens,
41
+ estilosBase,
42
+ css `
43
+ :host {
44
+ display: block;
45
+ padding: 1rem 0;
46
+ border-bottom: 1px solid var(--_color-borde);
47
+ }
48
+ :host(:last-of-type) {
49
+ border-bottom: 0;
50
+ }
51
+ .linea {
52
+ display: grid;
53
+ grid-template-columns: var(--lr-linea-carrito-imagen, 5rem) 1fr auto;
54
+ gap: 0.875rem;
55
+ align-items: start;
56
+ }
57
+ :host([compacta]) .linea {
58
+ grid-template-columns: var(--lr-linea-carrito-imagen, 4rem) 1fr auto;
59
+ }
60
+ .imagen {
61
+ display: block;
62
+ aspect-ratio: 1;
63
+ border-radius: calc(var(--_radio) / 1.5);
64
+ background: var(--_color-borde);
65
+ overflow: hidden;
66
+ cursor: pointer;
67
+ }
68
+ img {
69
+ width: 100%;
70
+ height: 100%;
71
+ object-fit: cover;
72
+ display: block;
73
+ }
74
+ .info {
75
+ min-width: 0;
76
+ display: grid;
77
+ gap: 0.25rem;
78
+ }
79
+ a {
80
+ color: inherit;
81
+ text-decoration: none;
82
+ font-weight: 600;
83
+ line-height: 1.35;
84
+ }
85
+ a:hover {
86
+ text-decoration: underline;
87
+ }
88
+ a:focus-visible,
89
+ .imagen:focus-visible {
90
+ outline: 2px solid var(--_color-primario);
91
+ outline-offset: 2px;
92
+ }
93
+ .variante,
94
+ .unitario {
95
+ color: var(--_color-texto-suave);
96
+ font-size: 0.8125rem;
97
+ }
98
+ .controles {
99
+ display: flex;
100
+ align-items: center;
101
+ gap: 0.5rem;
102
+ margin-top: 0.375rem;
103
+ }
104
+ .quitar {
105
+ all: unset;
106
+ display: inline-flex;
107
+ align-items: center;
108
+ gap: 0.25rem;
109
+ padding: 0.375rem;
110
+ border-radius: 6px;
111
+ color: var(--_color-texto-suave);
112
+ font-size: 0.8125rem;
113
+ cursor: pointer;
114
+ }
115
+ .quitar:hover {
116
+ color: var(--lr-color-peligro, #dc2626);
117
+ background: #fef2f2;
118
+ }
119
+ .quitar:focus-visible {
120
+ outline: 2px solid var(--_color-primario);
121
+ }
122
+ .quitar svg {
123
+ font-size: 1.125rem;
124
+ }
125
+ .precios {
126
+ text-align: right;
127
+ white-space: nowrap;
128
+ }
129
+ .total {
130
+ font-weight: 700;
131
+ }
132
+ .antes {
133
+ display: block;
134
+ color: var(--_color-texto-suave);
135
+ text-decoration: line-through;
136
+ font-size: 0.8125rem;
137
+ }
138
+ .texto-quitar {
139
+ display: var(--_texto-quitar, inline);
140
+ }
141
+ :host([compacta]) {
142
+ --_texto-quitar: none;
143
+ }
144
+ `,
145
+ ]; }
146
+ render() {
147
+ const total = this.precio * this.cantidad;
148
+ const conDescuento = this.precioAntes !== undefined && this.precioAntes > this.precio;
149
+ return html `<div class="linea" part="linea">
150
+ <span class="imagen" role="link" tabindex="-1" aria-hidden="true" @click=${this.verProducto}>
151
+ ${this.imagen ? html `<img src=${this.imagen} alt="" loading="lazy" />` : nothing}
152
+ </span>
153
+ <div class="info">
154
+ <a href=${this.href || '#'} @click=${this.verProducto}>${this.nombre}</a>
155
+ ${this.variante ? html `<span class="variante">${this.variante}</span>` : nothing}
156
+ ${this.cantidad > 1 || this.soloLectura
157
+ ? html `<span class="unitario">${this.soloLectura ? `${this.cantidad} × ` : ''}${formatearPrecio(this.precio, this.moneda)} c/u</span>`
158
+ : nothing}
159
+ <slot name="extra"></slot>
160
+ ${this.soloLectura
161
+ ? nothing
162
+ : html `<div class="controles">
163
+ <lr-cantidad
164
+ tamano="chico"
165
+ .valor=${this.cantidad}
166
+ .max=${this.max}
167
+ etiqueta="Cantidad de ${this.nombre}"
168
+ @cambio=${this.alCambiarCantidad}
169
+ ></lr-cantidad>
170
+ <button class="quitar" aria-label="Quitar ${this.nombre}" @click=${this.quitar}>
171
+ ${iconoTrash}<span class="texto-quitar">Quitar</span>
172
+ </button>
173
+ </div>`}
174
+ </div>
175
+ <div class="precios">
176
+ <span class="total">${formatearPrecio(total, this.moneda)}</span>
177
+ ${conDescuento ? html `<span class="antes">${formatearPrecio(this.precioAntes * this.cantidad, this.moneda)}</span>` : nothing}
178
+ </div>
179
+ </div>`;
180
+ }
181
+ alCambiarCantidad(evento) {
182
+ evento.stopPropagation();
183
+ this.cantidad = evento.detail.valor;
184
+ emitir(this, 'cambio-cantidad', { id: this.idProducto, cantidad: this.cantidad });
185
+ }
186
+ quitar() {
187
+ emitir(this, 'quitar', { id: this.idProducto });
188
+ }
189
+ verProducto(evento) {
190
+ if (this.href && (evento.ctrlKey || evento.metaKey))
191
+ return;
192
+ evento.preventDefault();
193
+ emitir(this, 'ver-producto', { id: this.idProducto });
194
+ }
195
+ };
196
+ __decorate([
197
+ property({ attribute: 'id-producto' })
198
+ ], LrLineaCarrito.prototype, "idProducto", void 0);
199
+ __decorate([
200
+ property()
201
+ ], LrLineaCarrito.prototype, "nombre", void 0);
202
+ __decorate([
203
+ property()
204
+ ], LrLineaCarrito.prototype, "variante", void 0);
205
+ __decorate([
206
+ property()
207
+ ], LrLineaCarrito.prototype, "imagen", void 0);
208
+ __decorate([
209
+ property({ type: Number })
210
+ ], LrLineaCarrito.prototype, "precio", void 0);
211
+ __decorate([
212
+ property({ type: Number, attribute: 'precio-antes' })
213
+ ], LrLineaCarrito.prototype, "precioAntes", void 0);
214
+ __decorate([
215
+ property({ type: Number })
216
+ ], LrLineaCarrito.prototype, "cantidad", void 0);
217
+ __decorate([
218
+ property({ type: Number })
219
+ ], LrLineaCarrito.prototype, "max", void 0);
220
+ __decorate([
221
+ property()
222
+ ], LrLineaCarrito.prototype, "moneda", void 0);
223
+ __decorate([
224
+ property()
225
+ ], LrLineaCarrito.prototype, "href", void 0);
226
+ __decorate([
227
+ property({ type: Boolean, attribute: 'solo-lectura' })
228
+ ], LrLineaCarrito.prototype, "soloLectura", void 0);
229
+ __decorate([
230
+ property({ type: Boolean, reflect: true })
231
+ ], LrLineaCarrito.prototype, "compacta", void 0);
232
+ LrLineaCarrito = __decorate([
233
+ customElement('lr-linea-carrito')
234
+ ], LrLineaCarrito);
235
+ export { LrLineaCarrito };
236
+ //# sourceMappingURL=linea-carrito.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linea-carrito.js","sourceRoot":"","sources":["../src/linea-carrito.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,31 @@
1
1
  {
2
2
  "name": "@lit-roa/linea-carrito",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Línea de carrito con imagen, variante, cantidad y quitar.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src"
17
+ ],
18
+ "sideEffects": [
19
+ "./dist/index.js"
20
+ ],
21
+ "scripts": {
22
+ "dev": "vite",
23
+ "build": "tsc -b"
24
+ },
25
+ "dependencies": {
26
+ "@lit-roa/base": "^0.4.0",
27
+ "@lit-roa/cantidad": "^0.1.1",
28
+ "lit": "^3.3.3"
29
+ },
30
+ "license": "MIT"
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { LrLineaCarrito, type DetalleCambioLinea, type DetalleLinea } from './linea-carrito.js';
@@ -0,0 +1,223 @@
1
+ import { LitElement, css, html, nothing } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { emitir, estilosBase, formatearPrecio, iconoTrash, tokens } from '@lit-roa/base';
4
+ import '@lit-roa/cantidad';
5
+ import type { DetalleCambioCantidad } from '@lit-roa/cantidad';
6
+
7
+ export interface DetalleCambioLinea {
8
+ id: string;
9
+ cantidad: number;
10
+ }
11
+
12
+ export interface DetalleLinea {
13
+ id: string;
14
+ }
15
+
16
+ /**
17
+ * Una línea del carrito: imagen, nombre, variante, precio, selector de
18
+ * cantidad y botón para quitar.
19
+ *
20
+ * @fires cambio-cantidad - `detail`: `{ id, cantidad }`.
21
+ * @fires quitar - `detail`: `{ id }`.
22
+ * @fires ver-producto - Al tocar la imagen o el nombre. `detail`: `{ id }`.
23
+ * @slot extra - Debajo de la variante (ej. "Envío gratis" o avisos de existencias).
24
+ */
25
+ @customElement('lr-linea-carrito')
26
+ export class LrLineaCarrito extends LitElement {
27
+ static override styles = [
28
+ tokens,
29
+ estilosBase,
30
+ css`
31
+ :host {
32
+ display: block;
33
+ padding: 1rem 0;
34
+ border-bottom: 1px solid var(--_color-borde);
35
+ }
36
+ :host(:last-of-type) {
37
+ border-bottom: 0;
38
+ }
39
+ .linea {
40
+ display: grid;
41
+ grid-template-columns: var(--lr-linea-carrito-imagen, 5rem) 1fr auto;
42
+ gap: 0.875rem;
43
+ align-items: start;
44
+ }
45
+ :host([compacta]) .linea {
46
+ grid-template-columns: var(--lr-linea-carrito-imagen, 4rem) 1fr auto;
47
+ }
48
+ .imagen {
49
+ display: block;
50
+ aspect-ratio: 1;
51
+ border-radius: calc(var(--_radio) / 1.5);
52
+ background: var(--_color-borde);
53
+ overflow: hidden;
54
+ cursor: pointer;
55
+ }
56
+ img {
57
+ width: 100%;
58
+ height: 100%;
59
+ object-fit: cover;
60
+ display: block;
61
+ }
62
+ .info {
63
+ min-width: 0;
64
+ display: grid;
65
+ gap: 0.25rem;
66
+ }
67
+ a {
68
+ color: inherit;
69
+ text-decoration: none;
70
+ font-weight: 600;
71
+ line-height: 1.35;
72
+ }
73
+ a:hover {
74
+ text-decoration: underline;
75
+ }
76
+ a:focus-visible,
77
+ .imagen:focus-visible {
78
+ outline: 2px solid var(--_color-primario);
79
+ outline-offset: 2px;
80
+ }
81
+ .variante,
82
+ .unitario {
83
+ color: var(--_color-texto-suave);
84
+ font-size: 0.8125rem;
85
+ }
86
+ .controles {
87
+ display: flex;
88
+ align-items: center;
89
+ gap: 0.5rem;
90
+ margin-top: 0.375rem;
91
+ }
92
+ .quitar {
93
+ all: unset;
94
+ display: inline-flex;
95
+ align-items: center;
96
+ gap: 0.25rem;
97
+ padding: 0.375rem;
98
+ border-radius: 6px;
99
+ color: var(--_color-texto-suave);
100
+ font-size: 0.8125rem;
101
+ cursor: pointer;
102
+ }
103
+ .quitar:hover {
104
+ color: var(--lr-color-peligro, #dc2626);
105
+ background: #fef2f2;
106
+ }
107
+ .quitar:focus-visible {
108
+ outline: 2px solid var(--_color-primario);
109
+ }
110
+ .quitar svg {
111
+ font-size: 1.125rem;
112
+ }
113
+ .precios {
114
+ text-align: right;
115
+ white-space: nowrap;
116
+ }
117
+ .total {
118
+ font-weight: 700;
119
+ }
120
+ .antes {
121
+ display: block;
122
+ color: var(--_color-texto-suave);
123
+ text-decoration: line-through;
124
+ font-size: 0.8125rem;
125
+ }
126
+ .texto-quitar {
127
+ display: var(--_texto-quitar, inline);
128
+ }
129
+ :host([compacta]) {
130
+ --_texto-quitar: none;
131
+ }
132
+ `,
133
+ ];
134
+
135
+ @property({ attribute: 'id-producto' }) idProducto = '';
136
+
137
+ @property() nombre = '';
138
+
139
+ /** Texto de la variante, ej. "Molido · 250 g". */
140
+ @property() variante = '';
141
+
142
+ @property() imagen = '';
143
+
144
+ /** Precio unitario. */
145
+ @property({ type: Number }) precio = 0;
146
+
147
+ /** Precio unitario antes del descuento. */
148
+ @property({ type: Number, attribute: 'precio-antes' }) precioAntes?: number;
149
+
150
+ @property({ type: Number }) cantidad = 1;
151
+
152
+ /** Máximo para el selector (existencias). */
153
+ @property({ type: Number }) max?: number;
154
+
155
+ @property() moneda = 'MXN';
156
+
157
+ /** Liga al producto (solo viaja en el evento; el sitio navega). */
158
+ @property() href = '';
159
+
160
+ /** Sin cambiar cantidad ni quitar (ej. en el resumen del checkout). */
161
+ @property({ type: Boolean, attribute: 'solo-lectura' }) soloLectura = false;
162
+
163
+ /** Imagen más chica y botón de quitar solo con ícono, para el mini carrito. */
164
+ @property({ type: Boolean, reflect: true }) compacta = false;
165
+
166
+ override render() {
167
+ const total = this.precio * this.cantidad;
168
+ const conDescuento = this.precioAntes !== undefined && this.precioAntes > this.precio;
169
+ return html`<div class="linea" part="linea">
170
+ <span class="imagen" role="link" tabindex="-1" aria-hidden="true" @click=${this.verProducto}>
171
+ ${this.imagen ? html`<img src=${this.imagen} alt="" loading="lazy" />` : nothing}
172
+ </span>
173
+ <div class="info">
174
+ <a href=${this.href || '#'} @click=${this.verProducto}>${this.nombre}</a>
175
+ ${this.variante ? html`<span class="variante">${this.variante}</span>` : nothing}
176
+ ${this.cantidad > 1 || this.soloLectura
177
+ ? html`<span class="unitario">${this.soloLectura ? `${this.cantidad} × ` : ''}${formatearPrecio(this.precio, this.moneda)} c/u</span>`
178
+ : nothing}
179
+ <slot name="extra"></slot>
180
+ ${this.soloLectura
181
+ ? nothing
182
+ : html`<div class="controles">
183
+ <lr-cantidad
184
+ tamano="chico"
185
+ .valor=${this.cantidad}
186
+ .max=${this.max}
187
+ etiqueta="Cantidad de ${this.nombre}"
188
+ @cambio=${this.alCambiarCantidad}
189
+ ></lr-cantidad>
190
+ <button class="quitar" aria-label="Quitar ${this.nombre}" @click=${this.quitar}>
191
+ ${iconoTrash}<span class="texto-quitar">Quitar</span>
192
+ </button>
193
+ </div>`}
194
+ </div>
195
+ <div class="precios">
196
+ <span class="total">${formatearPrecio(total, this.moneda)}</span>
197
+ ${conDescuento ? html`<span class="antes">${formatearPrecio(this.precioAntes! * this.cantidad, this.moneda)}</span>` : nothing}
198
+ </div>
199
+ </div>`;
200
+ }
201
+
202
+ private alCambiarCantidad(evento: CustomEvent<DetalleCambioCantidad>) {
203
+ evento.stopPropagation();
204
+ this.cantidad = evento.detail.valor;
205
+ emitir<DetalleCambioLinea>(this, 'cambio-cantidad', { id: this.idProducto, cantidad: this.cantidad });
206
+ }
207
+
208
+ private quitar() {
209
+ emitir<DetalleLinea>(this, 'quitar', { id: this.idProducto });
210
+ }
211
+
212
+ private verProducto(evento: MouseEvent) {
213
+ if (this.href && (evento.ctrlKey || evento.metaKey)) return;
214
+ evento.preventDefault();
215
+ emitir<DetalleLinea>(this, 'ver-producto', { id: this.idProducto });
216
+ }
217
+ }
218
+
219
+ declare global {
220
+ interface HTMLElementTagNameMap {
221
+ 'lr-linea-carrito': LrLineaCarrito;
222
+ }
223
+ }