@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 +55 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/linea-carrito.d.ts +50 -0
- package/dist/linea-carrito.d.ts.map +1 -0
- package/dist/linea-carrito.js +236 -0
- package/dist/linea-carrito.js.map +1 -0
- package/package.json +29 -4
- package/src/index.ts +1 -0
- package/src/linea-carrito.ts +223 -0
package/README.md
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @lit-roa/linea-carrito
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
+
}
|