@muten/shadcn 0.1.14 → 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.
- package/AGENTS.md +88 -1
- package/globals.css +1 -0
- package/kit.css +329 -124
- package/motion.css +3 -0
- package/package.json +1 -1
- package/registry/accordion.muten +3 -3
- package/registry/agenda.js +19 -16
- package/registry/agenda.muten +10 -10
- package/registry/alert-dialog.muten +2 -2
- package/registry/alert.muten +1 -1
- package/registry/app-shell.muten +1 -1
- package/registry/attachment.muten +1 -1
- package/registry/avatar.muten +4 -4
- package/registry/badge.muten +14 -3
- package/registry/band.muten +2 -2
- package/registry/bar-chart.muten +2 -2
- package/registry/bio-play.muten +2 -2
- package/registry/booking-play.muten +5 -5
- package/registry/booking.muten +2 -2
- package/registry/breadcrumb.muten +4 -4
- package/registry/button.muten +9 -7
- package/registry/calendar.js +15 -10
- package/registry/calendar.muten +1 -1
- package/registry/card.muten +1 -1
- package/registry/carousel.muten +1 -1
- package/registry/chat-play.muten +8 -8
- package/registry/chatbot.muten +5 -4
- package/registry/checklist.muten +1 -1
- package/registry/choice-list.muten +3 -3
- package/registry/code.js +10 -4
- package/registry/collapsible.muten +2 -2
- package/registry/color-picker.js +37 -36
- package/registry/color-picker.muten +1 -1
- package/registry/combobox.js +1 -1
- package/registry/combobox.muten +2 -2
- package/registry/command-palette.muten +2 -2
- package/registry/command.js +1 -1
- package/registry/command.muten +7 -7
- package/registry/context-menu.muten +3 -3
- package/registry/count-up.js +33 -31
- package/registry/count-up.muten +6 -4
- package/registry/date-field.js +1 -1
- package/registry/date-field.muten +3 -3
- package/registry/day-plan.muten +5 -5
- package/registry/device-frame.muten +3 -3
- package/registry/dialog.muten +1 -1
- package/registry/dirty-bar.js +2 -2
- package/registry/dirty-bar.muten +3 -3
- package/registry/drawer.muten +1 -1
- package/registry/dropdown-menu.muten +4 -4
- package/registry/empty.muten +1 -1
- package/registry/feature-grid.muten +1 -1
- package/registry/feature-rows.muten +2 -2
- package/registry/field-behavior.js +13 -3
- package/registry/field-behavior.muten +3 -3
- package/registry/field.muten +5 -5
- package/registry/file-input.js +8 -4
- package/registry/file-input.muten +3 -3
- package/registry/fixed-card.muten +4 -4
- package/registry/folder-picker.js +6 -2
- package/registry/folder-picker.muten +2 -2
- package/registry/heatmap.muten +5 -5
- package/registry/image-upload.js +1 -1
- package/registry/image-upload.muten +2 -2
- package/registry/inbox-play.muten +5 -5
- package/registry/indicator.muten +1 -1
- package/registry/info-table.muten +1 -1
- package/registry/input-group.muten +11 -11
- package/registry/input-otp.muten +2 -2
- package/registry/input.muten +1 -1
- package/registry/inspector.muten +2 -2
- package/registry/item.muten +2 -2
- package/registry/kit/board.js +36 -34
- package/registry/kit/collections.js +24 -24
- package/registry/kit/core.js +197 -114
- package/registry/kit/dates.js +29 -27
- package/registry/kit/i18n.js +172 -0
- package/registry/kit/menus.js +14 -13
- package/registry/kit/overlays.js +7 -2
- package/registry/kit/structure.js +66 -61
- package/registry/kit/table.js +19 -17
- package/registry/kit/values.js +36 -28
- package/registry/kit-skeleton.muten +1 -1
- package/registry/kit-widget.js +9 -4
- package/registry/lang-scope.js +8 -0
- package/registry/lang-scope.muten +5 -0
- package/registry/legal-doc.muten +5 -5
- package/registry/link-btn.muten +1 -1
- package/registry/list-motion.js +46 -0
- package/registry/list-motion.muten +4 -0
- package/registry/live-stack.muten +2 -2
- package/registry/logo-cloud.muten +2 -2
- package/registry/marketing.muten +7 -7
- package/registry/member-list.muten +2 -2
- package/registry/menubar.muten +3 -3
- package/registry/message-scroller.muten +3 -3
- package/registry/message.muten +9 -9
- package/registry/messaging.muten +1 -1
- package/registry/modal.muten +4 -4
- package/registry/money-field.js +1 -1
- package/registry/money-field.muten +2 -2
- package/registry/nav-button.muten +4 -4
- package/registry/navigation-menu.muten +4 -4
- package/registry/navpill.muten +6 -6
- package/registry/new-appointment.js +1 -1
- package/registry/new-appointment.muten +4 -4
- package/registry/number-field.js +6 -4
- package/registry/number-field.muten +1 -1
- package/registry/order-board.js +1 -1
- package/registry/order-board.muten +2 -2
- package/registry/order-play.muten +4 -4
- package/registry/pagination-nav.js +1 -1
- package/registry/pagination-nav.muten +3 -3
- package/registry/pagination.muten +4 -4
- package/registry/password-field.muten +2 -2
- package/registry/permission-matrix.muten +4 -4
- package/registry/phone-bar.muten +6 -6
- package/registry/phone-field.js +1 -1
- package/registry/phone-field.muten +1 -1
- package/registry/phone-shell.js +1 -1
- package/registry/pill.muten +2 -2
- package/registry/plans.muten +1 -1
- package/registry/popover.muten +2 -2
- package/registry/pricing.muten +4 -4
- package/registry/product-play.muten +3 -3
- package/registry/progress.muten +3 -3
- package/registry/queue.muten +1 -1
- package/registry/radio-group.muten +1 -1
- package/registry/rank-item.muten +4 -4
- package/registry/resizable.muten +1 -1
- package/registry/row-menu.js +1 -1
- package/registry/row-menu.muten +2 -2
- package/registry/save-button.muten +5 -5
- package/registry/scale-slider.js +1 -1
- package/registry/scale-slider.muten +2 -2
- package/registry/screen-tour.muten +1 -1
- package/registry/share-bar.muten +2 -2
- package/registry/share-button.muten +4 -4
- package/registry/sheet.muten +1 -1
- package/registry/sidebar.muten +12 -6
- package/registry/site-footer.muten +6 -6
- package/registry/sortable-behavior.js +67 -20
- package/registry/sortable-list.js +3 -1
- package/registry/sortable-list.muten +2 -2
- package/registry/sortable.muten +5 -4
- package/registry/spinner.muten +2 -2
- package/registry/split-button.muten +2 -2
- package/registry/stacked-bars.js +3 -3
- package/registry/stacked-bars.muten +2 -2
- package/registry/stamp-card.muten +2 -2
- package/registry/stat-band.muten +3 -3
- package/registry/stat.muten +3 -3
- package/registry/status-bar.muten +2 -2
- package/registry/stepper.muten +4 -4
- package/registry/store-qr.js +1 -1
- package/registry/store-qr.muten +2 -2
- package/registry/swatch.muten +1 -1
- package/registry/switch.muten +8 -7
- package/registry/table-view.muten +3 -3
- package/registry/tabs-behavior.js +9 -1
- package/registry/tabs.muten +2 -2
- package/registry/tag-input.js +1 -1
- package/registry/tag-input.muten +2 -2
- package/registry/text.muten +2 -2
- package/registry/time-field.js +1 -1
- package/registry/time-field.muten +3 -3
- package/registry/timeline.muten +4 -4
- package/registry/toaster.muten +2 -2
- package/registry/toggle-group.muten +1 -1
- package/registry/toggle.muten +3 -3
- package/registry/toolbar.muten +4 -4
- package/registry/tooltip.muten +2 -2
- package/registry/tree-view.js +38 -25
- package/registry/tree-view.muten +1 -1
- package/registry/veil.muten +1 -1
- package/registry/virtual-list.js +1 -1
- package/registry/virtual-list.muten +2 -2
- package/registry/virtual-window.js +4 -4
- package/registry/virtual-window.muten +2 -2
- package/registry/wordline.muten +1 -1
- package/registry/zoom-control.muten +1 -1
- package/registry.json +22 -2
package/registry/kit/board.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// kit/board.js - the order Board and its card (the reference artifact's
|
|
1
|
+
// kit/board.js - the order Board and its card (the reference artifact's “Board” and “Card: minimum and maximum”),
|
|
2
2
|
// composed only from atoms. Lanes and cards are data; the card groups its information by what it is about and
|
|
3
3
|
// orders it by what needs the owner first. Drag with the mouse, hold on touch, Space/arrows with the keyboard.
|
|
4
4
|
// lanes [{id, title, next}] next = the label of the step that moves a card INTO the following lane
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
// on.change(json [{id, lane}]) after a card moves; on.action(json {id, action}) for every card button.
|
|
10
10
|
export function orderBoard(root, i, core, on) {
|
|
11
11
|
const { ui, $, $$, lib, openModal: open, closeModal, CONTENT, body, foot, easeHeight, money0, hue, ini } = core;
|
|
12
|
+
// its language: the nearest lang (the page's), English by default; the card data the page passes is said as is
|
|
13
|
+
const L = core.langOf(root), T = (k, ...a) => core.t(L, k, ...a);
|
|
12
14
|
const list = (v) => (Array.isArray(v) ? v : []);
|
|
13
15
|
const copy = (x) => JSON.parse(JSON.stringify(x));
|
|
14
16
|
const CARDS = Object.fromEntries(list(i.cards).map((c) => [c.id, { actions: [], ...copy(c) }]));
|
|
@@ -43,7 +45,7 @@ export function orderBoard(root, i, core, on) {
|
|
|
43
45
|
footer: `<div class="cx-vstack"><div class="cx-hstack">${o.photos.slice(0, 5).map((x) => ui.avatar(x, { shape: 'square', size: 'lg', bg: x })).join('')}${o.photos.length > 5 ? `<span data-slot="avatar" data-size="lg" data-shape="square" class="cn-avatar group/avatar"><span class="cn-avatar-fallback">+${o.photos.length - 5}</span></span>` : ''}</div>${o.extras?.length ? `<div class="cx-hstack">${o.extras.map((e) => ui.badge(e, { variant: 'outline' })).join('')}</div>` : ''}</div>`,
|
|
44
46
|
}),
|
|
45
47
|
pay: (p, c) => {
|
|
46
|
-
const action = p.method === 'transfer' && p.tone === 'new' ? ['receipt', '
|
|
48
|
+
const action = p.method === 'transfer' && p.tone === 'new' ? ['receipt', T('Review receipt'), 'image'] : p.method === 'card' ? ['stripe', T('See Stripe receipt'), 'card'] : null;
|
|
47
49
|
return ui.item({
|
|
48
50
|
media: ui.itemMedia(ui.icon('card')),
|
|
49
51
|
title: `<span>Pago · ${money0(c.total)}</span>`,
|
|
@@ -54,7 +56,7 @@ export function orderBoard(root, i, core, on) {
|
|
|
54
56
|
},
|
|
55
57
|
delivery: (d) => {
|
|
56
58
|
const info = d.track ? `<b>${d.track.steps[d.track.at]}</b> · ${d.track.courier.split(' ')[0]}, ${d.track.eta}` : d.map ? d.map.address : '';
|
|
57
|
-
const more = (d.track || d.map) ? ui.button({ label: d.track ? '
|
|
59
|
+
const more = (d.track || d.map) ? ui.button({ label: d.track ? T('See delivery') : T('See map'), size: 'xs', attrs: 'data-bact="delivery"' }) : '';
|
|
58
60
|
return ui.item({
|
|
59
61
|
media: ui.itemMedia(ui.icon(d.mode === 'pickup' ? 'store' : 'truck')),
|
|
60
62
|
title: `${d.label}${d.when ? ` · ${d.when}` : ''}`,
|
|
@@ -69,18 +71,18 @@ export function orderBoard(root, i, core, on) {
|
|
|
69
71
|
const signals = (c) => {
|
|
70
72
|
const list = c.request ? (Array.isArray(c.request) ? c.request : [c.request]) : [];
|
|
71
73
|
const items = list.map((r, i) => ui.button({ icon: r.icon, size: 'icon-sm', variant: 'ghost', tone: SIGNAL_TONE[r.tone] || 'info', cls: 'cx-has-indicator', attrs: `data-bact="request" data-i="${i}" data-name="${r.title}" aria-haspopup="dialog" aria-label="${r.title}${r.text ? ': ' + r.text : ''}"` }).replace('</button>', `${ui.indicator(SIGNAL_TONE[r.tone] || 'info')}</button>`));
|
|
72
|
-
if (c.note) items.push(ui.button({ icon: 'quote', size: 'icon-sm', variant: 'ghost', tone: 'note', cls: 'cx-has-indicator', attrs: `data-bact="note" data-name="
|
|
73
|
-
return items.length ? `<div class="cx-hstack" role="group" aria-label="
|
|
74
|
+
if (c.note) items.push(ui.button({ icon: 'quote', size: 'icon-sm', variant: 'ghost', tone: 'note', cls: 'cx-has-indicator', attrs: `data-bact="note" data-name="${T('Client note')}" aria-haspopup="dialog" aria-label="${T('{0}\'s note: {1}', c.who.split(' ')[0], c.note)}"` }).replace('</button>', `${ui.indicator('note')}</button>`));
|
|
75
|
+
return items.length ? `<div class="cx-hstack" role="group" aria-label="${T('Pending')}">${items.join('')}</div>` : '';
|
|
74
76
|
};
|
|
75
77
|
const CONTACT = {
|
|
76
78
|
whatsapp: { icon: 'b-whatsapp', color: '#25D366', name: 'WhatsApp' },
|
|
77
79
|
telegram: { icon: 'b-telegram', color: '#26A5E4', name: 'Telegram' },
|
|
78
80
|
instagram: { icon: 'b-instagram', color: '#E4405F', name: 'Instagram' },
|
|
79
|
-
phone: { icon: 'call', color: 'var(--foreground)', name: '
|
|
81
|
+
phone: { icon: 'call', color: 'var(--foreground)', name: T('a call') },
|
|
80
82
|
};
|
|
81
83
|
const cardInner = (id) => {
|
|
82
84
|
const c = CARDS[id], via = CONTACT[c.contact];
|
|
83
|
-
const contact = via ? ui.button({ icon: via.icon, size: 'icon-sm', variant: 'ghost', tone: 'brand', attrs: `style="--brand:${via.color}" data-bact="contact" aria-label="
|
|
85
|
+
const contact = via ? ui.button({ icon: via.icon, size: 'icon-sm', variant: 'ghost', tone: 'brand', attrs: `style="--brand:${via.color}" data-bact="contact" aria-label="${T('Contact {0} on {1}, as they asked', c.who, via.name)}" title="${T('Prefers {0}', via.name)}"` }) : '';
|
|
84
86
|
const person = ui.item({
|
|
85
87
|
media: ui.itemMedia(ui.avatar(c.who), 'default'),
|
|
86
88
|
title: `<span class="cx-ellipsis" title="${c.who}">${c.who}</span>`,
|
|
@@ -102,13 +104,13 @@ export function orderBoard(root, i, core, on) {
|
|
|
102
104
|
const list = $('.cx-lane-list', l), n = $$('.cx-bcard', list).length;
|
|
103
105
|
$('.cx-count', l).textContent = n;
|
|
104
106
|
const empty = $('.cx-lane-empty-b', list);
|
|
105
|
-
if (!n && !$('.cx-bph', list) && !empty) list.insertAdjacentHTML('beforeend',
|
|
107
|
+
if (!n && !$('.cx-bph', list) && !empty) list.insertAdjacentHTML('beforeend', `<div class="cx-lane-empty-b">${T('Drop an order here')}</div>`);
|
|
106
108
|
if ((n || $('.cx-bph', list)) && empty) empty.remove();
|
|
107
109
|
});
|
|
108
110
|
const sayCard = (card) => { const lane = card.closest('.cx-blane'), list = $$('.cx-bcard', lane); brdLive.textContent = `Pedido ${card.dataset.card}: ${lane.getAttribute('aria-label')}, posición ${list.indexOf(card) + 1} de ${list.length}`; };
|
|
109
111
|
const flip = (els, change) => { // animate siblings from their old place to the new one
|
|
110
112
|
const before = new Map(els.map((x) => [x, x.getBoundingClientRect()])); change();
|
|
111
|
-
before.forEach((r, x) => { const n = x.getBoundingClientRect(); const dx = r.left - n.left, dy = r.top - n.top; if (dx || dy) x.animate([{ transform: `translate(${dx}px, ${dy}px)` }, { transform: 'none' }], { duration: 200, easing: 'cubic-bezier(.
|
|
113
|
+
before.forEach((r, x) => { const n = x.getBoundingClientRect(); const dx = r.left - n.left, dy = r.top - n.top; if (dx || dy) x.animate([{ transform: `translate(${dx}px, ${dy}px)` }, { transform: 'none' }], { duration: 200, easing: 'cubic-bezier(.23, 1, .32, 1)' }); });
|
|
112
114
|
};
|
|
113
115
|
let bDrag = null;
|
|
114
116
|
brd.addEventListener('pointerdown', (e) => {
|
|
@@ -148,7 +150,7 @@ export function orderBoard(root, i, core, on) {
|
|
|
148
150
|
const to = d.ph.getBoundingClientRect(), from = d.card.getBoundingClientRect();
|
|
149
151
|
d.card.classList.remove('cx-dragging'); d.card.style.cssText = '';
|
|
150
152
|
d.ph.replaceWith(d.card); syncLanes();
|
|
151
|
-
d.card.animate([{ transform: `translate(${from.left - to.left}px, ${from.top - to.top}px) rotate(1.5deg)` }, { transform: 'none' }], { duration: 220, easing: 'cubic-bezier(.
|
|
153
|
+
d.card.animate([{ transform: `translate(${from.left - to.left}px, ${from.top - to.top}px) rotate(1.5deg)` }, { transform: 'none' }], { duration: 220, easing: 'cubic-bezier(.23, 1, .32, 1)' });
|
|
152
154
|
sayCard(d.card); emit();
|
|
153
155
|
};
|
|
154
156
|
addEventListener('pointerup', bEnd); addEventListener('pointercancel', bEnd);
|
|
@@ -201,10 +203,10 @@ export function orderBoard(root, i, core, on) {
|
|
|
201
203
|
let receiptFor = null, replyFor = null, deliveryFor = null, requestFor = null, requestIdx = 0;
|
|
202
204
|
KINDS.request = () => {
|
|
203
205
|
const c = CARDS[requestFor], list = Array.isArray(c.request) ? c.request : [c.request], r = list[requestIdx] || list[0];
|
|
204
|
-
$('#mdl-title').textContent = r.title; $('#mdl-desc').textContent = `${
|
|
205
|
-
body.innerHTML = `${r.text ? `<blockquote class="cx-req-full" data-tone="${r.tone}">${r.text}</blockquote>` :
|
|
206
|
+
$('#mdl-title').textContent = r.title; $('#mdl-desc').textContent = `${T('{0} · order {1}', c.who, requestFor)}${r.when ? ' · ' + r.when : ''}`;
|
|
207
|
+
body.innerHTML = `${r.text ? `<blockquote class="cx-req-full" data-tone="${r.tone}">${r.text}</blockquote>` : `<p class="cx-note" style="text-align:left">${T('No message from the client.')}</p>`}${r.photo ? `<span class="cx-bc-ph" style="width:96px;background:${r.photo}"></span>` : ''}${list.length > 1 ? `<div class="cx-req-next"><b>${T('Also to solve')}</b>${list.filter((x) => x !== r).map((x) => `<span><svg><use href="#${x.icon}"/></svg>${x.title}</span>`).join('')}</div>` : ''}`;
|
|
206
208
|
const [yes, no] = r.actions;
|
|
207
|
-
foot.innerHTML = (no ? btn('outline', no[1], `data-req-act="${no[0]}"`) : btn('outline', '
|
|
209
|
+
foot.innerHTML = (no ? btn('outline', no[1], `data-req-act="${no[0]}"`) : btn('outline', T('Close'), 'data-dismiss')) + (yes ? btn('default', yes[1], `data-req-act="${yes[0]}"`) : '');
|
|
208
210
|
foot.firstElementChild.classList.add('cx-exit'); foot.lastElementChild.classList.add('cx-go');
|
|
209
211
|
};
|
|
210
212
|
document.addEventListener('click', (e) => {
|
|
@@ -216,30 +218,30 @@ export function orderBoard(root, i, core, on) {
|
|
|
216
218
|
let noteFor = null, stripeFor = null;
|
|
217
219
|
KINDS.note = () => {
|
|
218
220
|
const c = CARDS[noteFor];
|
|
219
|
-
$('#mdl-title').textContent =
|
|
221
|
+
$('#mdl-title').textContent = T('{0}\'s note', c.who.split(' ')[0]); $('#mdl-desc').textContent = T('{0} · order {1}', c.who, noteFor);
|
|
220
222
|
body.innerHTML = `<blockquote class="cx-req-full" data-tone="note">${c.note}</blockquote>`;
|
|
221
|
-
foot.innerHTML = btn('outline', '
|
|
223
|
+
foot.innerHTML = btn('outline', T('Close'), 'data-dismiss') + btn('default', T('Copy note'), 'data-note-copy');
|
|
222
224
|
foot.firstElementChild.classList.add('cx-exit'); foot.lastElementChild.classList.add('cx-go');
|
|
223
225
|
};
|
|
224
226
|
KINDS.stripe = () => {
|
|
225
227
|
const c = CARDS[stripeFor], p = c.pay;
|
|
226
|
-
$('#mdl-title').textContent = '
|
|
227
|
-
body.innerHTML = `<div class="cx-stripe"><div class="cx-stripe-top"><span class="cx-stripe-amt cx-num">${money0(c.total)}</span><span data-slot="badge" class="cn-badge group/badge cx-tone-ok"><span class="cx-dot"></span
|
|
228
|
-
foot.innerHTML = btn('outline', '
|
|
228
|
+
$('#mdl-title').textContent = T('Payment receipt'); $('#mdl-desc').textContent = T('{0} · order {1}', c.who, stripeFor);
|
|
229
|
+
body.innerHTML = `<div class="cx-stripe"><div class="cx-stripe-top"><span class="cx-stripe-amt cx-num">${money0(c.total)}</span><span data-slot="badge" class="cn-badge group/badge cx-tone-ok"><span class="cx-dot"></span>${T('Paid')}</span></div><dl><dt>${T('Method')}</dt><dd>${p.detail.split(' · ')[0]}</dd><dt>${T('Processed by')}</dt><dd>Stripe</dd><dt>${T('Date')}</dt><dd>${T('Oct 8, 2026')} · 09:14</dd><dt>${T('Payment ID')}</dt><dd class="cx-num">pi_3Q${stripeFor.slice(1)}xK8aR2</dd><dt>${T('Fee')}</dt><dd class="cx-num">${money0(Math.round(c.total * 0.039))}</dd><dt>${T('You get')}</dt><dd class="cx-num"><b>${money0(c.total - Math.round(c.total * 0.039))}</b></dd></dl></div>`;
|
|
230
|
+
foot.innerHTML = btn('outline', T('Close'), 'data-dismiss') + `<a data-slot="button" class="cn-button cn-button-variant-default cn-button-size-default group/button cx-go" href="https://dashboard.stripe.com/test/payments" target="_blank" rel="noopener">${T('Open in Stripe')}</a>`;
|
|
229
231
|
foot.firstElementChild.classList.add('cx-exit');
|
|
230
232
|
};
|
|
231
233
|
document.addEventListener('click', (e) => {
|
|
232
234
|
if (!e.target.closest('[data-note-copy]') || !noteFor) return;
|
|
233
235
|
const t = e.target.closest('[data-note-copy]'), text = CARDS[noteFor].note;
|
|
234
|
-
try { navigator.clipboard.writeText(text).then(() => { t.
|
|
236
|
+
try { navigator.clipboard.writeText(text).then(() => { t.innerHTML = `<span class="cx-swap-in">${T('Copied')}</span>`; }, () => { getSelection().selectAllChildren($('.cx-req-full', body)); }); } catch { getSelection().selectAllChildren($('.cx-req-full', body)); }
|
|
235
237
|
});
|
|
236
238
|
KINDS.delivery = () => {
|
|
237
239
|
const c = CARDS[deliveryFor], d = c.delivery;
|
|
238
|
-
$('#mdl-title').textContent = d.label; $('#mdl-desc').textContent = `${
|
|
240
|
+
$('#mdl-title').textContent = d.label; $('#mdl-desc').textContent = `${T('{0} · order {1}', c.who, deliveryFor)}${d.when ? ' · ' + d.when : ''}`;
|
|
239
241
|
const steps = d.track ? `<div class="cx-dl-steps" style="--n:${d.track.steps.length}">${d.track.steps.map((st, i) => `<div ${i < d.track.at ? 'data-done' : i === d.track.at ? 'data-now aria-current="step"' : ''}><i></i><span>${st}</span></div>`).join('')}</div>` : '';
|
|
240
|
-
const courier = d.track ? `<div class="cx-dl-courier"><span data-slot="avatar" data-size="default" class="cn-avatar cx-av" style="--h:${hue(d.track.courier)}"><span class="cn-avatar-fallback">${ini(d.track.courier)}</span></span><div><b>${d.track.courier}</b><small>Domiciliario · ${d.track.vehicle}</small></div><div class="cx-dl-eta"><small
|
|
242
|
+
const courier = d.track ? `<div class="cx-dl-courier"><span data-slot="avatar" data-size="default" class="cn-avatar cx-av" style="--h:${hue(d.track.courier)}"><span class="cn-avatar-fallback">${ini(d.track.courier)}</span></span><div><b>${d.track.courier}</b><small>Domiciliario · ${d.track.vehicle}</small></div><div class="cx-dl-eta"><small>${T('Arrives in')}</small><b>${d.track.eta}</b></div></div>` : '';
|
|
241
243
|
body.innerHTML = `<div class="cx-dl">${d.map ? `<div class="cx-dl-map">${miniMap(d.map)}</div><div class="cx-dl-addr"><svg><use href="#mappin"/></svg><div><b>${d.map.address}</b><small>${d.map.detail}</small></div></div>` : ''}${steps}${courier}</div>`;
|
|
242
|
-
foot.innerHTML = btn('outline', '
|
|
244
|
+
foot.innerHTML = btn('outline', T('Close'), 'data-dismiss') + (d.map ? `<a data-slot="button" class="cn-button cn-button-variant-default cn-button-size-default group/button cx-go" href="https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(d.map.address)}" target="_blank" rel="noopener"><svg><use href="#mappin"/></svg>${T('Open in Maps')}</a>` : '');
|
|
243
245
|
foot.firstElementChild.classList.add('cx-exit');
|
|
244
246
|
};
|
|
245
247
|
const redraw = (id) => { const card = $(`.cx-bcard[data-card="${id}"]`, root); easeHeight(card, () => { card.innerHTML = cardInner(id); }); card.animate([{ boxShadow: '0 0 0 2px var(--selected)' }, { boxShadow: '0 0 0 1px var(--border)' }], { duration: 700, easing: 'ease-out' }); };
|
|
@@ -251,7 +253,7 @@ export function orderBoard(root, i, core, on) {
|
|
|
251
253
|
const ls = lanesEls().filter((l) => l.dataset.lane !== 'cancel'), li = ls.indexOf(card.closest('.cx-blane')); if (li < 0 || li >= ls.length - 1) return;
|
|
252
254
|
const NEXT = Object.fromEntries(LANES_B.map((l) => [l.id, l.next ? [['advance', l.next, 'secondary']] : []]));
|
|
253
255
|
const to = ls[li + 1]; c.actions = NEXT[to.dataset.lane] ?? c.actions;
|
|
254
|
-
if (to.dataset.lane === 'done') { if (c.delivery) delete c.delivery.track; c.status = ['ok', '
|
|
256
|
+
if (to.dataset.lane === 'done') { if (c.delivery) delete c.delivery.track; c.status = ['ok', T('Delivered')]; }
|
|
255
257
|
flip($$('.cx-bcard', brd), () => { $('.cx-lane-list', to).prepend(card); card.innerHTML = cardInner(id); syncLanes(); });
|
|
256
258
|
card.focus({ preventScroll: true }); sayCard(card); emit();
|
|
257
259
|
}
|
|
@@ -262,8 +264,8 @@ export function orderBoard(root, i, core, on) {
|
|
|
262
264
|
if (act === 'stripe') { stripeFor = id; openModal('stripe', 'auto'); }
|
|
263
265
|
if (act === 'contact') brdLive.textContent = `Abriendo ${CONTACT[c.contact].name} con ${c.who}`;
|
|
264
266
|
if (act === 'reply') { replyFor = id; openModal('reply', 'auto'); }
|
|
265
|
-
if (act === 'cancel-yes') { delete c.request; c.status = ['bad', '
|
|
266
|
-
if (act === 'cancel-no') { const rest = Array.isArray(c.request) ? c.request.filter((_, i) => i !== +(b.dataset.i || 0)) : []; if (rest.length) c.request = rest.length === 1 ? rest[0] : rest; else delete c.request; c.actions = [['advance', '
|
|
267
|
+
if (act === 'cancel-yes') { delete c.request; c.status = ['bad', T('Canceled')]; c.pay = { ...c.pay, tone: 'muted', label: T('Refunded {0}', money0(c.total)) }; delete c.delivery; c.actions = []; if (!card.closest('.cx-board')) { redraw(id); return; } const to = $('[data-lane=cancel] .cx-lane-list', brd); flip($$('.cx-bcard', brd), () => { to.prepend(card); card.innerHTML = cardInner(id); syncLanes(); }); to.closest('.cx-blane').scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'smooth' }); brdLive.textContent = `Pedido ${id} cancelado y devuelto`; emit(); }
|
|
268
|
+
if (act === 'cancel-no') { const rest = Array.isArray(c.request) ? c.request.filter((_, i) => i !== +(b.dataset.i || 0)) : []; if (rest.length) c.request = rest.length === 1 ? rest[0] : rest; else delete c.request; c.actions = [['advance', T('Prepare'), 'secondary']]; redraw(id); }
|
|
267
269
|
if (act === 'refund') { if (Array.isArray(c.request)) { c.request = c.request.filter((_, i) => i !== +(b.dataset.i || 0)); if (c.request.length === 1) c.request = c.request[0]; } else delete c.request; c.pay = { ...c.pay, tone: 'info', label: `Reembolsado ${money0(c.total)}` }; redraw(id); }
|
|
268
270
|
};
|
|
269
271
|
root.addEventListener('click', onCardButton);
|
|
@@ -271,29 +273,29 @@ export function orderBoard(root, i, core, on) {
|
|
|
271
273
|
const sizes = $('.cx-card-sizes', root); if (sizes) sizes.innerHTML = SIZES.map(([t, d, id]) => `<div class="cx-cs-col"><div class="cx-cs-h"><b>${t}</b><span>${d}</span></div><div data-slot="card" data-size="sm" class="cn-card group/card cx-bcard cx-bcard-static" data-card="${id}" aria-label="${t}">${cardInner(id)}</div></div>`).join('');
|
|
272
274
|
KINDS.receipt = () => {
|
|
273
275
|
const c = CARDS[receiptFor];
|
|
274
|
-
$('#mdl-title').textContent = '
|
|
275
|
-
body.innerHTML = `<div class="cx-rcpt"><div class="cx-rcpt-shot" role="img" aria-label="
|
|
276
|
-
foot.innerHTML = btn('outline', '
|
|
276
|
+
$('#mdl-title').textContent = T('Payment proof'); $('#mdl-desc').textContent = T('{0} · order {1}', c.who, receiptFor);
|
|
277
|
+
body.innerHTML = `<div class="cx-rcpt"><div class="cx-rcpt-shot" role="img" aria-label="${T('Screenshot of the transfer')}"><span class="cx-rcpt-ok"><svg><use href="#check"/></svg></span><b>${T('Transfer successful')}</b><span class="cx-rcpt-amt">${money0(c.total)}</span><dl><dt>${T('To')}</dt><dd>Barbería El Roble</dd><dt>${T('From')}</dt><dd>${c.who}</dd><dt>${T('Reference')}</dt><dd class="cx-num">M${receiptFor.slice(1)}7731</dd><dt>${T('Date')}</dt><dd>${T('Oct 8, 2026')} · 10:42</dd></dl></div><div class="cx-rcpt-check"><span>${T('Order amount')}</span><b class="cx-num">${money0(c.total)}</b><span data-slot="badge" class="cn-badge group/badge cx-tone-ok"><span class="cx-dot"></span>${T('Matches')}</span></div></div>`;
|
|
278
|
+
foot.innerHTML = btn('outline', T('Reject'), 'data-rcpt="no"') + btn('default', T('Approve payment'), 'data-rcpt="yes"');
|
|
277
279
|
foot.firstElementChild.classList.add('cx-exit'); foot.lastElementChild.classList.add('cx-go');
|
|
278
280
|
};
|
|
279
281
|
KINDS.reply = () => {
|
|
280
282
|
const c = CARDS[replyFor];
|
|
281
|
-
$('#mdl-title').textContent =
|
|
282
|
-
body.innerHTML = `<div class="cx-bc-req" data-tone="bad" style="margin:0"><div class="cx-bc-req-h"><svg><use href="#info"/></svg><b
|
|
283
|
-
foot.innerHTML = btn('outline', '
|
|
283
|
+
$('#mdl-title').textContent = T('Answer {0}', c.who); $('#mdl-desc').textContent = T('Complaint about order {0}', replyFor);
|
|
284
|
+
body.innerHTML = `<div class="cx-bc-req" data-tone="bad" style="margin:0"><div class="cx-bc-req-h"><svg><use href="#info"/></svg><b>${T('Complaint')}</b><small>${(c.request.when ?? c.request[0]?.when)}</small></div><p>«${(c.request.text ?? c.request[0]?.text)}»</p></div><div data-slot="field" class="cn-field cn-field-orientation-vertical group/field"><label data-slot="label" class="cn-label" for="rp-t">${T('Your answer')}</label><textarea data-slot="textarea" class="cn-textarea" id="rp-t" rows="4">${T('Hi {0}, I am so sorry. We are sending you a new one today at no cost.', c.who.split(' ')[0])}</textarea><p data-slot="field-description" class="cn-field-description">${T('It reaches them on WhatsApp.')}</p></div>`;
|
|
285
|
+
foot.innerHTML = btn('outline', T('Cancel'), 'data-dismiss') + btn('default', T('Send answer'), 'data-reply-send');
|
|
284
286
|
foot.firstElementChild.classList.add('cx-exit'); foot.lastElementChild.classList.add('cx-go');
|
|
285
287
|
};
|
|
286
288
|
document.addEventListener('click', (e) => {
|
|
287
289
|
const r = e.target.closest('[data-rcpt]');
|
|
288
290
|
if (r && receiptFor) {
|
|
289
291
|
const c = CARDS[receiptFor], ok = r.dataset.rcpt === 'yes';
|
|
290
|
-
c.pay = ok ? { method: 'transfer', tone: 'ok', label: '
|
|
291
|
-
c.actions = ok ? [['advance', '
|
|
292
|
+
c.pay = ok ? { method: 'transfer', tone: 'ok', label: T('Paid'), detail: c.pay?.detail } : { method: 'transfer', tone: 'bad', label: T('Rejected'), detail: c.pay?.detail };
|
|
293
|
+
c.actions = ok ? [['advance', T('Prepare'), 'secondary']] : [];
|
|
292
294
|
redraw(receiptFor); brdLive.textContent = ok ? `Pago de ${c.who} aprobado` : `Comprobante de ${c.who} rechazado`;
|
|
293
295
|
receiptFor = null; closeModal();
|
|
294
296
|
}
|
|
295
297
|
if (e.target.closest('[data-reply-send]') && replyFor) {
|
|
296
|
-
const c = CARDS[replyFor]; c.request = { ...(Array.isArray(c.request) ? c.request[0] : c.request), tone: 'info', icon: 'check', title: '
|
|
298
|
+
const c = CARDS[replyFor]; c.request = { ...(Array.isArray(c.request) ? c.request[0] : c.request), tone: 'info', icon: 'check', title: T('Complaint answered'), when: T('now'), text: c.request.text, actions: [['refund', T('Refund'), 'ghost']] };
|
|
297
299
|
redraw(replyFor); replyFor = null; closeModal();
|
|
298
300
|
}
|
|
299
301
|
});
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
// Carousel's behaviour, generic. Data and labels come in; nothing about a product is inside.
|
|
3
3
|
|
|
4
4
|
// Tags: chips animate in and out, the field's height eases; suggestions; a cap with its count. i {label, tags
|
|
5
|
-
// "
|
|
5
|
+
// "Beard,Classic cut", suggestions "Fade,Razor", max, hint} → on.change("Beard,Classic cut,Fade")
|
|
6
6
|
export function tagInput(root, i, core, on) {
|
|
7
|
-
const { $, $$, fold, easeHeight } = core;
|
|
7
|
+
const { $, $$, fold, easeHeight } = core, T = (k, ...a) => core.t(core.langOf(root), k, ...a);
|
|
8
8
|
const split = (s) => String(s || '').split(',').map((x) => x.trim()).filter(Boolean);
|
|
9
9
|
const MAXT = Number(i.max) || 8, SUG = split(i.suggestions), id = 'tg' + Math.random().toString(36).slice(2, 7);
|
|
10
10
|
let tags = split(i.tags);
|
|
11
|
-
root.innerHTML = `<div data-slot="field" class="cn-field cn-field-orientation-vertical group/field"><label data-slot="label" class="cn-label" for="${id}">${i.label || '
|
|
11
|
+
root.innerHTML = `<div data-slot="field" class="cn-field cn-field-orientation-vertical group/field"><label data-slot="label" class="cn-label" for="${id}">${T(i.label || 'Tags')}</label><div class="cx-taginput"><input id="${id}" class="cx-tag-field" placeholder="${i.placeholder || 'Agrega y presiona Enter'}" autocomplete="off"></div><p data-slot="field-description" class="cn-field-description cx-count-line"><span>${i.hint || ''}</span><span data-tg-n></span></p></div>${SUG.length ? `<div class="cx-suggest"><span>${T('Suggestions')}</span><div class="cx-line" style="justify-content:flex-start" data-tg-sug></div></div>` : ''}`;
|
|
12
12
|
const tgBox = $('.cx-taginput', root), tgIn = $('input', root), tgN = $('[data-tg-n]', root), tgSug = $('[data-tg-sug]', root);
|
|
13
|
-
const chipHtml = (t) => `<span data-slot="badge" class="cn-badge cn-badge-variant-secondary cx-chip" data-tag="${t}">${t}<button class="cn-button cn-button-variant-ghost cn-button-size-icon-xs cx-chip-x" aria-label="
|
|
13
|
+
const chipHtml = (t) => `<span data-slot="badge" class="cn-badge cn-badge-variant-secondary cx-chip" data-tag="${t}">${t}<button class="cn-button cn-button-variant-ghost cn-button-size-icon-xs cx-chip-x" aria-label="${T('Remove {0}', t)}" title="Quitar" tabindex="-1"><svg style="width:12px;height:12px"><use href="#x"/></svg></button></span>`;
|
|
14
14
|
const emit = () => on?.change?.(tags.join(','));
|
|
15
15
|
const tgMeta = () => {
|
|
16
16
|
tgN.textContent = `${tags.length}/${MAXT}`; tgN.parentElement.toggleAttribute('data-near', tags.length >= MAXT);
|
|
17
|
-
tgIn.placeholder = tags.length >= MAXT ?
|
|
17
|
+
tgIn.placeholder = tags.length >= MAXT ? T('You reached the limit of {0}', MAXT) : tags.length ? '' : (i.placeholder || T('Add and press Enter'));
|
|
18
18
|
if (tgSug) $$('.cx-sug', tgSug).forEach((b) => { b.disabled = tags.some((t) => fold(t) === fold(b.textContent)) || tags.length >= MAXT; });
|
|
19
19
|
};
|
|
20
20
|
const tgAdd = (raw) => {
|
|
@@ -40,19 +40,19 @@ export function tagInput(root, i, core, on) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// Pagination nav for server pages: always 7 slots once there are more than 7 pages (the buttons never slide),
|
|
43
|
-
//
|
|
43
|
+
// “Page n of N” on a phone, and the range line. i {total, per, page, noun} → on.page(n)
|
|
44
44
|
export function paginationNav(root, i, core, on) {
|
|
45
|
-
const { $ } = core;
|
|
46
|
-
root.innerHTML =
|
|
45
|
+
const { $ } = core, L = core.langOf(root), T = (k, ...a) => core.t(L, k, ...a);
|
|
46
|
+
root.innerHTML = `<div class="cx-pg-foot"><nav class="cn-pagination cx-pagination" aria-label="${T('Pages')}"><ul class="cn-pagination-content" style="list-style:none;margin:0;padding:0"></ul></nav><span class="cx-num" aria-live="polite" data-pg-range></span></div>`;
|
|
47
47
|
const pgNav = $('ul', root), pgRange = $('[data-pg-range]', root);
|
|
48
|
-
let total = Number(i.total) || 0, per = Number(i.per) || 10, page = Number(i.page) || 1, noun = i.noun || '
|
|
48
|
+
let total = Number(i.total) || 0, per = Number(i.per) || 10, page = Number(i.page) || 1, noun = T(i.noun || 'results');
|
|
49
49
|
const pages = () => Math.max(1, Math.ceil(total / per));
|
|
50
50
|
const nums = () => { const P = pages(); if (P <= 7) return Array.from({ length: P }, (_, k) => k + 1); if (page <= 4) return [1, 2, 3, 4, 5, '…', P]; if (page >= P - 3) return [1, '…', P - 4, P - 3, P - 2, P - 1, P]; return [1, '…', page - 1, page, page + 1, '…', P]; };
|
|
51
51
|
const paint = (focus) => {
|
|
52
52
|
const P = pages();
|
|
53
|
-
const nav = (dir, p, off) => `<li><button data-slot="button" class="cn-button cn-button-variant-ghost cn-button-size-default cn-pagination-${dir === -1 ? 'previous' : 'next'} group/button" data-page="${p}" ${off ? 'disabled' : ''} aria-label="
|
|
54
|
-
pgNav.innerHTML = nav(-1, page - 1, page === 1) + nums().map((p) => (p === '…' ? '<li class="cx-pg-num"><span class="cn-pagination-ellipsis cx-pg-gap" aria-hidden="true"><svg><use href="#more"/></svg></span></li>' : `<li class="cx-pg-num"><button data-slot="button" class="cn-button ${p === page ? 'cn-button-variant-outline' : 'cn-button-variant-ghost'} cn-button-size-icon group/button cx-num" data-page="${p}" ${p === page ? 'aria-current="page"' : ''} aria-label="
|
|
55
|
-
pgRange.textContent =
|
|
53
|
+
const nav = (dir, p, off) => `<li><button data-slot="button" class="cn-button cn-button-variant-ghost cn-button-size-default cn-pagination-${dir === -1 ? 'previous' : 'next'} group/button" data-page="${p}" ${off ? 'disabled' : ''} aria-label="${dir === -1 ? T('Previous page') : T('Next page')}">${dir === -1 ? `<svg><use href="#left"/></svg><span>${T('Previous')}</span>` : `<span>${T('Next')}</span><svg><use href="#right"/></svg>`}</button></li>`;
|
|
54
|
+
pgNav.innerHTML = nav(-1, page - 1, page === 1) + nums().map((p) => (p === '…' ? '<li class="cx-pg-num"><span class="cn-pagination-ellipsis cx-pg-gap" aria-hidden="true"><svg><use href="#more"/></svg></span></li>' : `<li class="cx-pg-num"><button data-slot="button" class="cn-button ${p === page ? 'cn-button-variant-outline' : 'cn-button-variant-ghost'} cn-button-size-icon group/button cx-num" data-page="${p}" ${p === page ? 'aria-current="page"' : ''} aria-label="${T('Page {0}', p)}">${p}</button></li>`)).join('') + `<li class="cx-pg-compact">${T('Page {0} of {1}', page, P)}</li>` + nav(1, page + 1, page === P);
|
|
55
|
+
pgRange.textContent = T('{0}–{1} of {2} {3}', (page - 1) * per + 1, Math.min(total, page * per), total, noun);
|
|
56
56
|
if (focus) pgNav.querySelector('[aria-current]')?.focus({ preventScroll: true });
|
|
57
57
|
};
|
|
58
58
|
pgNav.addEventListener('click', (e) => { const b = e.target.closest('[data-page]'); if (!b || b.disabled) return; page = +b.dataset.page; paint(true); on?.page?.(page); });
|
|
@@ -61,12 +61,12 @@ export function paginationNav(root, i, core, on) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
// Virtual list: only the visible rows (+ a margin) exist in the DOM. i {rows [{title, subtitle, value}], total
|
|
64
|
-
// (rows repeat up to it, for a long demo), rowHeight, label} → on.jump not needed:
|
|
64
|
+
// (rows repeat up to it, for a long demo), rowHeight, label} → on.jump not needed: “Go to n” via inputs.jump.
|
|
65
65
|
export function virtualList(root, i, core) {
|
|
66
|
-
const { $ } = core;
|
|
66
|
+
const { $ } = core, L = core.langOf(root), T = (k, ...a) => core.t(L, k, ...a), loc = L === 'es' ? 'es-CO' : 'en-US';
|
|
67
67
|
const rows = Array.isArray(i.rows) ? i.rows : [];
|
|
68
68
|
const VTOTAL = Math.max(Number(i.total) || 0, rows.length), VROW = Number(i.rowHeight) || 52, VOVER = 6;
|
|
69
|
-
root.innerHTML = `<div class="cx-pages-wrap"><div class="cx-vlist cx-scroll" tabindex="0" role="list" aria-label="${i.label || '
|
|
69
|
+
root.innerHTML = `<div class="cx-pages-wrap"><div class="cx-vlist cx-scroll" tabindex="0" role="list" aria-label="${T(i.label || 'List')}"><div class="cx-vspace"><div class="cx-vwin"></div></div></div><div class="cx-pg-foot"><span class="cx-num" data-vl-info></span>${i.jump ? `<button data-slot="button" class="cn-button cn-button-variant-outline cn-button-size-sm group/button" data-vl-jump>${T('Go to {0}', Number(i.jump).toLocaleString(loc))}</button>` : ''}</div></div>`;
|
|
70
70
|
const vBox = $('.cx-vlist', root), vSpace = $('.cx-vspace', root), vWin = $('.cx-vwin', root), vInfo = $('[data-vl-info]', root);
|
|
71
71
|
vSpace.style.height = VTOTAL * VROW + 'px';
|
|
72
72
|
let vFirst = -1, vRaf = 0;
|
|
@@ -80,7 +80,7 @@ export function virtualList(root, i, core) {
|
|
|
80
80
|
vWin.innerHTML = html;
|
|
81
81
|
}
|
|
82
82
|
const a = Math.floor(top / VROW) + 1, b = Math.min(VTOTAL, Math.ceil((top + h) / VROW));
|
|
83
|
-
vInfo.textContent =
|
|
83
|
+
vInfo.textContent = T('{0}–{1} of {2} · {3} rows in the DOM', a.toLocaleString(loc), b.toLocaleString(loc), VTOTAL.toLocaleString(loc), vWin.childElementCount);
|
|
84
84
|
};
|
|
85
85
|
vBox.addEventListener('scroll', () => { cancelAnimationFrame(vRaf); vRaf = requestAnimationFrame(vPaint); }, { passive: true });
|
|
86
86
|
vBox.addEventListener('keydown', (e) => { const d = { ArrowDown: VROW, ArrowUp: -VROW, PageDown: vBox.clientHeight, PageUp: -vBox.clientHeight }[e.key]; if (d) { e.preventDefault(); vBox.scrollTop += d; } if (e.key === 'Home') { e.preventDefault(); vBox.scrollTop = 0; } if (e.key === 'End') { e.preventDefault(); vBox.scrollTop = vBox.scrollHeight; } });
|
|
@@ -91,16 +91,16 @@ export function virtualList(root, i, core) {
|
|
|
91
91
|
// Carousel behaviour over the slides muten rendered inside a `.cx-car` (the Carousel part): pages, indicator
|
|
92
92
|
// (dots · progress · count · thumbs), loop, autoplay with its own pause, arrows (bar | overlay), keys and mouse drag.
|
|
93
93
|
export function carouselBehavior(root, i, core) {
|
|
94
|
-
const { $, $$, ui } = core;
|
|
94
|
+
const { $, $$, ui } = core, T = (k, ...a) => core.t(core.langOf(root), k, ...a);
|
|
95
95
|
const perView = String(i.perView || '1').split(',').map(Number), [perSm, perLg] = perView.length > 1 ? perView : [Math.min(perView[0], 2), perView[0]];
|
|
96
96
|
const center = i.center === 'on', loop = i.loop === 'on', autoplay = Number(i.autoplay) || 0, indicator = i.indicator || 'dots', arrows = i.arrows || 'bar';
|
|
97
97
|
root.style.setProperty('--per', perLg); root.style.setProperty('--per-sm', perSm);
|
|
98
98
|
if (center) root.dataset.center = '';
|
|
99
99
|
const view = $('.cx-car-view', root); view.dataset.arrows = arrows;
|
|
100
|
-
const prevB = ui.button({ icon: 'left', size: 'icon-sm', attrs:
|
|
101
|
-
const nextB = ui.button({ icon: 'right', size: 'icon-sm', attrs:
|
|
100
|
+
const prevB = ui.button({ icon: 'left', size: 'icon-sm', attrs: `data-car="prev" aria-label="${T('Previous')}" title="${T('Previous')}"`, cls: 'cx-car-arrow' });
|
|
101
|
+
const nextB = ui.button({ icon: 'right', size: 'icon-sm', attrs: `data-car="next" aria-label="${T('Next')}" title="${T('Next')}"`, cls: 'cx-car-arrow' });
|
|
102
102
|
if (arrows === 'overlay') view.insertAdjacentHTML('beforeend', prevB + nextB);
|
|
103
|
-
root.insertAdjacentHTML('beforeend', `<div class="cx-car-bar"><div class="cx-car-ind" data-ind="${indicator}"></div><div class="cx-hstack" style="gap:6px">${autoplay ? ui.button({ icon: 'pause', size: 'icon-sm', variant: 'ghost', attrs:
|
|
103
|
+
root.insertAdjacentHTML('beforeend', `<div class="cx-car-bar"><div class="cx-car-ind" data-ind="${indicator}"></div><div class="cx-hstack" style="gap:6px">${autoplay ? ui.button({ icon: 'pause', size: 'icon-sm', variant: 'ghost', attrs: `data-car="play" aria-label="${T('Pause')}" title="${T('Pause')}"` }) : ''}${arrows === 'bar' ? prevB + nextB : ''}</div></div>`);
|
|
104
104
|
const track = $('.cx-car-track', root), ind = $('.cx-car-ind', root), cards = $$('.cx-car-slide', track);
|
|
105
105
|
cards.forEach((c, k) => { c.setAttribute('role', 'group'); c.setAttribute('aria-roledescription', 'diapositiva'); c.setAttribute('aria-label', `${k + 1} de ${cards.length}`); });
|
|
106
106
|
const step = () => (cards[1] ? cards[1].offsetLeft - cards[0].offsetLeft : track.clientWidth);
|
|
@@ -116,10 +116,10 @@ export function carouselBehavior(root, i, core) {
|
|
|
116
116
|
const paint = () => {
|
|
117
117
|
const n = pages(), cur = current();
|
|
118
118
|
// the dots are tabs, so their holder is the tablist (a tab without one fails the a11y check)
|
|
119
|
-
if (indicator === 'dots') { ind.setAttribute('role', 'tablist'); ind.setAttribute('aria-label', '
|
|
119
|
+
if (indicator === 'dots') { ind.setAttribute('role', 'tablist'); ind.setAttribute('aria-label', T('Slides')); if (ind.childElementCount !== n) ind.innerHTML = Array.from({ length: n }, (_, k) => ui.button({ variant: 'ghost', size: 'icon-xs', cls: 'cx-dotb', attrs: `role="tab" data-go="${k}" aria-label="${T('Go to {0} of {1}', k + 1, n)}"`, label: '<span class="cx-dot-mark"></span>' })).join(''); $$('[data-go]', ind).forEach((d, k) => d.setAttribute('aria-selected', k === cur)); }
|
|
120
120
|
if (indicator === 'progress') ind.innerHTML = `<div class="cx-hstack" style="gap:10px;flex-wrap:nowrap">${ui.progress(((cur + 1) / n) * 100, `Diapositiva ${cur + 1} de ${n}`)}${ui.text(`${cur + 1} / ${n}`, 'caption')}</div>`;
|
|
121
121
|
if (indicator === 'count') ind.innerHTML = ui.text(`${cur + 1} de ${n}`, 'caption');
|
|
122
|
-
if (indicator === 'thumbs') { if (!ind.childElementCount) ind.innerHTML = `<div class="cx-hstack" role="tablist" aria-label="
|
|
122
|
+
if (indicator === 'thumbs') { if (!ind.childElementCount) ind.innerHTML = `<div class="cx-hstack" role="tablist" aria-label="${T('Thumbnails')}">${cards.map((_, k) => ui.button({ variant: 'ghost', size: 'icon-lg', cls: 'cx-thumb', attrs: `role="tab" data-go="${k}" aria-label="${T('Photo {0}', k + 1)}"`, label: thumbOf(k) })).join('')}</div>`; $$('[data-go]', ind).forEach((d, k) => { d.setAttribute('aria-selected', k === cur); d.setAttribute('aria-pressed', k === cur); }); }
|
|
123
123
|
if (center) cards.forEach((c, k) => c.toggleAttribute('data-active', k === cur));
|
|
124
124
|
if (!loop) { $$('[data-car="prev"]', root).forEach((b) => { b.disabled = cur === 0; }); $$('[data-car="next"]', root).forEach((b) => { b.disabled = cur >= n - 1; }); }
|
|
125
125
|
};
|
|
@@ -129,7 +129,7 @@ export function carouselBehavior(root, i, core) {
|
|
|
129
129
|
const a = e.target.closest('[data-car]'); if (!a) return;
|
|
130
130
|
if (a.dataset.car === 'prev') go(current() - 1);
|
|
131
131
|
if (a.dataset.car === 'next') go(current() + 1);
|
|
132
|
-
if (a.dataset.car === 'play') { playing = !playing; a.innerHTML = ui.icon(playing ? 'pause' : 'play'); a.setAttribute('aria-label', playing ? '
|
|
132
|
+
if (a.dataset.car === 'play') { playing = !playing; a.innerHTML = ui.icon(playing ? 'pause' : 'play'); a.setAttribute('aria-label', playing ? T('Pause') : T('Play')); a.title = a.getAttribute('aria-label'); }
|
|
133
133
|
});
|
|
134
134
|
track.tabIndex = 0;
|
|
135
135
|
track.addEventListener('keydown', (e) => { if (e.key === 'ArrowRight') { e.preventDefault(); go(current() + 1); } if (e.key === 'ArrowLeft') { e.preventDefault(); go(current() - 1); } });
|
|
@@ -146,7 +146,7 @@ export function carouselBehavior(root, i, core) {
|
|
|
146
146
|
addEventListener('pointerup', () => { if (!drag) return; const moved = drag.moved; drag = null; track.classList.remove('dragging'); if (moved) { go(nearest()); track.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); }, { capture: true, once: true }); } });
|
|
147
147
|
let playing = !!autoplay && !matchMedia('(prefers-reduced-motion: reduce)').matches, hold = false;
|
|
148
148
|
if (autoplay) {
|
|
149
|
-
if (!playing) { const b = $('[data-car="play"]', root); b.innerHTML = ui.icon('play'); b.setAttribute('aria-label', '
|
|
149
|
+
if (!playing) { const b = $('[data-car="play"]', root); b.innerHTML = ui.icon('play'); b.setAttribute('aria-label', T('Play')); }
|
|
150
150
|
['pointerenter', 'focusin', 'touchstart'].forEach((t) => root.addEventListener(t, () => { hold = true; }, { passive: true }));
|
|
151
151
|
['pointerleave', 'focusout'].forEach((t) => root.addEventListener(t, () => { hold = false; }));
|
|
152
152
|
setInterval(() => { if (playing && !hold && !document.hidden) go(loop || current() < pages() - 1 ? current() + 1 : 0); }, autoplay);
|